get('q')); $students = collect(); $selectedStudent = null; $requiredDocumentSummary = null; $matchedBy = null; $selectedStudentId = $request->integer('student'); if ($query !== '') { $students = Student::query() ->with([ 'academicYear', 'department', 'classRoom', 'documents.documentType', 'achievements', ]) ->where(function ($builder) use ($query) { $builder->where('student_number', 'like', "%{$query}%") ->orWhere('nisn', 'like', "%{$query}%") ->orWhere('full_name', 'like', "%{$query}%"); }) ->orderByRaw('CASE WHEN student_number = ? THEN 0 WHEN nisn = ? THEN 1 WHEN full_name = ? THEN 2 ELSE 3 END', [$query, $query, $query]) ->orderBy('full_name') ->limit(10) ->get(); $selectedStudent = $students->firstWhere('id', $selectedStudentId) ?? $students->first(); if ($selectedStudent) { $requiredDocumentSummary = $this->buildRequiredDocumentSummary($selectedStudent); $matchedBy = $this->detectMatchedBy($selectedStudent, $query); } $students->each(function (Student $student) { $student->public_photo_url = $this->resolvePhotoUrl($student); }); } return view('public.student-lookup', [ 'query' => $query, 'students' => $students, 'student' => $selectedStudent, 'requiredDocumentSummary' => $requiredDocumentSummary, 'matchedBy' => $matchedBy, 'selectedStudentId' => $selectedStudent?->id, ]); } protected function detectMatchedBy(Student $student, string $query): ?string { if ($student->student_number && strcasecmp($student->student_number, $query) === 0) { return 'NIS'; } if ($student->nisn && strcasecmp($student->nisn, $query) === 0) { return 'NISN'; } return 'Nama'; } 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'); }) ->sortByDesc('created_at') ->first(); if (! $photoDocument?->file_path) { return null; } return asset('storage/' . $photoDocument->file_path); } protected function buildRequiredDocumentSummary(Student $student): array { $requiredTypes = DocumentType::query() ->where('is_active', true) ->where('is_required', true) ->get(); if ($requiredTypes->isEmpty()) { return [ 'total' => 0, 'uploaded' => 0, 'verified' => 0, 'missing' => 0, 'unverified' => 0, 'percentage' => 100, 'verification_percentage' => 100, 'missing_items' => collect(), 'unverified_items' => collect(), ]; } $documents = $student->documents; $uploaded = 0; $verified = 0; $missingItems = collect(); $unverifiedItems = collect(); foreach ($requiredTypes as $type) { $matchingDocuments = $documents->where('document_type_id', $type->id); $latestDocument = $matchingDocuments->sortByDesc('created_at')->first(); if (! $latestDocument) { $missingItems->push($type->name); continue; } $uploaded++; if ($latestDocument->verification_status === 'valid') { $verified++; } else { $unverifiedItems->push($type->name); } } $total = $requiredTypes->count(); return [ 'total' => $total, 'uploaded' => $uploaded, 'verified' => $verified, 'missing' => max($total - $uploaded, 0), 'unverified' => max($total - $verified, 0), 'percentage' => $total > 0 ? (int) round(($uploaded / $total) * 100) : 100, 'verification_percentage' => $total > 0 ? (int) round(($verified / $total) * 100) : 100, 'missing_items' => $missingItems, 'unverified_items' => $unverifiedItems, ]; } }