check headr file import peserta
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -18,8 +18,28 @@
|
||||
@if(session('import_result'))
|
||||
@php $r = session('import_result'); @endphp
|
||||
|
||||
{{-- Invalid headers --}}
|
||||
@if(!empty($r['invalid_headers']))
|
||||
<div class="card border-0 shadow-sm mb-4 border-start border-4 border-danger">
|
||||
<div class="card-body">
|
||||
<h6 class="fw-semibold mb-2 text-danger">
|
||||
<i class="bi bi-x-circle me-2"></i>Format Header CSV Tidak Sah
|
||||
</h6>
|
||||
<p class="small text-muted mb-2">
|
||||
Baris pertama fail CSV mesti mengandungi header yang betul mengikut susunan ini:
|
||||
</p>
|
||||
<code class="d-block bg-light p-2 rounded small mb-3">name,no_kp,email,phone,agency</code>
|
||||
@if(!empty($r['found_headers']))
|
||||
<p class="small text-muted mb-0">
|
||||
<i class="bi bi-exclamation-triangle text-warning me-1"></i>
|
||||
Header yang dijumpai: <code>{{ $r['found_headers'] }}</code>
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- All IC empty — offer delete --}}
|
||||
@if(!empty($r['all_empty_ic']))
|
||||
@elseif(!empty($r['all_empty_ic']))
|
||||
<div class="card border-0 shadow-sm mb-4 border-start border-4 border-warning">
|
||||
<div class="card-body">
|
||||
<h6 class="fw-semibold mb-2 text-warning">
|
||||
@@ -41,6 +61,7 @@
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
{{-- Normal import result --}}
|
||||
<div class="card border-0 shadow-sm mb-4 border-start border-4
|
||||
{{ $r['failed'] > 0 ? 'border-warning' : 'border-success' }}">
|
||||
<div class="card-body">
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user