feat: public check-in flow and attendance (Fasa 5)
- AttendanceService: staffCheckin and walkInRegister methods - CheckinController: QR-based check-in (staff & walk-in external) - AttendanceCheckController: semak kehadiran & sijil status - Views: checkin show/success/already/unavailable, semak show/result Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,9 +3,58 @@
|
||||
namespace App\Http\Controllers\Public;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Certificate;
|
||||
use App\Models\Participant;
|
||||
use App\Models\ProgramQrCode;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AttendanceCheckController extends Controller
|
||||
{
|
||||
//
|
||||
public function show(string $qr_token): View
|
||||
{
|
||||
$qrCode = ProgramQrCode::where('token', $qr_token)->where('is_active', true)->firstOrFail();
|
||||
$program = $qrCode->program;
|
||||
|
||||
abort_if($program->status !== 'published', 404);
|
||||
|
||||
return view('public.semak.show', compact('program', 'qrCode'));
|
||||
}
|
||||
|
||||
public function check(string $qr_token, Request $request): View
|
||||
{
|
||||
$qrCode = ProgramQrCode::where('token', $qr_token)->where('is_active', true)->firstOrFail();
|
||||
$program = $qrCode->program;
|
||||
|
||||
$request->validate([
|
||||
'no_kp' => ['required', 'digits:12'],
|
||||
], [
|
||||
'no_kp.required' => 'Sila masukkan No. Kad Pengenalan anda.',
|
||||
'no_kp.digits' => 'No. Kad Pengenalan mestilah 12 digit tanpa sempang.',
|
||||
]);
|
||||
|
||||
$noKp = preg_replace('/[^0-9]/', '', $request->no_kp);
|
||||
$participant = Participant::where('no_kp', $noKp)->first();
|
||||
|
||||
if (! $participant) {
|
||||
return view('public.semak.result', [
|
||||
'program' => $program,
|
||||
'qrCode' => $qrCode,
|
||||
'found' => false,
|
||||
'participant' => null,
|
||||
'attendance' => null,
|
||||
'certificate' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
$attendance = $participant->attendanceForProgram($program->id);
|
||||
$certificate = $attendance
|
||||
? Certificate::where('program_id', $program->id)
|
||||
->where('participant_id', $participant->id)
|
||||
->first()
|
||||
: null;
|
||||
|
||||
return view('public.semak.result', compact('program', 'qrCode', 'participant', 'attendance', 'certificate'))
|
||||
->with('found', (bool) $attendance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,118 @@
|
||||
namespace App\Http\Controllers\Public;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ProgramQrCode;
|
||||
use App\Services\AttendanceService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class CheckinController extends Controller
|
||||
{
|
||||
//
|
||||
public function __construct(private AttendanceService $attendanceService) {}
|
||||
|
||||
public function show(string $qr_token): View|RedirectResponse
|
||||
{
|
||||
$qrCode = ProgramQrCode::where('token', $qr_token)->where('is_active', true)->firstOrFail();
|
||||
$program = $qrCode->program;
|
||||
|
||||
if ($program->status !== 'published') {
|
||||
return view('public.checkin.unavailable', compact('program', 'qrCode'))
|
||||
->with('message', 'Program ini belum dibuka atau sudah ditutup.');
|
||||
}
|
||||
|
||||
// If download period is active → redirect to attendance check page
|
||||
if ($program->isDownloadOpen()) {
|
||||
return redirect()->route('public.semak.show', $qr_token);
|
||||
}
|
||||
|
||||
// Check-in not yet open
|
||||
if ($program->checkin_start_at && now()->lt($program->checkin_start_at)) {
|
||||
return view('public.checkin.unavailable', compact('program', 'qrCode'))
|
||||
->with('message', 'Check-in belum dibuka. Mula pada ' . $program->checkin_start_at->format('d M Y, H:i'));
|
||||
}
|
||||
|
||||
// Check-in already closed
|
||||
if ($program->checkin_end_at && now()->gt($program->checkin_end_at)) {
|
||||
return view('public.checkin.unavailable', compact('program', 'qrCode'))
|
||||
->with('message', 'Tempoh check-in telah tamat.');
|
||||
}
|
||||
|
||||
return view('public.checkin.show', compact('program', 'qrCode'));
|
||||
}
|
||||
|
||||
public function staffCheckin(string $qr_token, Request $request): View|RedirectResponse
|
||||
{
|
||||
$qrCode = ProgramQrCode::where('token', $qr_token)->where('is_active', true)->firstOrFail();
|
||||
$program = $qrCode->program;
|
||||
|
||||
$request->validate([
|
||||
'no_kp' => ['required', 'string', 'max:20'],
|
||||
], [
|
||||
'no_kp.required' => 'Sila masukkan No. Kad Pengenalan anda.',
|
||||
]);
|
||||
|
||||
$result = $this->attendanceService->staffCheckin($program, $request->no_kp, $request);
|
||||
|
||||
return match ($result['status']) {
|
||||
'success' => view('public.checkin.success', [
|
||||
'program' => $program,
|
||||
'participant' => $result['participant'],
|
||||
'attendance' => $result['attendance'],
|
||||
'qrCode' => $qrCode,
|
||||
]),
|
||||
'already_checked_in' => view('public.checkin.already', [
|
||||
'program' => $program,
|
||||
'participant' => $result['participant'],
|
||||
'attendance' => $result['attendance'],
|
||||
]),
|
||||
'not_found' => back()->withInput()
|
||||
->with('error', 'No. Kad Pengenalan tidak dijumpai dalam senarai peserta program ini.')
|
||||
->with('show_external_option', $program->allow_walk_in),
|
||||
'not_registered' => back()->withInput()
|
||||
->with('error', 'No. Kad Pengenalan tidak dijumpai dalam senarai pra-daftar.')
|
||||
->with('show_external_option', $program->allow_walk_in),
|
||||
default => back()->with('error', 'Ralat tidak dijangka. Sila cuba lagi.'),
|
||||
};
|
||||
}
|
||||
|
||||
public function externalRegister(string $qr_token, Request $request): View|RedirectResponse
|
||||
{
|
||||
$qrCode = ProgramQrCode::where('token', $qr_token)->where('is_active', true)->firstOrFail();
|
||||
$program = $qrCode->program;
|
||||
|
||||
if (! $program->allow_walk_in) {
|
||||
return back()->with('error', 'Pendaftaran orang luar tidak dibenarkan untuk program ini.');
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'no_kp' => ['required', 'digits:12'],
|
||||
'email' => ['nullable', 'email', 'max:255'],
|
||||
'phone' => ['nullable', 'string', 'max:20'],
|
||||
'agency' => ['nullable', 'string', 'max:255'],
|
||||
], [
|
||||
'name.required' => 'Sila masukkan nama penuh anda.',
|
||||
'no_kp.required' => 'Sila masukkan No. Kad Pengenalan anda.',
|
||||
'no_kp.digits' => 'No. Kad Pengenalan mestilah 12 digit tanpa sempang.',
|
||||
'email.email' => 'Format emel tidak sah.',
|
||||
]);
|
||||
|
||||
$result = $this->attendanceService->walkInRegister($program, $request->all(), $request);
|
||||
|
||||
return match ($result['status']) {
|
||||
'success' => view('public.checkin.success', [
|
||||
'program' => $program,
|
||||
'participant' => $result['participant'],
|
||||
'attendance' => $result['attendance'],
|
||||
'qrCode' => $qrCode,
|
||||
]),
|
||||
'already_checked_in' => view('public.checkin.already', [
|
||||
'program' => $program,
|
||||
'participant' => $result['participant'],
|
||||
'attendance' => $result['attendance'],
|
||||
]),
|
||||
default => back()->withInput()->with('error', 'Ralat sistem. Sila cuba lagi.'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user