tambah fungsi upload peserta sebagai hadir
This commit is contained in:
@@ -112,6 +112,58 @@ class ParticipantController extends Controller
|
||||
return back()->with('success', 'Peserta berjaya ditambah.');
|
||||
}
|
||||
|
||||
public function edit(Program $program, ProgramParticipant $pp, Request $request): View
|
||||
{
|
||||
if ($pp->program_id !== $program->id) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$pp->load('participant');
|
||||
$filters = $request->only(['search', 'source', 'status', 'page']);
|
||||
|
||||
return view('admin.programs.participants.edit', compact('program', 'pp', 'filters'));
|
||||
}
|
||||
|
||||
public function update(Program $program, ProgramParticipant $pp, Request $request): RedirectResponse
|
||||
{
|
||||
if ($pp->program_id !== $program->id) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'no_kp' => ['required', 'string', 'regex:/^\d{12}$/', 'unique:participants,no_kp,' . $pp->participant_id],
|
||||
'email' => ['nullable', 'email', 'max:255'],
|
||||
'phone' => ['nullable', 'string', 'max:20'],
|
||||
'agency' => ['nullable', 'string', 'max:255'],
|
||||
'session' => ['nullable', 'in:pagi,petang,full_day'],
|
||||
]);
|
||||
|
||||
$pp->load('participant');
|
||||
|
||||
DB::transaction(function () use ($pp, $request) {
|
||||
$pp->participant->update([
|
||||
'name' => $request->name,
|
||||
'no_kp' => preg_replace('/[^0-9]/', '', $request->no_kp),
|
||||
'email' => $request->email ?: null,
|
||||
'phone' => $request->phone ?: null,
|
||||
'agency' => $request->agency ?: null,
|
||||
]);
|
||||
|
||||
$pp->update([
|
||||
'pre_registered_session' => $request->session ?: null,
|
||||
]);
|
||||
});
|
||||
|
||||
AuditLogService::log('participant.updated', $pp->participant);
|
||||
|
||||
$filters = array_filter($request->only(['search', 'source', 'status', 'page']));
|
||||
$indexUrl = route('admin.programs.participants.index', $program)
|
||||
. ($filters ? '?' . http_build_query($filters) : '');
|
||||
|
||||
return redirect($indexUrl)->with('success', 'Maklumat peserta berjaya dikemaskini.');
|
||||
}
|
||||
|
||||
public function destroy(Program $program, ProgramParticipant $pp): RedirectResponse
|
||||
{
|
||||
if ($pp->program_id !== $program->id) {
|
||||
@@ -129,31 +181,52 @@ class ParticipantController extends Controller
|
||||
|
||||
public function importForm(Program $program): View
|
||||
{
|
||||
return view('admin.programs.participants.import', compact('program'));
|
||||
$cutoff = $program->checkin_end_at ?? $program->end_date->endOfDay();
|
||||
$programEnded = now()->gt($cutoff);
|
||||
|
||||
return view('admin.programs.participants.import', compact('program', 'programEnded'));
|
||||
}
|
||||
|
||||
public function import(Program $program, Request $request, ParticipantImportService $importer): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'csv_file' => ['required', 'file', 'mimes:csv,txt', 'max:5120'],
|
||||
'session' => ['nullable', 'in:pagi,petang,full_day'],
|
||||
'csv_file' => ['required', 'file', 'mimes:csv,txt', 'max:5120'],
|
||||
'session' => ['nullable', 'in:pagi,petang,full_day'],
|
||||
'mark_attendance' => ['nullable', 'boolean'],
|
||||
]);
|
||||
|
||||
$cutoff = $program->checkin_end_at ?? $program->end_date->endOfDay();
|
||||
$markAttendance = now()->gt($cutoff) && $request->boolean('mark_attendance');
|
||||
|
||||
$result = $importer->import(
|
||||
$program,
|
||||
$request->file('csv_file'),
|
||||
$request->input('session', $program->default_staff_session)
|
||||
$request->input('session', $program->default_staff_session),
|
||||
$markAttendance
|
||||
);
|
||||
|
||||
AuditLogService::log('participant.imported', $program, [], [
|
||||
'success' => $result['success'],
|
||||
'duplicates' => $result['duplicates'],
|
||||
'failed' => $result['failed'],
|
||||
'success' => $result['success'],
|
||||
'duplicates' => $result['duplicates'],
|
||||
'failed' => $result['failed'],
|
||||
'mark_attendance'=> $markAttendance,
|
||||
]);
|
||||
|
||||
return back()->with('import_result', $result);
|
||||
}
|
||||
|
||||
public function clearParticipants(Program $program): RedirectResponse
|
||||
{
|
||||
$deleted = $program->programParticipants()
|
||||
->where('status', '!=', 'checked_in')
|
||||
->whereDoesntHave('attendance')
|
||||
->delete();
|
||||
|
||||
return redirect()
|
||||
->route('admin.programs.participants.import.form', $program)
|
||||
->with('success', "{$deleted} rekod peserta (belum hadir) telah dipadam.");
|
||||
}
|
||||
|
||||
public function export(Program $program): \Symfony\Component\HttpFoundation\StreamedResponse
|
||||
{
|
||||
$headers = [
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Attendance;
|
||||
use App\Models\Participant;
|
||||
use App\Models\Program;
|
||||
use App\Models\ProgramParticipant;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
@@ -11,37 +13,54 @@ use League\Csv\Reader;
|
||||
|
||||
class ParticipantImportService
|
||||
{
|
||||
public function import(Program $program, UploadedFile $file, ?string $defaultSession): array
|
||||
public function import(Program $program, UploadedFile $file, ?string $defaultSession, bool $markAttendance = false): array
|
||||
{
|
||||
$result = ['success' => 0, 'duplicates' => 0, 'failed' => 0, 'errors' => []];
|
||||
$result = ['success' => 0, 'duplicates' => 0, 'failed' => 0, 'errors' => [], 'all_empty_ic' => false];
|
||||
|
||||
$csv = Reader::createFromPath($file->getRealPath(), 'r');
|
||||
$csv->setHeaderOffset(0);
|
||||
|
||||
// Strip UTF-8 BOM if present (Excel-exported CSV)
|
||||
$csv->setOutputBOM('');
|
||||
try {
|
||||
$csv->addStreamFilter('convert.iconv.UTF-8/UTF-8');
|
||||
} catch (\Throwable) {}
|
||||
|
||||
// Collect all rows first to detect all_empty_ic
|
||||
$rows = [];
|
||||
foreach ($csv->getRecords() as $rowNum => $row) {
|
||||
$row = array_map('trim', $row);
|
||||
|
||||
// Normalise header keys (lowercase, strip BOM)
|
||||
$row = array_combine(
|
||||
array_map(fn($k) => strtolower(preg_replace('/[\x{FEFF}\s]/u', '', $k)), array_keys($row)),
|
||||
array_values($row)
|
||||
);
|
||||
$rows[$rowNum] = $row;
|
||||
}
|
||||
|
||||
if (empty($rows)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// 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'] ?? ''),
|
||||
$rows
|
||||
);
|
||||
if (count(array_filter($noKpValues)) === 0) {
|
||||
$result['all_empty_ic'] = true;
|
||||
return $result;
|
||||
}
|
||||
|
||||
$session = $defaultSession ?? $program->default_staff_session;
|
||||
|
||||
foreach ($rows as $rowNum => $row) {
|
||||
$data = [
|
||||
'name' => $row['name'] ?? $row['nama'] ?? '',
|
||||
'name' => $row['name'] ?? $row['nama'] ?? '',
|
||||
'no_kp' => preg_replace('/[^0-9]/', '', $row['no_kp'] ?? $row['nokp'] ?? $row['ic'] ?? ''),
|
||||
'email' => $row['email'] ?? $row['emel'] ?? null,
|
||||
'phone' => $row['phone'] ?? $row['telefon'] ?? $row['phone'] ?? null,
|
||||
'agency' => $row['agency'] ?? $row['agensi'] ?? $row['jabatan'] ?? null,
|
||||
'email' => $row['email'] ?? $row['emel'] ?? null,
|
||||
'phone' => $row['phone'] ?? $row['telefon'] ?? null,
|
||||
'agency' => $row['agency'] ?? $row['agensi'] ?? $row['jabatan'] ?? null,
|
||||
];
|
||||
|
||||
// Validate row
|
||||
$validator = Validator::make($data, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'no_kp' => ['required', 'digits:12'],
|
||||
@@ -56,8 +75,7 @@ class ParticipantImportService
|
||||
}
|
||||
|
||||
try {
|
||||
DB::transaction(function () use ($program, $data, $defaultSession, &$result) {
|
||||
// Find or create participant by no_kp
|
||||
DB::transaction(function () use ($program, $data, $session, $markAttendance, &$result) {
|
||||
$participant = Participant::firstOrCreate(
|
||||
['no_kp' => $data['no_kp']],
|
||||
[
|
||||
@@ -69,25 +87,36 @@ class ParticipantImportService
|
||||
]
|
||||
);
|
||||
|
||||
// Check duplicate in this program
|
||||
$exists = $program->programParticipants()
|
||||
->where('participant_id', $participant->id)
|
||||
->exists();
|
||||
$pp = $program->programParticipants()
|
||||
->where('participant_id', $participant->id)
|
||||
->first();
|
||||
|
||||
if ($exists) {
|
||||
$result['duplicates']++;
|
||||
if ($pp) {
|
||||
// Participant already registered
|
||||
if ($markAttendance) {
|
||||
$this->recordAttendance($program, $participant, $pp, $session);
|
||||
$result['duplicates']++;
|
||||
} else {
|
||||
$result['duplicates']++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$program->programParticipants()->create([
|
||||
$newStatus = $markAttendance ? 'checked_in' : 'registered';
|
||||
|
||||
$pp = $program->programParticipants()->create([
|
||||
'participant_id' => $participant->id,
|
||||
'registration_source' => 'import',
|
||||
'is_pre_registered' => true,
|
||||
'pre_registered_session' => $defaultSession,
|
||||
'status' => 'registered',
|
||||
'pre_registered_session' => $session,
|
||||
'status' => $newStatus,
|
||||
'registered_at' => now(),
|
||||
]);
|
||||
|
||||
if ($markAttendance) {
|
||||
$this->recordAttendance($program, $participant, $pp, $session);
|
||||
}
|
||||
|
||||
$result['success']++;
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
@@ -98,4 +127,25 @@ class ParticipantImportService
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function recordAttendance(Program $program, Participant $participant, ProgramParticipant $pp, ?string $session): void
|
||||
{
|
||||
$alreadyAttended = Attendance::where('program_id', $program->id)
|
||||
->where('participant_id', $participant->id)
|
||||
->exists();
|
||||
if ($alreadyAttended) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pp->update(['status' => 'checked_in']);
|
||||
|
||||
Attendance::create([
|
||||
'program_id' => $program->id,
|
||||
'participant_id' => $participant->id,
|
||||
'program_participant_id' => $pp->id,
|
||||
'attendance_source' => 'import',
|
||||
'attendance_session' => $session ?? 'full_day',
|
||||
'checked_in_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
DB::statement("ALTER TABLE attendances MODIFY attendance_source ENUM('pre_registered_staff','walk_in_external','admin_manual','import') NOT NULL");
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
DB::statement("ALTER TABLE attendances MODIFY attendance_source ENUM('pre_registered_staff','walk_in_external','admin_manual') NOT NULL");
|
||||
}
|
||||
};
|
||||
29
src/package-lock.json
generated
29
src/package-lock.json
generated
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "ecert",
|
||||
"name": "src",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
@@ -16,6 +16,29 @@
|
||||
"vite": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.1",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
|
||||
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||
@@ -67,7 +90,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
@@ -920,7 +942,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -1092,7 +1113,6 @@
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -1123,7 +1143,6 @@
|
||||
"integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.4",
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('title', 'Edit Peserta — ' . $pp->participant->name)
|
||||
@section('header', 'Edit Maklumat Peserta')
|
||||
|
||||
@section('breadcrumb')
|
||||
<li class="breadcrumb-item"><a href="{{ route('admin.programs.index') }}">Program</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('admin.programs.show', $program) }}">{{ Str::limit($program->title, 25) }}</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('admin.programs.participants.index', $program) }}">Peserta</a></li>
|
||||
<li class="breadcrumb-item active">Edit</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-6">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white border-bottom py-3">
|
||||
<span class="fw-semibold">
|
||||
<i class="bi bi-person-gear me-2 text-primary"></i>Kemaskini Maklumat Peserta
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ route('admin.programs.participants.update', [$program, $pp]) }}">
|
||||
@csrf @method('PUT')
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-medium">Nama Penuh <span class="text-danger">*</span></label>
|
||||
<input type="text" name="name" class="form-control @error('name') is-invalid @enderror"
|
||||
value="{{ old('name', $pp->participant->name) }}" required>
|
||||
@error('name')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-medium">No. Kad Pengenalan <span class="text-danger">*</span></label>
|
||||
<input type="text" name="no_kp" class="form-control @error('no_kp') is-invalid @enderror"
|
||||
value="{{ old('no_kp', $pp->participant->no_kp) }}"
|
||||
placeholder="12 digit tanpa sempang" required>
|
||||
@error('no_kp')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-medium">Emel</label>
|
||||
<input type="email" name="email" class="form-control @error('email') is-invalid @enderror"
|
||||
value="{{ old('email', $pp->participant->email) }}"
|
||||
placeholder="Kosongkan jika tiada">
|
||||
@error('email')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-medium">No. Telefon</label>
|
||||
<input type="text" name="phone" class="form-control @error('phone') is-invalid @enderror"
|
||||
value="{{ old('phone', $pp->participant->phone) }}"
|
||||
placeholder="Kosongkan jika tiada">
|
||||
@error('phone')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-medium">Jabatan / Agensi</label>
|
||||
<input type="text" name="agency" class="form-control @error('agency') is-invalid @enderror"
|
||||
value="{{ old('agency', $pp->participant->agency) }}"
|
||||
placeholder="Kosongkan jika tiada">
|
||||
@error('agency')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-medium">Sesi</label>
|
||||
<select name="session" class="form-select @error('session') is-invalid @enderror">
|
||||
<option value="">— Tiada Sesi —</option>
|
||||
<option value="pagi" {{ old('session', $pp->pre_registered_session) === 'pagi' ? 'selected' : '' }}>Pagi</option>
|
||||
<option value="petang" {{ old('session', $pp->pre_registered_session) === 'petang' ? 'selected' : '' }}>Petang</option>
|
||||
<option value="full_day" {{ old('session', $pp->pre_registered_session) === 'full_day' ? 'selected' : '' }}>Sehari Penuh</option>
|
||||
</select>
|
||||
@error('session')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
@foreach($filters as $key => $value)
|
||||
<input type="hidden" name="{{ $key }}" value="{{ $value }}">
|
||||
@endforeach
|
||||
|
||||
@php $backUrl = route('admin.programs.participants.index', $program) . ($filters ? '?' . http_build_query($filters) : ''); @endphp
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check-lg me-1"></i> Simpan
|
||||
</button>
|
||||
<a href="{{ $backUrl }}" class="btn btn-outline-secondary">Batal</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -17,6 +17,30 @@
|
||||
{{-- Import Result --}}
|
||||
@if(session('import_result'))
|
||||
@php $r = session('import_result'); @endphp
|
||||
|
||||
{{-- All IC empty — offer delete --}}
|
||||
@if(!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">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>Semua No. K/P Kosong
|
||||
</h6>
|
||||
<p class="small text-muted mb-3">
|
||||
Fail CSV yang dimuat naik tidak mengandungi sebarang No. K/P yang sah.
|
||||
Tiada rekod diimport. Adakah anda ingin <strong>memadam semua peserta belum hadir</strong> dalam program ini?
|
||||
</p>
|
||||
<form method="POST" action="{{ route('admin.programs.participants.clear', $program) }}">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn btn-danger btn-sm"
|
||||
onclick="return confirm('Padam semua peserta yang belum hadir? Tindakan ini tidak boleh dibatalkan.')">
|
||||
<i class="bi bi-trash me-1"></i> Padam Peserta Belum Hadir
|
||||
</button>
|
||||
<a href="{{ route('admin.programs.participants.import.form', $program) }}"
|
||||
class="btn btn-outline-secondary btn-sm ms-2">Batal</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="card border-0 shadow-sm mb-4 border-start border-4
|
||||
{{ $r['failed'] > 0 ? 'border-warning' : 'border-success' }}">
|
||||
<div class="card-body">
|
||||
@@ -59,6 +83,7 @@
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
{{-- Upload Form --}}
|
||||
<div class="card border-0 shadow-sm">
|
||||
@@ -79,7 +104,7 @@
|
||||
@error('csv_file')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-medium">Sesi Default</label>
|
||||
<select name="session" class="form-select">
|
||||
<option value="">— Ikut Tetapan Program —</option>
|
||||
@@ -90,6 +115,24 @@
|
||||
<div class="form-text">Sesi yang akan digunakan untuk semua peserta dalam fail ini.</div>
|
||||
</div>
|
||||
|
||||
@if($programEnded)
|
||||
<div class="mb-4 p-3 bg-warning bg-opacity-10 rounded border border-warning-subtle">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="mark_attendance"
|
||||
value="1" id="markAttendance">
|
||||
<label class="form-check-label fw-medium" for="markAttendance">
|
||||
Tandakan sebagai Data Kehadiran
|
||||
</label>
|
||||
</div>
|
||||
<div class="small text-muted mt-1 ms-4">
|
||||
<i class="bi bi-info-circle me-1"></i>
|
||||
Tempoh check-in telah tamat pada <strong>{{ ($program->checkin_end_at ?? $program->end_date->endOfDay())->format('d M Y, H:i') }}</strong>.
|
||||
Jika ditanda, semua peserta dalam fail ini akan direkodkan sebagai <strong>hadir</strong>.
|
||||
Peserta sedia ada dalam program akan dikemaskini status kehadirannya.
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100">
|
||||
<i class="bi bi-upload me-2"></i>Mula Import
|
||||
</button>
|
||||
|
||||
@@ -201,6 +201,10 @@
|
||||
<i class="bi bi-download"></i>
|
||||
</a>
|
||||
@endif
|
||||
<a href="{{ route('admin.programs.participants.edit', [$program, $pp]) . (request()->hasAny(['search','source','status','page']) ? '?' . http_build_query(request()->only(['search','source','status','page'])) : '') }}"
|
||||
class="btn btn-sm btn-outline-secondary" title="Edit Peserta">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</a>
|
||||
@if($pp->status !== 'checked_in')
|
||||
<form method="POST"
|
||||
action="{{ route('admin.programs.participants.destroy', [$program, $pp]) }}"
|
||||
|
||||
@@ -53,9 +53,12 @@ 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');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user