Compare commits
No commits in common. "925fbe0bd76fd222a8feaea9d5b29fc74cd0227d" and "382dc4aa03f89875d23da0e0ca4a6fc77e9c5f20" have entirely different histories.
925fbe0bd7
...
382dc4aa03
@ -37,7 +37,6 @@ public function handle()
|
||||
'migrate:company' => '🏭 Migrating companies & partner media...',
|
||||
'migrate:journalist' => '📰 Migrating journalists...',
|
||||
'migrate:media-orders' => '📦 Migrating media orders...',
|
||||
'migrate:reports' => '📋 Migrating report proofs (bukti tayang)...',
|
||||
'migrate:media-cooperation' => '🤝 Migrating media cooperation...',
|
||||
'migrate:media-monitoring' => '🖥️ Migrating media monitoring...',
|
||||
'migrate:issue-management' => '⚠️ Migrating issue management...',
|
||||
|
||||
@ -381,24 +381,6 @@ private function processReport(object $legacyReport, int $cooperationId, int $ta
|
||||
|
||||
[$cleanTitle, $cleanLink] = $this->extractLinkData($legacyReport);
|
||||
|
||||
$existingReportId = DB::table('reports')
|
||||
->where('media_task_assignment_id', $mediaTaskAssignment->id)
|
||||
->where('publication_date', $legacyReport->broadcast)
|
||||
->where('link', $cleanLink)
|
||||
->value('id');
|
||||
|
||||
if ($existingReportId) {
|
||||
if (! isset($this->migratedReportIds[$existingReportId])) {
|
||||
$report = Report::find($existingReportId);
|
||||
if ($report) {
|
||||
$this->migrateProof($report, $legacyReport);
|
||||
$this->migratedReportIds[$existingReportId] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
DB::table('reports')->insert([
|
||||
'media_task_assignment_id' => $mediaTaskAssignment->id,
|
||||
'title' => $cleanTitle,
|
||||
@ -412,13 +394,13 @@ private function processReport(object $legacyReport, int $cooperationId, int $ta
|
||||
|
||||
$newReportId = (int) DB::getPdo()->lastInsertId();
|
||||
|
||||
if (! isset($this->migratedReportIds[$newReportId])) {
|
||||
$report = Report::find($newReportId);
|
||||
if ($report) {
|
||||
$this->migrateProof($report, $legacyReport);
|
||||
$this->migratedReportIds[$newReportId] = true;
|
||||
}
|
||||
}
|
||||
// // Migrate proof image if not already done
|
||||
// if (! isset($this->migratedReportIds[$newReportId])) {
|
||||
// $report = Report::find($newReportId);
|
||||
// if ($report) {
|
||||
// $this->migrateProof($report, $legacyReport);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -134,26 +134,12 @@ private function processReport(object $legacy): void
|
||||
return;
|
||||
}
|
||||
|
||||
[$cleanTitle, $cleanLink] = $this->extractLinkData($legacy);
|
||||
|
||||
$existing = DB::table('reports')
|
||||
->where('media_task_assignment_id', $mediaTaskAssignment->id)
|
||||
->where('publication_date', $legacy->broadcast)
|
||||
->where('link', $cleanLink)
|
||||
->first();
|
||||
|
||||
if ($existing) {
|
||||
$proofMigrated = $this->migrateProofIfNeeded($existing->id, $legacy);
|
||||
$this->output->writeln($proofMigrated ? '<info>SKIP + IMG</info>' : '<info>SKIP</info>');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Insert the report
|
||||
DB::table('reports')->insert([
|
||||
'media_task_assignment_id' => $mediaTaskAssignment->id,
|
||||
'title' => $cleanTitle,
|
||||
'title' => $legacy->title ?? '',
|
||||
'publication_date' => $legacy->broadcast,
|
||||
'link' => $cleanLink,
|
||||
'link' => $legacy->link ?? '',
|
||||
'description' => '',
|
||||
'status' => $this->mapApprovalStatus((string) $legacy->status),
|
||||
'created_at' => $legacy->created_at ?? now(),
|
||||
@ -162,46 +148,32 @@ private function processReport(object $legacy): void
|
||||
|
||||
$newReportId = (int) DB::getPdo()->lastInsertId();
|
||||
|
||||
$proofMigrated = $this->migrateProofIfNeeded($newReportId, $legacy);
|
||||
// Migrate proof image if not already done
|
||||
if (! isset($this->migratedIds[$newReportId])) {
|
||||
$report = Report::find($newReportId);
|
||||
if ($report) {
|
||||
$this->migrateProof($report, $legacy);
|
||||
}
|
||||
}
|
||||
|
||||
$this->output->writeln($proofMigrated ? '<info>DONE + IMG</info>' : '<info>DONE</info>');
|
||||
$this->output->writeln('<info>DONE</info>');
|
||||
} catch (\Exception $e) {
|
||||
$this->output->writeln('<error>FAILED: '.$e->getMessage().'</error>');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register proof media for a report when not yet migrated.
|
||||
* Migrate the proof image file from S3 to the Report media library.
|
||||
*/
|
||||
private function migrateProofIfNeeded(int $reportId, object $legacy): bool
|
||||
{
|
||||
if (isset($this->migratedIds[$reportId])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$report = Report::find($reportId);
|
||||
if (! $report) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->migrateProof($report, $legacy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate the proof image metadata (file stays on S3 legacy path).
|
||||
*/
|
||||
private function migrateProof(Report $report, object $legacy): bool
|
||||
private function migrateProof(Report $report, object $legacy): void
|
||||
{
|
||||
$legacyPath = trim($legacy->proof ?? '');
|
||||
if (empty($legacyPath) || $legacyPath === '-' || $legacyPath === 'no-image.png') {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
$sourcePath = 'old-data/reports/'.$legacyPath;
|
||||
$this->importProofFile($report, $sourcePath, $legacy);
|
||||
$this->migratedIds[$report->id] = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -250,45 +222,6 @@ private function guessMime(string $fileName): string
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Ekstrak title & link bersih dari legacy report.
|
||||
*
|
||||
* @return array{0: string, 1: string} [title, link]
|
||||
*/
|
||||
private function extractLinkData(object $legacyReport): array
|
||||
{
|
||||
$rawTitle = trim($legacyReport->title ?? '');
|
||||
$rawLink = trim($legacyReport->link ?? '');
|
||||
|
||||
$titleIsUrl = (bool) preg_match('#^https?://#i', $rawTitle);
|
||||
|
||||
preg_match('#https?://\S+#', $rawLink, $urlMatches);
|
||||
$extractedUrl = rtrim($urlMatches[0] ?? '', '.,;)');
|
||||
|
||||
if ($titleIsUrl) {
|
||||
$cleanLink = $rawTitle;
|
||||
|
||||
$lines = array_values(array_filter(
|
||||
array_map('trim', preg_split('/[\r\n]+/', $rawLink))
|
||||
));
|
||||
$candidateTitle = '';
|
||||
foreach ($lines as $line) {
|
||||
if (! preg_match('#^https?://#i', $line)) {
|
||||
$candidateTitle = $line;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$cleanTitle = $candidateTitle !== ''
|
||||
? mb_substr($candidateTitle, 0, 255)
|
||||
: mb_substr($rawTitle, 0, 255);
|
||||
} else {
|
||||
$cleanLink = $extractedUrl ?: mb_substr($rawLink, 0, 2048);
|
||||
$cleanTitle = mb_substr($rawTitle, 0, 255);
|
||||
}
|
||||
|
||||
return [$cleanTitle, $cleanLink];
|
||||
}
|
||||
|
||||
/**
|
||||
* Map legacy reports.status integer to ApprovalStatus enum value.
|
||||
* Legacy: 0 = menunggu, 1 = menerima, 2 = menolak
|
||||
|
||||
@ -156,8 +156,7 @@ public function form(Schema $schema): Schema
|
||||
'indigo' => '🌌 Indigo (Ungu Gelap)',
|
||||
])
|
||||
->placeholder('Pilih warna tema')
|
||||
->native(false)
|
||||
->required(),
|
||||
->native(false),
|
||||
|
||||
Select::make('settings.font')
|
||||
->label('Jenis Huruf (Font)')
|
||||
@ -169,8 +168,7 @@ public function form(Schema $schema): Schema
|
||||
'Montserrat' => 'Montserrat (Classic)',
|
||||
'Lexend' => 'Lexend (Readable)',
|
||||
])
|
||||
->native(false)
|
||||
->required(),
|
||||
->native(false),
|
||||
|
||||
Select::make('settings.content_width')
|
||||
->label('Lebar Konten')
|
||||
@ -178,8 +176,7 @@ public function form(Schema $schema): Schema
|
||||
'full' => '↔️ Lebar Penuh (Full)',
|
||||
'centered' => '🏢 Terpusat (Centered)',
|
||||
])
|
||||
->native(false)
|
||||
->required(),
|
||||
->native(false),
|
||||
|
||||
Select::make('settings.border_radius')
|
||||
->label('Radius Sudut (Border)')
|
||||
@ -189,8 +186,7 @@ public function form(Schema $schema): Schema
|
||||
'lg' => '🎉 Rounded (Cheerful)',
|
||||
'xl' => '🎈 Extra Round',
|
||||
])
|
||||
->native(false)
|
||||
->required(),
|
||||
->native(false),
|
||||
|
||||
Toggle::make('settings.top_navigation')
|
||||
->label('Navigasi Atas (Top Nav)')
|
||||
|
||||
@ -3,18 +3,14 @@
|
||||
namespace App\Filament\Resources\Manage\Cooperations\Actions\Report;
|
||||
|
||||
use App\Enums\RoleEnum;
|
||||
use App\Filament\Support\MediaDataUri;
|
||||
use App\Settings\GeneralSettings;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
|
||||
class ExportAllReportsPdfAction extends Action
|
||||
{
|
||||
private const MAX_RECORDS = 500;
|
||||
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'export_all_pdf';
|
||||
@ -28,43 +24,29 @@ protected function setUp(): void
|
||||
->color('info')
|
||||
->icon('heroicon-o-arrow-down-tray')
|
||||
->action(function (Table $table) {
|
||||
$query = $table->getLivewire()->getFilteredTableQuery();
|
||||
$count = (clone $query)->count();
|
||||
|
||||
if ($count > self::MAX_RECORDS) {
|
||||
Notification::make()
|
||||
->title('Data terlalu banyak')
|
||||
->body('Maksimal '.self::MAX_RECORDS.' laporan per unduhan. Gunakan filter tabel untuk mempersempit data.')
|
||||
->warning()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$settings = app(GeneralSettings::class);
|
||||
|
||||
$records = $query
|
||||
->with([
|
||||
'mediaTaskAssignment.partnerMedia',
|
||||
'media',
|
||||
])
|
||||
->get();
|
||||
$records = $table->getLivewire()->getFilteredTableQuery()->get();
|
||||
|
||||
$logoPath = $settings->site_icon ? storage_path('app/public/'.$settings->site_icon) : null;
|
||||
$logoBase64 = MediaDataUri::fromPath($logoPath, maxWidth: 60, maxHeight: 60);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$record->imageBase64 = MediaDataUri::thumbnailForPdf(
|
||||
$record->getFirstMedia('reports'),
|
||||
maxWidth: 150,
|
||||
maxHeight: 80,
|
||||
);
|
||||
$logoBase64 = null;
|
||||
if ($logoPath && file_exists($logoPath)) {
|
||||
$logoData = base64_encode(file_get_contents($logoPath));
|
||||
$logoBase64 = 'data:image/'.pathinfo($logoPath, PATHINFO_EXTENSION).';base64,'.$logoData;
|
||||
}
|
||||
|
||||
return response()->streamDownload(function () use ($records, $settings, $logoBase64) {
|
||||
@ini_set('memory_limit', '512M');
|
||||
set_time_limit(120);
|
||||
$records->each(function ($record) {
|
||||
$image = $record->getFirstMedia('reports');
|
||||
$record->imageBase64 = null;
|
||||
if ($image) {
|
||||
$imagePath = $image->getPath();
|
||||
if (file_exists($imagePath)) {
|
||||
$imageData = base64_encode(file_get_contents($imagePath));
|
||||
$record->imageBase64 = 'data:image/'.pathinfo($imagePath, PATHINFO_EXTENSION).';base64,'.$imageData;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return response()->streamDownload(function () use ($records, $settings, $logoBase64) {
|
||||
echo Pdf::loadHtml(
|
||||
Blade::render('filament.manage.reports.print-all', [
|
||||
'records' => $records,
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
namespace App\Filament\Resources\Manage\Cooperations\Actions\Report;
|
||||
|
||||
use App\Enums\RoleEnum;
|
||||
use App\Filament\Support\MediaDataUri;
|
||||
use App\Models\Report;
|
||||
use App\Settings\GeneralSettings;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
@ -28,17 +27,24 @@ protected function setUp(): void
|
||||
$settings = app(GeneralSettings::class);
|
||||
|
||||
$logoPath = $settings->site_icon ? storage_path('app/public/'.$settings->site_icon) : null;
|
||||
$logoBase64 = MediaDataUri::fromPath($logoPath, maxWidth: 80, maxHeight: 100);
|
||||
|
||||
$imageBase64 = MediaDataUri::thumbnailForPdf(
|
||||
$record->getFirstMedia('reports'),
|
||||
maxWidth: 800,
|
||||
maxHeight: 400,
|
||||
);
|
||||
$logoBase64 = null;
|
||||
if ($logoPath && file_exists($logoPath)) {
|
||||
$logoData = base64_encode(file_get_contents($logoPath));
|
||||
$logoBase64 = 'data:image/'.pathinfo($logoPath, PATHINFO_EXTENSION).';base64,'.$logoData;
|
||||
}
|
||||
|
||||
$image = $record->getFirstMedia('reports');
|
||||
$imageBase64 = null;
|
||||
if ($image) {
|
||||
$imagePath = $image->getPath();
|
||||
if (file_exists($imagePath)) {
|
||||
$imageData = base64_encode(file_get_contents($imagePath));
|
||||
$imageBase64 = 'data:image/'.pathinfo($imagePath, PATHINFO_EXTENSION).';base64,'.$imageData;
|
||||
}
|
||||
}
|
||||
|
||||
return response()->streamDownload(function () use ($record, $settings, $logoBase64, $imageBase64) {
|
||||
set_time_limit(60);
|
||||
|
||||
echo Pdf::loadHtml(
|
||||
Blade::render('filament.manage.reports.print', [
|
||||
'record' => $record,
|
||||
|
||||
@ -45,6 +45,7 @@ public static function configure(Schema $schema): Schema
|
||||
->native(false)
|
||||
->displayFormat('l, d F Y')
|
||||
->required()
|
||||
->minDate(fn ($context) => $context === 'create' ? today() : null)
|
||||
->reactive(),
|
||||
|
||||
DatePicker::make('final_submission_date')
|
||||
|
||||
@ -46,7 +46,8 @@ public static function configure(Schema $schema): Schema
|
||||
->label('Penulis')
|
||||
->placeholder('John Doe')
|
||||
->autocomplete(false)
|
||||
->required(),
|
||||
->required()
|
||||
->maxLength(50),
|
||||
|
||||
TextInput::make('link')
|
||||
->label('Tautan (URL)')
|
||||
|
||||
@ -58,7 +58,6 @@ public static function configure(Table $table): Table
|
||||
|
||||
TextColumn::make('writter')
|
||||
->label('Penulis')
|
||||
->limit(30)
|
||||
->searchable()
|
||||
->sortable(),
|
||||
|
||||
|
||||
@ -1,127 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Support;
|
||||
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||
|
||||
class MediaDataUri
|
||||
{
|
||||
public static function fromMedia(?Media $media): ?string
|
||||
{
|
||||
if (! $media) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$disk = Storage::disk($media->disk);
|
||||
$path = $media->getPathRelativeToRoot();
|
||||
|
||||
if (! $disk->exists($path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mime = $media->mime_type ?: 'application/octet-stream';
|
||||
|
||||
return 'data:'.$mime.';base64,'.base64_encode($disk->get($path));
|
||||
}
|
||||
|
||||
public static function fromPath(?string $absolutePath, int $maxWidth = 80, int $maxHeight = 80): ?string
|
||||
{
|
||||
if (! $absolutePath || ! file_exists($absolutePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$contents = file_get_contents($absolutePath);
|
||||
|
||||
if ($contents === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::encodeResizedImage($contents, $maxWidth, $maxHeight)
|
||||
?? self::encodeRawFile($absolutePath, $contents);
|
||||
}
|
||||
|
||||
public static function thumbnailForPdf(?Media $media, int $maxWidth = 150, int $maxHeight = 80): ?string
|
||||
{
|
||||
if (! $media) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$disk = Storage::disk($media->disk);
|
||||
$path = $media->getPathRelativeToRoot();
|
||||
|
||||
if (! $disk->exists($path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$mime = $media->mime_type ?? '';
|
||||
|
||||
if (! str_starts_with($mime, 'image/')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$contents = $disk->get($path);
|
||||
|
||||
return self::encodeResizedImage($contents, $maxWidth, $maxHeight)
|
||||
?? self::fromMedia($media);
|
||||
}
|
||||
|
||||
private static function encodeResizedImage(string $contents, int $maxWidth, int $maxHeight): ?string
|
||||
{
|
||||
if (! extension_loaded('gd')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$image = @imagecreatefromstring($contents);
|
||||
|
||||
if ($image === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$width = imagesx($image);
|
||||
$height = imagesy($image);
|
||||
$ratio = min($maxWidth / $width, $maxHeight / $height, 1);
|
||||
$newWidth = max(1, (int) round($width * $ratio));
|
||||
$newHeight = max(1, (int) round($height * $ratio));
|
||||
|
||||
$thumb = imagecreatetruecolor($newWidth, $newHeight);
|
||||
|
||||
if ($thumb === false) {
|
||||
imagedestroy($image);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
imagecopyresampled($thumb, $image, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height);
|
||||
imagedestroy($image);
|
||||
|
||||
ob_start();
|
||||
imagejpeg($thumb, quality: 75);
|
||||
imagedestroy($thumb);
|
||||
$jpeg = ob_get_clean();
|
||||
|
||||
if ($jpeg === false || $jpeg === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'data:image/jpeg;base64,'.base64_encode($jpeg);
|
||||
}
|
||||
|
||||
private static function encodeRawFile(string $absolutePath, string $contents): ?string
|
||||
{
|
||||
$extension = strtolower(pathinfo($absolutePath, PATHINFO_EXTENSION));
|
||||
$mime = match ($extension) {
|
||||
'png' => 'image/png',
|
||||
'jpg', 'jpeg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'webp' => 'image/webp',
|
||||
default => null,
|
||||
};
|
||||
|
||||
if ($mime === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'data:'.$mime.';base64,'.base64_encode($contents);
|
||||
}
|
||||
}
|
||||
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('media_monitorings', function (Blueprint $table) {
|
||||
$table->text('writter')->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('media_monitorings', function (Blueprint $table) {
|
||||
$table->string('writter', 50)->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -3,4 +3,4 @@
|
||||
use Illuminate\Support\Facades\Schedule;
|
||||
|
||||
Schedule::command('cooperation:update-status')->daily();
|
||||
Schedule::command('monitoring:crawl')->daily();
|
||||
Schedule::command('monitoring:crawl')->dailyAt('12:00');
|
||||
|
||||
@ -20,13 +20,12 @@
|
||||
expect(CooperationStatus::options())->toEqual([
|
||||
1 => 'Menunggu',
|
||||
2 => 'Media Order',
|
||||
3 => 'Pembayaran',
|
||||
4 => 'Selesai',
|
||||
4 => 'Pembayaran',
|
||||
5 => 'Selesai',
|
||||
]);
|
||||
});
|
||||
|
||||
test('cooperation status enum values and comment', function () {
|
||||
expect(CooperationStatus::values())->toEqual([1, 2, 3, 4]);
|
||||
expect(CooperationStatus::comment())->toBe('1: Menunggu, 2: Media Order, 3: Pembayaran, 4: Selesai');
|
||||
expect(CooperationStatus::values())->toEqual([1, 2, 4, 5]);
|
||||
expect(CooperationStatus::comment())->toBe('1: Menunggu, 2: Media Order, 4: Pembayaran, 5: Selesai');
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user