programParticipants() ->with('participant') ->latest(); if ($request->filled('search')) { $query->whereHas('participant', function ($q) use ($request) { $q->where('name', 'like', '%' . $request->search . '%') ->orWhere('agency', 'like', '%' . $request->search . '%'); }); } if ($request->filled('source')) { $query->where('registration_source', $request->source); } if ($request->filled('status')) { $query->where('status', $request->status); } $programParticipants = $query->paginate(20)->withQueryString(); $countRow = DB::table('program_participants') ->where('program_id', $program->id) ->selectRaw("COUNT(*) as total, SUM(is_pre_registered) as pre_registered, SUM(registration_source = 'walk_in') as walk_in, SUM(status = 'checked_in') as checked_in") ->first(); $counts = [ 'total' => (int) ($countRow->total ?? 0), 'pre_registered' => (int) ($countRow->pre_registered ?? 0), 'walk_in' => (int) ($countRow->walk_in ?? 0), 'checked_in' => (int) ($countRow->checked_in ?? 0), ]; return view('admin.programs.participants.index', compact('program', 'programParticipants', 'counts')); } public function create(Program $program): View { return view('admin.programs.participants.create', compact('program')); } public function store(Program $program, Request $request): RedirectResponse { $request->validate([ 'name' => ['required', 'string', 'max:255'], 'no_kp' => ['required', 'string', 'regex:/^\d{6}-?\d{2}-?\d{4}$|^\d{12}$/'], 'email' => ['nullable', 'email', 'max:255'], 'phone' => ['nullable', 'string', 'max:20'], 'agency' => ['nullable', 'string', 'max:255'], 'session' => ['nullable', 'in:pagi,petang,full_day'], ]); $noKp = preg_replace('/[^0-9]/', '', $request->no_kp); // Check duplicate in this program $existing = Participant::where('no_kp', $noKp)->first(); if ($existing && $program->programParticipants()->where('participant_id', $existing->id)->exists()) { return back()->withInput()->with('error', 'Peserta dengan No. K/P ini sudah didaftarkan dalam program ini.'); } DB::transaction(function () use ($program, $request, $noKp, $existing) { $participant = $existing ?? Participant::create([ 'name' => $request->name, 'no_kp' => $noKp, 'email' => $request->email, 'phone' => $request->phone, 'agency' => $request->agency, 'participant_type' => 'staff', ]); $program->programParticipants()->create([ 'participant_id' => $participant->id, 'registration_source' => 'admin_manual', 'is_pre_registered' => true, 'pre_registered_session'=> $request->session ?? $program->default_staff_session, 'status' => 'registered', 'registered_at' => now(), ]); AuditLogService::log('participant.added', $participant); }); return back()->with('success', 'Peserta berjaya ditambah.'); } public function destroy(Program $program, ProgramParticipant $pp): RedirectResponse { if ($pp->program_id !== $program->id) { abort(403); } if ($pp->attendance()->exists()) { return back()->with('error', 'Peserta tidak boleh dikeluarkan kerana sudah ada rekod kehadiran.'); } $pp->delete(); return back()->with('success', 'Peserta berjaya dikeluarkan daripada program.'); } public function importForm(Program $program): View { return view('admin.programs.participants.import', compact('program')); } 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'], ]); $result = $importer->import( $program, $request->file('csv_file'), $request->input('session', $program->default_staff_session) ); AuditLogService::log('participant.imported', $program, [], [ 'success' => $result['success'], 'duplicates' => $result['duplicates'], 'failed' => $result['failed'], ]); return back()->with('import_result', $result); } public function export(Program $program): \Symfony\Component\HttpFoundation\StreamedResponse { $headers = [ 'Content-Type' => 'text/csv; charset=UTF-8', 'Content-Disposition' => 'attachment; filename="peserta_' . $program->uuid . '_' . now()->format('Ymd') . '.csv"', ]; return response()->stream(function () use ($program) { $handle = fopen('php://output', 'w'); // UTF-8 BOM for Excel compatibility fputs($handle, "\xEF\xBB\xBF"); fputcsv($handle, ['Nama', 'No K/P', 'Emel', 'Telefon', 'Agensi', 'Sesi', 'Sumber', 'Status', 'Tarikh Daftar']); $program->programParticipants() ->with('participant') ->lazy() ->each(function ($pp) use ($handle) { $p = $pp->participant; fputcsv($handle, [ $p->name, $p->no_kp, $p->email, $p->phone, $p->agency, $pp->pre_registered_session ?? '—', $pp->registration_source, $pp->status, $pp->registered_at?->format('d/m/Y H:i') ?? '—', ]); }); fclose($handle); }, 200, $headers); } }