Files
eCert-MBIP/app/Http/Controllers/Admin/ParticipantController.php
2026-05-19 09:53:36 +08:00

186 lines
6.8 KiB
PHP

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Participant;
use App\Models\Program;
use App\Models\ProgramParticipant;
use App\Services\AuditLogService;
use App\Services\ParticipantImportService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
use League\Csv\Writer;
use SplTempFileObject;
class ParticipantController extends Controller
{
public function index(Program $program, Request $request): View
{
$query = $program->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);
}
}