From 9e5ff6b85eedac6b48e16d821c7c3f9ff0ceee87 Mon Sep 17 00:00:00 2001 From: Saufi Date: Wed, 20 May 2026 22:07:02 +0800 Subject: [PATCH] check headr file import peserta --- src/app/Services/ParticipantImportService.php | 30 ++++++++++++++++--- .../programs/participants/import.blade.php | 23 +++++++++++++- src/routes/web.php | 6 ++-- 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/app/Services/ParticipantImportService.php b/src/app/Services/ParticipantImportService.php index 04f5fb1..a15bea8 100644 --- a/src/app/Services/ParticipantImportService.php +++ b/src/app/Services/ParticipantImportService.php @@ -13,9 +13,18 @@ use League\Csv\Reader; class ParticipantImportService { + // Column 1 must match one of these (normalized), Column 2 must match one of these + private const VALID_COL1 = ['name', 'nama']; + private const VALID_COL2 = ['nokp', 'ic', 'nric']; + + private function normalizeKey(string $key): string + { + return strtolower(preg_replace('/[^a-z0-9]/i', '', $key)); + } + public function import(Program $program, UploadedFile $file, ?string $defaultSession, bool $markAttendance = false): array { - $result = ['success' => 0, 'duplicates' => 0, 'failed' => 0, 'errors' => [], 'all_empty_ic' => false]; + $result = ['success' => 0, 'duplicates' => 0, 'failed' => 0, 'errors' => [], 'all_empty_ic' => false, 'invalid_headers' => false]; $csv = Reader::createFromPath($file->getRealPath(), 'r'); $csv->setHeaderOffset(0); @@ -25,12 +34,25 @@ class ParticipantImportService $csv->addStreamFilter('convert.iconv.UTF-8/UTF-8'); } catch (\Throwable) {} + // Validate headers — first two columns are mandatory and must be in order + $rawHeaders = $csv->getHeader(); + $normHeaders = array_map(fn($h) => $this->normalizeKey($h), $rawHeaders); + + $col1 = $normHeaders[0] ?? ''; + $col2 = $normHeaders[1] ?? ''; + + if (! in_array($col1, self::VALID_COL1) || ! in_array($col2, self::VALID_COL2)) { + $result['invalid_headers'] = true; + $result['found_headers'] = implode(', ', array_slice($rawHeaders, 0, 5)); + return $result; + } + // Collect all rows first to detect all_empty_ic $rows = []; foreach ($csv->getRecords() as $rowNum => $row) { $row = array_map('trim', $row); $row = array_combine( - array_map(fn($k) => strtolower(preg_replace('/[\x{FEFF}\s]/u', '', $k)), array_keys($row)), + array_map(fn($k) => $this->normalizeKey($k), array_keys($row)), array_values($row) ); $rows[$rowNum] = $row; @@ -42,7 +64,7 @@ class ParticipantImportService // If every row has an empty no_kp, offer delete instead $noKpValues = array_map( - fn($row) => preg_replace('/[^0-9]/', '', $row['no_kp'] ?? $row['nokp'] ?? $row['ic'] ?? ''), + fn($row) => preg_replace('/[^0-9]/', '', $row['nokp'] ?? $row['ic'] ?? ''), $rows ); if (count(array_filter($noKpValues)) === 0) { @@ -55,7 +77,7 @@ class ParticipantImportService foreach ($rows as $rowNum => $row) { $data = [ 'name' => $row['name'] ?? $row['nama'] ?? '', - 'no_kp' => preg_replace('/[^0-9]/', '', $row['no_kp'] ?? $row['nokp'] ?? $row['ic'] ?? ''), + 'no_kp' => preg_replace('/[^0-9]/', '', $row['nokp'] ?? $row['ic'] ?? ''), 'email' => $row['email'] ?? $row['emel'] ?? null, 'phone' => $row['phone'] ?? $row['telefon'] ?? null, 'agency' => $row['agency'] ?? $row['agensi'] ?? $row['jabatan'] ?? null, diff --git a/src/resources/views/admin/programs/participants/import.blade.php b/src/resources/views/admin/programs/participants/import.blade.php index 9aef42a..5273369 100644 --- a/src/resources/views/admin/programs/participants/import.blade.php +++ b/src/resources/views/admin/programs/participants/import.blade.php @@ -18,8 +18,28 @@ @if(session('import_result')) @php $r = session('import_result'); @endphp + {{-- Invalid headers --}} + @if(!empty($r['invalid_headers'])) +
+
+
+ Format Header CSV Tidak Sah +
+

+ Baris pertama fail CSV mesti mengandungi header yang betul mengikut susunan ini: +

+ name,no_kp,email,phone,agency + @if(!empty($r['found_headers'])) +

+ + Header yang dijumpai: {{ $r['found_headers'] }} +

+ @endif +
+
+ {{-- All IC empty — offer delete --}} - @if(!empty($r['all_empty_ic'])) + @elseif(!empty($r['all_empty_ic']))
@@ -41,6 +61,7 @@
@else + {{-- Normal import result --}}
diff --git a/src/routes/web.php b/src/routes/web.php index 1d257b6..cc88ae5 100644 --- a/src/routes/web.php +++ b/src/routes/web.php @@ -53,13 +53,13 @@ Route::middleware(['auth', 'admin'])->prefix('admin')->name('admin.')->group(fun Route::get('/', [ParticipantController::class, 'index'])->name('index'); Route::get('/create', [ParticipantController::class, 'create'])->name('create'); Route::post('/', [ParticipantController::class, 'store'])->name('store'); - Route::get('/{pp}/edit', [ParticipantController::class, 'edit'])->name('edit'); - Route::put('/{pp}', [ParticipantController::class, 'update'])->name('update'); - Route::delete('/{pp}', [ParticipantController::class, 'destroy'])->name('destroy'); Route::get('/import', [ParticipantController::class, 'importForm'])->name('import.form'); Route::post('/import', [ParticipantController::class, 'import'])->name('import'); Route::delete('/clear', [ParticipantController::class, 'clearParticipants'])->name('clear'); Route::get('/export', [ParticipantController::class, 'export'])->name('export'); + Route::get('/{pp}/edit', [ParticipantController::class, 'edit'])->name('edit'); + Route::put('/{pp}', [ParticipantController::class, 'update'])->name('update'); + Route::delete('/{pp}', [ParticipantController::class, 'destroy'])->name('destroy'); }); // Certificate Template