diff --git a/app/Http/Controllers/PublicStudentLookupController.php b/app/Http/Controllers/PublicStudentLookupController.php index 39b88b8..cf08f09 100644 --- a/app/Http/Controllers/PublicStudentLookupController.php +++ b/app/Http/Controllers/PublicStudentLookupController.php @@ -4,7 +4,9 @@ namespace App\Http\Controllers; use App\Models\DocumentType; use App\Models\Student; +use App\Models\StudentDocument; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Storage; use Illuminate\View\View; class PublicStudentLookupController extends Controller @@ -75,23 +77,63 @@ class PublicStudentLookupController extends Controller protected function resolvePhotoUrl(Student $student): ?string { $photoDocument = $student->documents - ->filter(function ($document) { - $code = strtoupper((string) ($document->documentType?->code ?? '')); - $name = strtolower((string) ($document->documentType?->name ?? '')); - - return $code === 'PAS_FOTO' - || $code === 'PASFOTO' - || str_contains($name, 'pas foto') - || str_contains($name, 'pasfoto'); + ->filter(fn ($document) => $this->isPhotoDocument($document)) + ->sortByDesc(function ($document) { + return sprintf( + '%d-%s', + $document->verification_status === 'valid' ? 1 : 0, + optional($document->created_at)?->format('YmdHis') ?? '00000000000000' + ); }) - ->sortByDesc('created_at') ->first(); + if (! $photoDocument && $student->relationLoaded('documents') === false) { + $photoDocument = StudentDocument::query() + ->with('documentType') + ->where('student_id', $student->id) + ->get() + ->filter(fn ($document) => $this->isPhotoDocument($document)) + ->sortByDesc(function ($document) { + return sprintf( + '%d-%s', + $document->verification_status === 'valid' ? 1 : 0, + optional($document->created_at)?->format('YmdHis') ?? '00000000000000' + ); + }) + ->first(); + } + if (! $photoDocument?->file_path) { return null; } - return asset('storage/' . $photoDocument->file_path); + if (! Storage::disk('public')->exists($photoDocument->file_path)) { + return null; + } + + return asset('storage/' . ltrim($photoDocument->file_path, '/')); + } + + protected function isPhotoDocument(StudentDocument $document): bool + { + $code = strtoupper((string) ($document->documentType?->code ?? '')); + $name = strtolower((string) ($document->documentType?->name ?? '')); + $fileName = strtolower((string) ($document->file_name ?? '')); + $mimeType = strtolower((string) ($document->mime_type ?? '')); + + $looksLikePhoto = $code === 'PAS_FOTO' + || $code === 'PASFOTO' + || str_contains($name, 'pas foto') + || str_contains($name, 'pasfoto') + || str_contains($fileName, 'pas foto') + || str_contains($fileName, 'pasfoto'); + + if ($looksLikePhoto) { + return true; + } + + return str_starts_with($mimeType, 'image/') + && (str_contains($name, 'foto') || str_contains($fileName, 'foto')); } protected function buildRequiredDocumentSummary(Student $student): array diff --git a/app/Http/Controllers/StudentSelfServiceController.php b/app/Http/Controllers/StudentSelfServiceController.php index 6e1631b..b90e1d7 100644 --- a/app/Http/Controllers/StudentSelfServiceController.php +++ b/app/Http/Controllers/StudentSelfServiceController.php @@ -5,8 +5,11 @@ namespace App\Http\Controllers; use App\Models\AcademicYear; use App\Models\SchoolClass; use App\Models\Student; +use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Support\Str; use Illuminate\View\View; +use RealRashid\SweetAlert\Facades\Alert; class StudentSelfServiceController extends Controller { @@ -34,6 +37,11 @@ class StudentSelfServiceController extends Controller ->withQueryString(); $summaryBaseQuery = Student::query(); + $statusLabels = [ + 'not_started' => 'Belum Mulai', + 'draft' => 'Draft', + 'submitted' => 'Sudah Dikirim', + ]; return view('student-self-service.index', [ 'students' => $students, @@ -45,17 +53,115 @@ class StudentSelfServiceController extends Controller ], 'academicYears' => AcademicYear::orderBy('name')->pluck('name', 'id'), 'classes' => SchoolClass::orderBy('name')->pluck('name', 'id'), - 'statuses' => [ - 'not_started' => 'Belum Mulai', - 'draft' => 'Draft', - 'submitted' => 'Sudah Dikirim', - ], + 'statuses' => $statusLabels, 'summary' => [ 'total' => (clone $summaryBaseQuery)->count(), 'not_started' => (clone $summaryBaseQuery)->where('self_service_status', 'not_started')->count(), 'draft' => (clone $summaryBaseQuery)->where('self_service_status', 'draft')->count(), 'submitted' => (clone $summaryBaseQuery)->where('self_service_status', 'submitted')->count(), + 'with_code' => (clone $summaryBaseQuery)->whereNotNull('self_service_access_code')->count(), + 'without_code' => (clone $summaryBaseQuery)->whereNull('self_service_access_code')->count(), ], ]); } + + public function generateMissingCodes(): RedirectResponse + { + $students = Student::query()->whereNull('self_service_access_code')->get(); + + if ($students->isEmpty()) { + Alert::info('Tidak ada perubahan', 'Semua siswa sudah memiliki kode akses.'); + + return redirect()->route('student-self-service.index')->with('success', 'Semua siswa sudah memiliki kode akses.'); + } + + foreach ($students as $student) { + $student->forceFill([ + 'self_service_access_code' => $this->generateUniqueCode(), + 'self_service_code_generated_at' => now(), + ])->save(); + } + + Alert::success('Berhasil', $students->count() . ' kode akses berhasil digenerate.'); + + return redirect()->route('student-self-service.index')->with('success', $students->count() . ' kode akses berhasil digenerate.'); + } + + public function regenerateAllCodes(): RedirectResponse + { + $students = Student::query()->get(); + + foreach ($students as $student) { + $student->forceFill([ + 'self_service_access_code' => $this->generateUniqueCode(), + 'self_service_code_generated_at' => now(), + ])->save(); + } + + Alert::success('Berhasil', 'Semua kode akses siswa berhasil direset dan digenerate ulang.'); + + return redirect()->route('student-self-service.index')->with('success', 'Semua kode akses siswa berhasil direset dan digenerate ulang.'); + } + + public function regenerateStudentCode(Student $student): RedirectResponse + { + $student->forceFill([ + 'self_service_access_code' => $this->generateUniqueCode(), + 'self_service_code_generated_at' => now(), + ])->save(); + + Alert::success('Berhasil', 'Kode akses untuk ' . $student->full_name . ' berhasil digenerate ulang.'); + + return redirect()->route('student-self-service.index')->with('success', 'Kode akses untuk ' . $student->full_name . ' berhasil digenerate ulang.'); + } + + public function exportClassCodes(Request $request): View|RedirectResponse + { + $validated = $request->validate([ + 'class_id' => ['required', 'exists:classes,id'], + ], [ + 'class_id.required' => 'Pilih kelas terlebih dahulu sebelum export kode.', + ]); + + $class = SchoolClass::with(['academicYear', 'department'])->findOrFail($validated['class_id']); + $students = Student::query() + ->with(['classRoom']) + ->where('class_id', $class->id) + ->orderBy('full_name') + ->get(); + + if ($students->isEmpty()) { + return redirect()->route('student-self-service.index', ['class_id' => $class->id]) + ->withErrors(['class_id' => 'Kelas terpilih belum memiliki data siswa.']); + } + + $studentsWithoutCode = $students->filter(fn (Student $student) => blank($student->self_service_access_code)); + + foreach ($studentsWithoutCode as $student) { + $student->forceFill([ + 'self_service_access_code' => $this->generateUniqueCode(), + 'self_service_code_generated_at' => now(), + ])->save(); + } + + $students = Student::query() + ->where('class_id', $class->id) + ->orderBy('full_name') + ->get(); + + return view('student-self-service.export-class-codes', [ + 'class' => $class, + 'students' => $students, + 'generatedCount' => $studentsWithoutCode->count(), + ]); + } + + protected function generateUniqueCode(): string + { + do { + $code = strtoupper(Str::random(8)); + } while (Student::where('self_service_access_code', $code)->exists()); + + return $code; + } } diff --git a/resources/views/public/student-lookup.blade.php b/resources/views/public/student-lookup.blade.php index 0a31f44..3dd3bb3 100644 --- a/resources/views/public/student-lookup.blade.php +++ b/resources/views/public/student-lookup.blade.php @@ -142,42 +142,33 @@
Pencarian cocok berdasarkan {{ strtolower($matchedBy ?? 'data siswa') }} dan menampilkan ringkasan informasi siswa yang tersedia untuk publik.
-+ Pencarian cocok berdasarkan {{ strtolower($matchedBy ?? 'data siswa') }} dan menampilkan ringkasan informasi siswa yang tersedia untuk publik. +
+Digunakan untuk proses pelengkapan data siswa secara mandiri melalui portal sekolah.
+| No | +Nama Siswa | +NIS | +NISN | +Kode Akses | +Tanda Terima | +
|---|---|---|---|---|---|
| {{ $index + 1 }} | +{{ $student->full_name }} | +{{ $student->student_number ?: '-' }} | +{{ $student->nisn ?: '-' }} | +{{ $student->self_service_access_code ?: '-' }} | ++ |
|
+
+ Mengetahui,
+ + Admin Kesiswaan + + (........................................) + |
+
+
+ Diterima oleh,
+ + Wali Kelas / Petugas + + (........................................) + |
+
+ {{ $summary['without_code'] > 0 + ? $summary['without_code'] . ' siswa belum memiliki kode. Gunakan generate massal agar portal self servis bisa langsung dipakai.' + : 'Kode akses sudah lengkap. Anda bisa reset bila sekolah ingin mengganti seluruh batch kode.' }} +
+Bootstrap awal atau ganti seluruh kode akses.
+Siapkan lembar siap cetak / PDF per kelas.
+