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'])) +
+ 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'] }}
+