feat: update student import and graduation workflows

This commit is contained in:
2026-04-30 00:04:52 +07:00
parent 189b076ab0
commit ac22f8b7c8
51 changed files with 4547 additions and 1287 deletions
@@ -16,7 +16,17 @@ class StudentSyncController extends Controller
abort_unless(collect($token->abilities ?? [])->contains('students'), 403, 'Token tidak memiliki akses ke data siswa.');
$classLevel = strtoupper(trim((string) $request->query('class_level', '')));
$items = Student::query()
->when(in_array($classLevel, ['X', 'XI', 'XII'], true), function ($query) use ($classLevel) {
$query->whereHas('classRoom', fn ($classQuery) => $classQuery->where('level', $classLevel));
})
->with([
'academicYear:id,name,start_year,end_year,is_active',
'department:id,code,name,description,is_active',
'classRoom:id,academic_year_id,department_id,name,level,is_active',
])
->orderBy('full_name')
->get([
'id',
@@ -24,16 +34,74 @@ class StudentSyncController extends Controller
'nisn',
'full_name',
'gender',
'place_of_birth',
'date_of_birth',
'nationality',
'address',
'postal_code',
'academic_year_id',
'department_id',
'class_id',
'entry_date',
'status',
'phone_number',
'email',
'photo_path',
'notes',
'updated_at',
]);
])
->map(function (Student $student) {
return [
'id' => $student->id,
'student_number' => $student->student_number,
'nisn' => $student->nisn,
'full_name' => $student->full_name,
'gender' => $student->gender,
'place_of_birth' => $student->place_of_birth,
'date_of_birth' => $student->date_of_birth?->toDateString(),
'nationality' => $student->nationality,
'address' => $student->address,
'postal_code' => $student->postal_code,
'academic_year_id' => $student->academic_year_id,
'academic_year_name' => $student->academicYear?->name,
'academic_year' => $student->academicYear ? [
'id' => $student->academicYear->id,
'name' => $student->academicYear->name,
'start_year' => $student->academicYear->start_year,
'end_year' => $student->academicYear->end_year,
'is_active' => (bool) $student->academicYear->is_active,
] : null,
'department_id' => $student->department_id,
'department_code' => $student->department?->code,
'department_name' => $student->department?->name,
'department' => $student->department ? [
'id' => $student->department->id,
'code' => $student->department->code,
'name' => $student->department->name,
'description' => $student->department->description,
'is_active' => (bool) $student->department->is_active,
] : null,
'class_id' => $student->class_id,
'class_name' => $student->classRoom?->name,
'class_level' => $student->classRoom?->level,
'classroom' => $student->classRoom ? [
'id' => $student->classRoom->id,
'name' => $student->classRoom->name,
'level' => $student->classRoom->level,
'academic_year_id' => $student->classRoom->academic_year_id,
'department_id' => $student->classRoom->department_id,
'is_active' => (bool) $student->classRoom->is_active,
] : null,
'entry_date' => $student->entry_date?->toDateString(),
'status' => $student->status,
'phone_number' => $student->phone_number,
'email' => $student->email,
'photo_path' => $student->photo_path,
'notes' => $student->notes,
'updated_at' => $student->updated_at?->toISOString(),
];
})
->values();
return response()->json([
'success' => true,
@@ -21,6 +21,7 @@ class GraduatedStudentController extends Controller
$academicYearId = $request->get('academic_year_id');
$departmentId = $request->get('department_id');
$classId = $request->get('class_id');
$documentStatus = (string) $request->get('document_status', 'all');
$sort = (string) $request->get('sort', 'full_name');
$direction = strtolower((string) $request->get('direction', 'asc')) === 'desc' ? 'desc' : 'asc';
@@ -38,6 +39,14 @@ class GraduatedStudentController extends Controller
$sort = 'full_name';
}
$documentFields = [
'skl_file_path',
'ijazah_file_path',
'transcript_file_path',
'ukk_certificate_file_path',
'lsp_certificate_file_path',
];
$graduations = StudentGraduation::query()
->with(['academicYear', 'department', 'classRoom'])
->when($search !== '', function ($query) use ($search) {
@@ -50,17 +59,46 @@ class GraduatedStudentController extends Controller
->when($academicYearId, fn ($query) => $query->where('academic_year_id', $academicYearId))
->when($departmentId, fn ($query) => $query->where('department_id', $departmentId))
->when($classId, fn ($query) => $query->where('class_id', $classId))
->when($documentStatus === 'incomplete', function ($query) use ($documentFields) {
foreach ($documentFields as $field) {
$query->orWhereNull($field);
}
})
->when($documentStatus === 'complete', function ($query) use ($documentFields) {
foreach ($documentFields as $field) {
$query->whereNotNull($field);
}
})
->orderBy($allowedSorts[$sort], $direction)
->orderBy('id', $direction)
->paginate(10)
->withQueryString();
$totalGraduations = StudentGraduation::count();
$summary = [
'total' => $graduations->total(),
'total' => $totalGraduations,
'filtered' => $graduations->total(),
'with_skl' => StudentGraduation::query()->whereNotNull('skl_file_path')->count(),
'with_ijazah' => StudentGraduation::query()->whereNotNull('ijazah_file_path')->count(),
'with_transcript' => StudentGraduation::query()->whereNotNull('transcript_file_path')->count(),
'with_ukk_certificate' => StudentGraduation::query()->whereNotNull('ukk_certificate_file_path')->count(),
'with_lsp_certificate' => StudentGraduation::query()->whereNotNull('lsp_certificate_file_path')->count(),
'this_year' => StudentGraduation::query()->whereYear('graduation_date', now()->year)->count(),
'latest_graduation_date' => StudentGraduation::query()->max('graduation_date'),
];
$graduations->getCollection()->transform(function (StudentGraduation $graduation) use ($documentFields) {
$availableDocuments = collect($documentFields)->filter(fn ($field) => filled($graduation->{$field}))->count();
$graduation->documents_total = count($documentFields);
$graduation->documents_available = $availableDocuments;
$graduation->documents_missing = max($graduation->documents_total - $availableDocuments, 0);
$graduation->documents_progress_percentage = $graduation->documents_total > 0
? (int) round(($availableDocuments / $graduation->documents_total) * 100)
: 100;
return $graduation;
});
return view('graduated-students.index', [
'graduations' => $graduations,
'search' => $search,
@@ -68,10 +106,16 @@ class GraduatedStudentController extends Controller
'academic_year_id' => $academicYearId,
'department_id' => $departmentId,
'class_id' => $classId,
'document_status' => $documentStatus,
],
'academicYears' => AcademicYear::orderBy('name')->pluck('name', 'id'),
'departments' => Department::orderBy('name')->pluck('name', 'id'),
'classes' => SchoolClass::orderBy('name')->pluck('name', 'id'),
'documentStatusOptions' => [
'all' => 'Semua Dokumen',
'complete' => 'Dokumen Lengkap',
'incomplete' => 'Dokumen Belum Lengkap',
],
'sort' => $sort,
'direction' => $direction,
'summary' => $summary,
@@ -94,11 +138,20 @@ class GraduatedStudentController extends Controller
'graduation_note' => ['nullable', 'string'],
'skl_file' => ['nullable', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:4096'],
'ijazah_file' => ['nullable', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:4096'],
'transcript_file' => ['nullable', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:4096'],
'ukk_certificate_file' => ['nullable', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:4096'],
'lsp_certificate_file' => ['nullable', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:4096'],
], [
'skl_file.mimes' => 'File SKL harus berupa PDF, JPG, JPEG, atau PNG.',
'skl_file.max' => 'Ukuran file SKL maksimal 4 MB.',
'ijazah_file.mimes' => 'File ijazah harus berupa PDF, JPG, JPEG, atau PNG.',
'ijazah_file.max' => 'Ukuran file ijazah maksimal 4 MB.',
'transcript_file.mimes' => 'File transkrip nilai harus berupa PDF, JPG, JPEG, atau PNG.',
'transcript_file.max' => 'Ukuran file transkrip nilai maksimal 4 MB.',
'ukk_certificate_file.mimes' => 'File sertifikat UKK harus berupa PDF, JPG, JPEG, atau PNG.',
'ukk_certificate_file.max' => 'Ukuran file sertifikat UKK maksimal 4 MB.',
'lsp_certificate_file.mimes' => 'File sertifikat LSP harus berupa PDF, JPG, JPEG, atau PNG.',
'lsp_certificate_file.max' => 'Ukuran file sertifikat LSP maksimal 4 MB.',
]);
$graduatedStudent->graduation_date = $validated['graduation_date'] ?? $graduatedStudent->graduation_date;
@@ -132,6 +185,48 @@ class GraduatedStudentController extends Controller
$graduatedStudent->ijazah_file_size = $file->getSize();
}
if ($request->hasFile('transcript_file')) {
if ($graduatedStudent->transcript_file_path && Storage::disk('public')->exists($graduatedStudent->transcript_file_path)) {
Storage::disk('public')->delete($graduatedStudent->transcript_file_path);
}
$file = $request->file('transcript_file');
$path = $file->store("student-graduation-documents/{$graduatedStudent->student_id}/transcript", 'public');
$graduatedStudent->transcript_file_name = $file->getClientOriginalName();
$graduatedStudent->transcript_file_path = $path;
$graduatedStudent->transcript_mime_type = $file->getMimeType();
$graduatedStudent->transcript_file_size = $file->getSize();
}
if ($request->hasFile('ukk_certificate_file')) {
if ($graduatedStudent->ukk_certificate_file_path && Storage::disk('public')->exists($graduatedStudent->ukk_certificate_file_path)) {
Storage::disk('public')->delete($graduatedStudent->ukk_certificate_file_path);
}
$file = $request->file('ukk_certificate_file');
$path = $file->store("student-graduation-documents/{$graduatedStudent->student_id}/ukk-certificate", 'public');
$graduatedStudent->ukk_certificate_file_name = $file->getClientOriginalName();
$graduatedStudent->ukk_certificate_file_path = $path;
$graduatedStudent->ukk_certificate_mime_type = $file->getMimeType();
$graduatedStudent->ukk_certificate_file_size = $file->getSize();
}
if ($request->hasFile('lsp_certificate_file')) {
if ($graduatedStudent->lsp_certificate_file_path && Storage::disk('public')->exists($graduatedStudent->lsp_certificate_file_path)) {
Storage::disk('public')->delete($graduatedStudent->lsp_certificate_file_path);
}
$file = $request->file('lsp_certificate_file');
$path = $file->store("student-graduation-documents/{$graduatedStudent->student_id}/lsp-certificate", 'public');
$graduatedStudent->lsp_certificate_file_name = $file->getClientOriginalName();
$graduatedStudent->lsp_certificate_file_path = $path;
$graduatedStudent->lsp_certificate_mime_type = $file->getMimeType();
$graduatedStudent->lsp_certificate_file_size = $file->getSize();
}
$graduatedStudent->save();
Alert::success('Berhasil', 'Arsip siswa lulus berhasil diperbarui.');
@@ -141,11 +236,19 @@ class GraduatedStudentController extends Controller
public function previewDocument(StudentGraduation $graduatedStudent, string $document): View
{
abort_unless(in_array($document, ['skl', 'ijazah'], true), 404);
abort_unless(in_array($document, ['skl', 'ijazah', 'transcript', 'ukk-certificate', 'lsp-certificate'], true), 404);
$filePathField = $document === 'skl' ? 'skl_file_path' : 'ijazah_file_path';
$fileNameField = $document === 'skl' ? 'skl_file_name' : 'ijazah_file_name';
$mimeTypeField = $document === 'skl' ? 'skl_mime_type' : 'ijazah_mime_type';
$documentConfig = [
'skl' => ['path' => 'skl_file_path', 'name' => 'skl_file_name', 'mime' => 'skl_mime_type', 'label' => 'SKL'],
'ijazah' => ['path' => 'ijazah_file_path', 'name' => 'ijazah_file_name', 'mime' => 'ijazah_mime_type', 'label' => 'Ijazah'],
'transcript' => ['path' => 'transcript_file_path', 'name' => 'transcript_file_name', 'mime' => 'transcript_mime_type', 'label' => 'Transkrip Nilai'],
'ukk-certificate' => ['path' => 'ukk_certificate_file_path', 'name' => 'ukk_certificate_file_name', 'mime' => 'ukk_certificate_mime_type', 'label' => 'Sertifikat UKK'],
'lsp-certificate' => ['path' => 'lsp_certificate_file_path', 'name' => 'lsp_certificate_file_name', 'mime' => 'lsp_certificate_mime_type', 'label' => 'Sertifikat LSP'],
][$document];
$filePathField = $documentConfig['path'];
$fileNameField = $documentConfig['name'];
$mimeTypeField = $documentConfig['mime'];
abort_unless($graduatedStudent->{$filePathField} && Storage::disk('public')->exists($graduatedStudent->{$filePathField}), 404);
@@ -157,7 +260,7 @@ class GraduatedStudentController extends Controller
return view('graduated-students.preview-document', [
'graduation' => $graduatedStudent,
'documentType' => $document,
'documentLabel' => $document === 'skl' ? 'SKL' : 'Ijazah',
'documentLabel' => $documentConfig['label'],
'fileName' => $graduatedStudent->{$fileNameField},
'fileUrl' => $fileUrl,
'mimeType' => $mimeType,
@@ -20,6 +20,9 @@ class GraduationBulkUploadController extends Controller
'documentTypes' => [
'skl' => 'SKL',
'ijazah' => 'Ijazah',
'transcript' => 'Transkrip Nilai',
'ukk-certificate' => 'Sertifikat UKK',
'lsp-certificate' => 'Sertifikat LSP',
],
'matchByOptions' => [
'student_number' => 'NIS',
@@ -31,15 +34,15 @@ class GraduationBulkUploadController extends Controller
public function preview(Request $request): RedirectResponse
{
$validated = $request->validate([
'document_type' => ['required', 'in:skl,ijazah'],
'document_type' => ['required', 'in:skl,ijazah,transcript,ukk-certificate,lsp-certificate'],
'match_by' => ['required', 'in:student_number,nisn'],
'documents' => ['required', 'array', 'min:1', 'max:10'],
'documents.*' => ['required', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:2048'],
'documents.*' => ['required', 'file', 'mimes:pdf,jpg,jpeg,png', 'max:4096'],
], [
'documents.required' => 'Pilih minimal satu file dokumen untuk dipreview.',
'documents.max' => 'Maksimal 10 file per batch upload.',
'documents.*.mimes' => 'Dokumen hanya boleh berupa PDF, JPG, JPEG, atau PNG.',
'documents.*.max' => 'Ukuran tiap dokumen maksimal 2 MB.',
'documents.*.max' => 'Ukuran tiap dokumen maksimal 4 MB.',
]);
$graduations = StudentGraduation::query()->get()->keyBy($validated['match_by']);
@@ -59,7 +62,13 @@ class GraduationBulkUploadController extends Controller
$baseName = pathinfo($originalName, PATHINFO_FILENAME);
$normalizedKey = trim($baseName);
$graduation = $graduations->get($normalizedKey);
$existingPathField = $validated['document_type'] === 'skl' ? 'skl_file_path' : 'ijazah_file_path';
$existingPathField = [
'skl' => 'skl_file_path',
'ijazah' => 'ijazah_file_path',
'transcript' => 'transcript_file_path',
'ukk-certificate' => 'ukk_certificate_file_path',
'lsp-certificate' => 'lsp_certificate_file_path',
][$validated['document_type']];
$storedTempPath = $file->store('temp/graduation-bulk-upload', 'public');
@@ -93,7 +102,13 @@ class GraduationBulkUploadController extends Controller
$preview = [
'token' => (string) Str::uuid(),
'document_type' => $validated['document_type'],
'document_label' => $validated['document_type'] === 'skl' ? 'SKL' : 'Ijazah',
'document_label' => [
'skl' => 'SKL',
'ijazah' => 'Ijazah',
'transcript' => 'Transkrip Nilai',
'ukk-certificate' => 'Sertifikat UKK',
'lsp-certificate' => 'Sertifikat LSP',
][$validated['document_type']],
'match_by' => $validated['match_by'],
'match_by_label' => $validated['match_by'] === 'student_number' ? 'NIS' : 'NISN',
'rows' => $rows,
@@ -120,10 +135,18 @@ class GraduationBulkUploadController extends Controller
}
$documentType = $preview['document_type'];
$fileNameField = $documentType === 'skl' ? 'skl_file_name' : 'ijazah_file_name';
$filePathField = $documentType === 'skl' ? 'skl_file_path' : 'ijazah_file_path';
$mimeTypeField = $documentType === 'skl' ? 'skl_mime_type' : 'ijazah_mime_type';
$fileSizeField = $documentType === 'skl' ? 'skl_file_size' : 'ijazah_file_size';
$documentConfig = [
'skl' => ['name' => 'skl_file_name', 'path' => 'skl_file_path', 'mime' => 'skl_mime_type', 'size' => 'skl_file_size'],
'ijazah' => ['name' => 'ijazah_file_name', 'path' => 'ijazah_file_path', 'mime' => 'ijazah_mime_type', 'size' => 'ijazah_file_size'],
'transcript' => ['name' => 'transcript_file_name', 'path' => 'transcript_file_path', 'mime' => 'transcript_mime_type', 'size' => 'transcript_file_size'],
'ukk-certificate' => ['name' => 'ukk_certificate_file_name', 'path' => 'ukk_certificate_file_path', 'mime' => 'ukk_certificate_mime_type', 'size' => 'ukk_certificate_file_size'],
'lsp-certificate' => ['name' => 'lsp_certificate_file_name', 'path' => 'lsp_certificate_file_path', 'mime' => 'lsp_certificate_mime_type', 'size' => 'lsp_certificate_file_size'],
][$documentType];
$fileNameField = $documentConfig['name'];
$filePathField = $documentConfig['path'];
$mimeTypeField = $documentConfig['mime'];
$fileSizeField = $documentConfig['size'];
$processed = 0;
$skipped = 0;
@@ -144,12 +144,20 @@ class PublicStudentLookupController extends Controller
public function downloadGraduationDocument(Student $student, string $document): StreamedResponse
{
abort_unless($student->status === 'graduated', 404);
abort_unless(in_array($document, ['skl', 'ijazah'], true), 404);
abort_unless(in_array($document, ['skl', 'ijazah', 'transcript', 'ukk-certificate', 'lsp-certificate'], true), 404);
$graduation = StudentGraduation::query()->where('student_id', $student->id)->firstOrFail();
$filePathField = $document === 'skl' ? 'skl_file_path' : 'ijazah_file_path';
$fileNameField = $document === 'skl' ? 'skl_file_name' : 'ijazah_file_name';
$documentConfig = [
'skl' => ['path' => 'skl_file_path', 'name' => 'skl_file_name'],
'ijazah' => ['path' => 'ijazah_file_path', 'name' => 'ijazah_file_name'],
'transcript' => ['path' => 'transcript_file_path', 'name' => 'transcript_file_name'],
'ukk-certificate' => ['path' => 'ukk_certificate_file_path', 'name' => 'ukk_certificate_file_name'],
'lsp-certificate' => ['path' => 'lsp_certificate_file_path', 'name' => 'lsp_certificate_file_name'],
][$document];
$filePathField = $documentConfig['path'];
$fileNameField = $documentConfig['name'];
abort_unless($graduation->{$filePathField} && Storage::disk('public')->exists($graduation->{$filePathField}), 404);
@@ -125,6 +125,7 @@ class PublicStudentSelfServiceController extends Controller
'date_of_birth' => ['nullable', 'date'],
'religion' => ['nullable', 'string', 'max:50'],
'nationality' => ['nullable', 'string', 'max:50'],
'nik' => ['nullable', 'string', 'max:30', Rule::unique('students', 'nik')->ignore($student->id)],
'family_card_number' => ['nullable', 'string', 'max:30'],
'child_order' => ['nullable', 'integer', 'min:1', 'max:50'],
'number_of_siblings' => ['nullable', 'integer', 'min:0', 'max:50'],
@@ -171,6 +172,7 @@ class PublicStudentSelfServiceController extends Controller
'date_of_birth' => $data['date_of_birth'] ?? null,
'religion' => $data['religion'] ?? null,
'nationality' => $data['nationality'] ?? 'Indonesia',
'nik' => $data['nik'] ?? null,
'family_card_number' => $data['family_card_number'] ?? null,
'child_order' => $data['child_order'] ?? null,
'number_of_siblings' => $data['number_of_siblings'] ?? null,
@@ -18,8 +18,10 @@ class StudentAchievementController extends Controller
$search = trim((string) $request->get('search'));
$academicYearId = $request->get('academic_year_id');
$classId = $request->get('class_id');
$level = trim((string) $request->get('level'));
$category = trim((string) $request->get('category'));
$achievements = StudentAchievement::query()
$achievementsQuery = StudentAchievement::query()
->with(['student.academicYear', 'student.department', 'student.classRoom'])
->when($search !== '', function ($query) use ($search) {
$query->where(function ($subQuery) use ($search) {
@@ -35,25 +37,67 @@ class StudentAchievementController extends Controller
})
->when($academicYearId, fn ($query) => $query->whereHas('student', fn ($studentQuery) => $studentQuery->where('academic_year_id', $academicYearId)))
->when($classId, fn ($query) => $query->whereHas('student', fn ($studentQuery) => $studentQuery->where('class_id', $classId)))
->when($level !== '', fn ($query) => $query->where('level', $level))
->when($category !== '', fn ($query) => $query->where('category', $category));
$achievements = (clone $achievementsQuery)
->latest('achievement_year')
->latest('achievement_date')
->latest('id')
->paginate(15)
->withQueryString();
$levelCounts = StudentAchievement::query()
->selectRaw('COALESCE(level, ?) as level_label, COUNT(*) as total', ['Tanpa tingkat'])
->groupBy('level')
->orderByDesc('total')
->pluck('total', 'level_label');
$withCertificateCount = StudentAchievement::query()->whereNotNull('certificate_file')->count();
$withPhotoCount = StudentAchievement::query()->whereNotNull('activity_photo')->count();
$thisYearCount = StudentAchievement::query()->where('achievement_year', now()->year)->count();
$recentAchievements = StudentAchievement::query()
->with(['student:id,full_name'])
->latest('achievement_date')
->latest('achievement_year')
->latest('id')
->limit(3)
->get();
$categoryOptions = StudentAchievement::query()
->whereNotNull('category')
->where('category', '!=', '')
->distinct()
->orderBy('category')
->pluck('category', 'category');
return view('student-achievements.index', [
'achievements' => $achievements,
'search' => $search,
'filters' => [
'academic_year_id' => $academicYearId,
'class_id' => $classId,
'level' => $level,
'category' => $category,
],
'academicYears' => AcademicYear::orderBy('name')->pluck('name', 'id'),
'classes' => SchoolClass::orderBy('name')->pluck('name', 'id'),
'categoryOptions' => $categoryOptions,
'levelOptions' => $this->levelOptions(),
'summary' => [
'total' => StudentAchievement::count(),
'filtered' => $achievements->total(),
'students' => StudentAchievement::distinct('student_id')->count('student_id'),
'latest_year' => StudentAchievement::max('achievement_year'),
'top_level' => $levelCounts->keys()->first(),
'top_level_total' => $levelCounts->first(),
'with_certificate' => $withCertificateCount,
'without_certificate' => max(StudentAchievement::count() - $withCertificateCount, 0),
'with_photo' => $withPhotoCount,
'this_year' => $thisYearCount,
],
'recentAchievements' => $recentAchievements,
]);
}
+531 -19
View File
@@ -75,6 +75,7 @@ class StudentController extends Controller
$subQuery->where('students.full_name', 'like', "%{$search}%")
->orWhere('students.student_number', 'like', "%{$search}%")
->orWhere('students.nisn', 'like', "%{$search}%")
->orWhere('students.nik', 'like', "%{$search}%")
->orWhere('students.phone_number', 'like', "%{$search}%");
});
})
@@ -161,6 +162,7 @@ class StudentController extends Controller
$subQuery->where('students.full_name', 'like', "%{$search}%")
->orWhere('students.student_number', 'like', "%{$search}%")
->orWhere('students.nisn', 'like', "%{$search}%")
->orWhere('students.nik', 'like', "%{$search}%")
->orWhere('students.phone_number', 'like', "%{$search}%");
});
})
@@ -276,9 +278,14 @@ class StudentController extends Controller
public function edit(Student $student): View
{
return view('students.edit', array_merge($this->formData(), [
'student' => $student,
]));
$student->loadMissing('classRoom');
return view('students.edit', array_merge(
$this->formData($student),
[
'student' => $student,
]
));
}
public function update(Request $request, Student $student): RedirectResponse
@@ -395,14 +402,34 @@ class StudentController extends Controller
$payload = Arr::only($row['normalized'], [
'student_number',
'nisn',
'nik',
'full_name',
'gender',
'place_of_birth',
'date_of_birth',
'religion',
'nationality',
'family_card_number',
'child_order',
'number_of_siblings',
'phone_number',
'email',
'address',
'province_id',
'regency_id',
'district_id',
'village_id',
'province',
'city',
'district',
'village',
'postal_code',
'academic_year_id',
'department_id',
'class_id',
'entry_date',
'status',
'phone_number',
'notes',
]);
$lookup = null;
@@ -422,12 +449,14 @@ class StudentController extends Controller
$lookup->update($payload);
$this->syncAcademicHistory($lookup, $payload);
$this->syncImportedParents($lookup, $row['normalized']['parents'] ?? []);
$updated++;
continue;
}
$student = Student::create($payload);
$this->syncAcademicHistory($student, $payload);
$this->syncImportedParents($student, $row['normalized']['parents'] ?? []);
$inserted++;
}
});
@@ -441,7 +470,7 @@ class StudentController extends Controller
->with('success', "Import data siswa selesai. {$inserted} data baru ditambahkan, {$updated} data diperbarui.");
}
public function downloadImportTemplate(): Response
public function downloadImportTemplate()
{
$this->ensureAdmin();
@@ -452,32 +481,112 @@ class StudentController extends Controller
$headers = [
'NIS',
'NISN',
'NIK',
'Nama Lengkap',
'Jenis Kelamin (L/P)',
'Tempat Lahir',
'Tanggal Lahir (YYYY-MM-DD)',
'Agama',
'Kewarganegaraan',
'No. Kartu Keluarga',
'Anak Ke',
'Jumlah Saudara',
'Nomor HP',
'Email',
'Alamat',
'Provinsi',
'Kabupaten/Kota',
'Kecamatan',
'Kelurahan/Desa',
'Kode Pos',
'Tahun Ajaran',
'Program Keahlian',
'Kelas',
'Tanggal Masuk (YYYY-MM-DD)',
'Status',
'Nomor HP',
'Catatan',
'Nama Ayah',
'NIK Ayah',
'Tahun Lahir Ayah',
'Pendidikan Ayah',
'Pekerjaan Ayah',
'No. HP Ayah',
'Alamat Ayah',
'Status Hidup Ayah',
'Nama Ibu',
'NIK Ibu',
'Tahun Lahir Ibu',
'Pendidikan Ibu',
'Pekerjaan Ibu',
'No. HP Ibu',
'Alamat Ibu',
'Status Hidup Ibu',
'Nama Wali',
'NIK Wali',
'Tahun Lahir Wali',
'Pendidikan Wali',
'Pekerjaan Wali',
'No. HP Wali',
'Alamat Wali',
'Status Hidup Wali',
];
$sheet->fromArray($headers, null, 'A1');
$sheet->fromArray([
'2026-001',
'9988776655',
'3512345678901234',
'Contoh Nama Siswa',
'L',
'Malang',
'2010-01-15',
'Islam',
'Indonesia',
'3512345678901235',
'1',
'2',
'081234567890',
'contoh@siswa.local',
'Jl. Contoh No. 1',
'Jawa Timur',
'Kota Malang',
'Klojen',
'Kauman',
'65119',
AcademicYear::orderBy('name')->value('name') ?? '2026/2027',
Department::orderBy('name')->value('name') ?? 'DKV',
SchoolClass::orderBy('name')->value('name') ?? 'X DKV 1',
'2026-07-15',
'active',
'081234567890',
'Catatan opsional',
'Budi Santoso',
'3512345678901111',
'1980',
'SMA',
'Wiraswasta',
'081200000001',
'Jl. Ayah No. 1',
'Ya',
'Siti Aminah',
'3512345678902222',
'1982',
'SMA',
'Ibu Rumah Tangga',
'081200000002',
'Jl. Ibu No. 1',
'Ya',
'Ahmad Wali',
'3512345678903333',
'1975',
'S1',
'Pegawai Swasta',
'081200000003',
'Jl. Wali No. 1',
'Ya',
], null, 'A2');
foreach (range('A', 'J') as $column) {
$sheet->getColumnDimension($column)->setAutoSize(true);
for ($columnIndex = 1; $columnIndex <= count($headers); $columnIndex++) {
$sheet->getColumnDimensionByColumn($columnIndex)->setAutoSize(true);
}
$referenceSheet = $spreadsheet->createSheet();
@@ -502,14 +611,54 @@ class StudentController extends Controller
['Kolom', 'Keterangan'],
['NIS', 'Boleh kosong, tapi idealnya diisi untuk identitas lokal sekolah.'],
['NISN', 'Boleh kosong jika belum ada.'],
['NIK', 'Opsional, tetapi jika diisi harus unik.'],
['Nama Lengkap', 'Wajib diisi.'],
['Jenis Kelamin (L/P)', 'Isi L atau P.'],
['Tempat Lahir', 'Opsional.'],
['Tanggal Lahir (YYYY-MM-DD)', 'Format contoh: 2010-01-15.'],
['Agama', 'Opsional, contoh: Islam, Kristen, Katolik, Hindu, Buddha, Konghucu.'],
['Kewarganegaraan', 'Opsional, default aman: Indonesia.'],
['No. Kartu Keluarga', 'Opsional.'],
['Anak Ke', 'Opsional, angka bulat minimal 1.'],
['Jumlah Saudara', 'Opsional, angka bulat minimal 0.'],
['Nomor HP', 'Opsional.'],
['Email', 'Opsional, harus format email valid bila diisi.'],
['Alamat', 'Opsional.'],
['Provinsi', 'Opsional, isi nama persis seperti data master wilayah.'],
['Kabupaten/Kota', 'Opsional, isi nama persis seperti data master wilayah.'],
['Kecamatan', 'Opsional, isi nama persis seperti data master wilayah.'],
['Kelurahan/Desa', 'Opsional, isi nama persis seperti data master wilayah.'],
['Kode Pos', 'Opsional.'],
['Tahun Ajaran', 'Isi dengan nama tahun ajaran persis seperti sheet Referensi.'],
['Program Keahlian', 'Isi dengan nama jurusan persis seperti sheet Referensi.'],
['Kelas', 'Isi dengan nama kelas persis seperti sheet Referensi.'],
['Tanggal Masuk (YYYY-MM-DD)', 'Format contoh: 2026-07-15.'],
['Status', 'Gunakan: active, inactive, transferred, graduated, dropped.'],
['Nomor HP', 'Opsional.'],
['Catatan', 'Opsional.'],
['Nama Ayah', 'Opsional.'],
['NIK Ayah', 'Opsional.'],
['Tahun Lahir Ayah', 'Opsional, angka 4 digit.'],
['Pendidikan Ayah', 'Opsional.'],
['Pekerjaan Ayah', 'Opsional.'],
['No. HP Ayah', 'Opsional.'],
['Alamat Ayah', 'Opsional.'],
['Status Hidup Ayah', 'Opsional, isi Ya/Tidak atau Hidup/Meninggal.'],
['Nama Ibu', 'Opsional.'],
['NIK Ibu', 'Opsional.'],
['Tahun Lahir Ibu', 'Opsional, angka 4 digit.'],
['Pendidikan Ibu', 'Opsional.'],
['Pekerjaan Ibu', 'Opsional.'],
['No. HP Ibu', 'Opsional.'],
['Alamat Ibu', 'Opsional.'],
['Status Hidup Ibu', 'Opsional, isi Ya/Tidak atau Hidup/Meninggal.'],
['Nama Wali', 'Opsional.'],
['NIK Wali', 'Opsional.'],
['Tahun Lahir Wali', 'Opsional, angka 4 digit.'],
['Pendidikan Wali', 'Opsional.'],
['Pekerjaan Wali', 'Opsional.'],
['No. HP Wali', 'Opsional.'],
['Alamat Wali', 'Opsional.'],
['Status Hidup Wali', 'Opsional, isi Ya/Tidak atau Hidup/Meninggal.'],
], null, 'A1');
foreach (range('A', 'B') as $column) {
$guideSheet->getColumnDimension($column)->setAutoSize(true);
@@ -526,11 +675,26 @@ class StudentController extends Controller
]);
}
public function downloadImportExample()
{
$this->ensureAdmin();
$path = storage_path('app/private/student-import-example.xlsx');
abort_unless(file_exists($path), 404, 'File contoh import siswa belum tersedia.');
return response()->file($path, [
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'Content-Disposition' => 'attachment; filename="contoh-import-siswa.xlsx"',
]);
}
protected function validateStudent(Request $request, ?Student $student = null): array
{
$data = $request->validate([
'student_number' => ['nullable', 'string', 'max:30', Rule::unique('students', 'student_number')->ignore($student?->id)],
'nisn' => ['nullable', 'string', 'max:30', Rule::unique('students', 'nisn')->ignore($student?->id)],
'nik' => ['nullable', 'string', 'max:30', Rule::unique('students', 'nik')->ignore($student?->id)],
'full_name' => ['required', 'string', 'max:150'],
'gender' => ['required', Rule::in(['L', 'P'])],
'place_of_birth' => ['nullable', 'string', 'max:100'],
@@ -598,16 +762,22 @@ class StudentController extends Controller
return $data;
}
protected function formData(): array
protected function formData(?Student $student = null): array
{
$classOptionsQuery = SchoolClass::query()
->when(
$student?->class_id,
fn ($query) => $query->where('is_active', true)->orWhere('id', $student->class_id),
fn ($query) => $query->where('is_active', true)
)
->orderBy('name');
return [
'academicYears' => AcademicYear::where('is_active', true)->orderBy('name')->pluck('name', 'id'),
'departments' => Department::where('is_active', true)->orderBy('name')->pluck('name', 'id'),
'classes' => SchoolClass::where('is_active', true)->orderBy('name')->pluck('name', 'id'),
'provinces' => Province::orderBy('name')->pluck('name', 'id'),
'classOptions' => SchoolClass::query()
->where('is_active', true)
->orderBy('name')
'classOptions' => $classOptionsQuery
->get(['id', 'name', 'department_id', 'academic_year_id'])
->map(fn ($classRoom) => [
'id' => $classRoom->id,
@@ -825,6 +995,48 @@ class StudentController extends Controller
$graduationRecord->ijazah_file_size = $file->getSize();
}
if (request()->hasFile('transcript_file')) {
if ($graduationRecord->transcript_file_path && Storage::disk('public')->exists($graduationRecord->transcript_file_path)) {
Storage::disk('public')->delete($graduationRecord->transcript_file_path);
}
$file = request()->file('transcript_file');
$path = $file->store("student-graduation-documents/{$student->id}/transcript", 'public');
$graduationRecord->transcript_file_name = $file->getClientOriginalName();
$graduationRecord->transcript_file_path = $path;
$graduationRecord->transcript_mime_type = $file->getMimeType();
$graduationRecord->transcript_file_size = $file->getSize();
}
if (request()->hasFile('ukk_certificate_file')) {
if ($graduationRecord->ukk_certificate_file_path && Storage::disk('public')->exists($graduationRecord->ukk_certificate_file_path)) {
Storage::disk('public')->delete($graduationRecord->ukk_certificate_file_path);
}
$file = request()->file('ukk_certificate_file');
$path = $file->store("student-graduation-documents/{$student->id}/ukk-certificate", 'public');
$graduationRecord->ukk_certificate_file_name = $file->getClientOriginalName();
$graduationRecord->ukk_certificate_file_path = $path;
$graduationRecord->ukk_certificate_mime_type = $file->getMimeType();
$graduationRecord->ukk_certificate_file_size = $file->getSize();
}
if (request()->hasFile('lsp_certificate_file')) {
if ($graduationRecord->lsp_certificate_file_path && Storage::disk('public')->exists($graduationRecord->lsp_certificate_file_path)) {
Storage::disk('public')->delete($graduationRecord->lsp_certificate_file_path);
}
$file = request()->file('lsp_certificate_file');
$path = $file->store("student-graduation-documents/{$student->id}/lsp-certificate", 'public');
$graduationRecord->lsp_certificate_file_name = $file->getClientOriginalName();
$graduationRecord->lsp_certificate_file_path = $path;
$graduationRecord->lsp_certificate_mime_type = $file->getMimeType();
$graduationRecord->lsp_certificate_file_size = $file->getSize();
}
$graduationRecord->save();
return;
@@ -838,14 +1050,54 @@ class StudentController extends Controller
return [
'student_number' => ['nullable', 'string', 'max:30'],
'nisn' => ['nullable', 'string', 'max:30'],
'nik' => ['nullable', 'string', 'max:30'],
'full_name' => ['required', 'string', 'max:150'],
'gender' => ['required', Rule::in(['L', 'P'])],
'place_of_birth' => ['nullable', 'string', 'max:100'],
'date_of_birth' => ['nullable', 'date'],
'religion' => ['nullable', 'string', 'max:50'],
'nationality' => ['nullable', 'string', 'max:50'],
'family_card_number' => ['nullable', 'string', 'max:30'],
'child_order' => ['nullable', 'integer', 'min:1'],
'number_of_siblings' => ['nullable', 'integer', 'min:0'],
'phone_number' => ['nullable', 'string', 'max:30'],
'email' => ['nullable', 'email', 'max:100'],
'address' => ['nullable', 'string'],
'province_id' => ['nullable', 'string', 'exists:provinces,id'],
'regency_id' => ['nullable', 'string', 'exists:regencies,id'],
'district_id' => ['nullable', 'string', 'exists:districts,id'],
'village_id' => ['nullable', 'string', 'exists:villages,id'],
'postal_code' => ['nullable', 'string', 'max:10'],
'academic_year_id' => ['required', 'integer', Rule::exists('academic_years', 'id')->where(fn ($query) => $query->where('is_active', true))],
'department_id' => ['required', 'integer', Rule::exists('departments', 'id')->where(fn ($query) => $query->where('is_active', true))],
'class_id' => ['required', 'integer', Rule::exists('classes', 'id')->where(fn ($query) => $query->where('is_active', true))],
'entry_date' => ['nullable', 'date'],
'status' => ['required', Rule::in(array_keys($this->statusOptions()))],
'phone_number' => ['nullable', 'string', 'max:30'],
'notes' => ['nullable', 'string'],
'parents.father.name' => ['nullable', 'string', 'max:150'],
'parents.father.nik' => ['nullable', 'string', 'max:30'],
'parents.father.birth_year' => ['nullable', 'integer', 'min:1900', 'max:' . now()->year],
'parents.father.education' => ['nullable', 'string', 'max:100'],
'parents.father.occupation' => ['nullable', 'string', 'max:100'],
'parents.father.phone_number' => ['nullable', 'string', 'max:30'],
'parents.father.address' => ['nullable', 'string'],
'parents.father.is_alive' => ['nullable', 'boolean'],
'parents.mother.name' => ['nullable', 'string', 'max:150'],
'parents.mother.nik' => ['nullable', 'string', 'max:30'],
'parents.mother.birth_year' => ['nullable', 'integer', 'min:1900', 'max:' . now()->year],
'parents.mother.education' => ['nullable', 'string', 'max:100'],
'parents.mother.occupation' => ['nullable', 'string', 'max:100'],
'parents.mother.phone_number' => ['nullable', 'string', 'max:30'],
'parents.mother.address' => ['nullable', 'string'],
'parents.mother.is_alive' => ['nullable', 'boolean'],
'parents.guardian.name' => ['nullable', 'string', 'max:150'],
'parents.guardian.nik' => ['nullable', 'string', 'max:30'],
'parents.guardian.birth_year' => ['nullable', 'integer', 'min:1900', 'max:' . now()->year],
'parents.guardian.education' => ['nullable', 'string', 'max:100'],
'parents.guardian.occupation' => ['nullable', 'string', 'max:100'],
'parents.guardian.phone_number' => ['nullable', 'string', 'max:30'],
'parents.guardian.address' => ['nullable', 'string'],
'parents.guardian.is_alive' => ['nullable', 'boolean'],
];
}
@@ -854,14 +1106,54 @@ class StudentController extends Controller
return [
'student_number' => 'NIS',
'nisn' => 'NISN',
'nik' => 'NIK',
'full_name' => 'nama lengkap',
'gender' => 'jenis kelamin',
'place_of_birth' => 'tempat lahir',
'date_of_birth' => 'tanggal lahir',
'religion' => 'agama',
'nationality' => 'kewarganegaraan',
'family_card_number' => 'nomor kartu keluarga',
'child_order' => 'anak ke',
'number_of_siblings' => 'jumlah saudara',
'phone_number' => 'nomor HP',
'email' => 'email',
'address' => 'alamat',
'province_id' => 'provinsi',
'regency_id' => 'kabupaten/kota',
'district_id' => 'kecamatan',
'village_id' => 'kelurahan/desa',
'postal_code' => 'kode pos',
'academic_year_id' => 'tahun ajaran',
'department_id' => 'program keahlian',
'class_id' => 'kelas',
'entry_date' => 'tanggal masuk',
'status' => 'status',
'phone_number' => 'nomor HP',
'notes' => 'catatan',
'parents.father.name' => 'nama ayah',
'parents.father.nik' => 'NIK ayah',
'parents.father.birth_year' => 'tahun lahir ayah',
'parents.father.education' => 'pendidikan ayah',
'parents.father.occupation' => 'pekerjaan ayah',
'parents.father.phone_number' => 'nomor HP ayah',
'parents.father.address' => 'alamat ayah',
'parents.father.is_alive' => 'status hidup ayah',
'parents.mother.name' => 'nama ibu',
'parents.mother.nik' => 'NIK ibu',
'parents.mother.birth_year' => 'tahun lahir ibu',
'parents.mother.education' => 'pendidikan ibu',
'parents.mother.occupation' => 'pekerjaan ibu',
'parents.mother.phone_number' => 'nomor HP ibu',
'parents.mother.address' => 'alamat ibu',
'parents.mother.is_alive' => 'status hidup ibu',
'parents.guardian.name' => 'nama wali',
'parents.guardian.nik' => 'NIK wali',
'parents.guardian.birth_year' => 'tahun lahir wali',
'parents.guardian.education' => 'pendidikan wali',
'parents.guardian.occupation' => 'pekerjaan wali',
'parents.guardian.phone_number' => 'nomor HP wali',
'parents.guardian.address' => 'alamat wali',
'parents.guardian.is_alive' => 'status hidup wali',
];
}
@@ -968,30 +1260,75 @@ class StudentController extends Controller
$studentNumber = $this->nullIfBlank($normalized['student_number'] ?? ($normalized['nis'] ?? null));
$nisn = $this->nullIfBlank($normalized['nisn'] ?? null);
$nik = $this->nullIfBlank($normalized['nik'] ?? null);
$fullName = $this->nullIfBlank($normalized['full_name'] ?? ($normalized['nama_lengkap'] ?? null));
$gender = strtoupper((string) $this->nullIfBlank($normalized['gender'] ?? ($normalized['jenis_kelamin_l_p'] ?? null)));
$status = strtolower((string) ($this->nullIfBlank($normalized['status'] ?? null) ?? 'active'));
$entryDate = $this->nullIfBlank($normalized['entry_date'] ?? ($normalized['tanggal_masuk_yyyy_mm_dd'] ?? null));
$placeOfBirth = $this->nullIfBlank($normalized['place_of_birth'] ?? ($normalized['tempat_lahir'] ?? null));
$dateOfBirth = $this->normalizeImportDate($normalized['date_of_birth'] ?? ($normalized['tanggal_lahir_yyyy_mm_dd'] ?? null));
$religion = $this->nullIfBlank($normalized['religion'] ?? ($normalized['agama'] ?? null));
$nationality = $this->nullIfBlank($normalized['nationality'] ?? ($normalized['kewarganegaraan'] ?? null));
$familyCardNumber = $this->nullIfBlank($normalized['family_card_number'] ?? ($normalized['no_kartu_keluarga'] ?? null));
$childOrder = $this->normalizeImportInteger($normalized['child_order'] ?? ($normalized['anak_ke'] ?? null));
$numberOfSiblings = $this->normalizeImportInteger($normalized['number_of_siblings'] ?? ($normalized['jumlah_saudara'] ?? null));
$phoneNumber = $this->nullIfBlank($normalized['phone_number'] ?? ($normalized['nomor_hp'] ?? null));
$email = $this->nullIfBlank($normalized['email'] ?? null);
$address = $this->nullIfBlank($normalized['address'] ?? ($normalized['alamat'] ?? null));
$postalCode = $this->nullIfBlank($normalized['postal_code'] ?? ($normalized['kode_pos'] ?? null));
$status = strtolower((string) ($this->nullIfBlank($normalized['status'] ?? null) ?? 'active'));
$entryDate = $this->normalizeImportDate($normalized['entry_date'] ?? ($normalized['tanggal_masuk_yyyy_mm_dd'] ?? null));
$notes = $this->nullIfBlank($normalized['notes'] ?? ($normalized['catatan'] ?? null));
$academicYearSource = $this->nullIfBlank($normalized['academic_year_id'] ?? ($normalized['tahun_ajaran'] ?? null));
$departmentSource = $this->nullIfBlank($normalized['department_id'] ?? ($normalized['program_keahlian'] ?? null));
$classSource = $this->nullIfBlank($normalized['class_id'] ?? ($normalized['kelas'] ?? null));
$provinceSource = $this->nullIfBlank($normalized['province_id'] ?? ($normalized['provinsi'] ?? null));
$regencySource = $this->nullIfBlank($normalized['regency_id'] ?? ($normalized['kabupaten_kota'] ?? null));
$districtSource = $this->nullIfBlank($normalized['district_id'] ?? ($normalized['kecamatan'] ?? null));
$villageSource = $this->nullIfBlank($normalized['village_id'] ?? ($normalized['kelurahan_desa'] ?? null));
$academicYearId = $this->resolveAcademicYearId($academicYearSource);
$departmentId = $this->resolveDepartmentId($departmentSource);
$classId = $this->resolveClassId($classSource, $academicYearId, $departmentId);
$provinceId = $this->resolveProvinceId($provinceSource);
$regencyId = $this->resolveRegencyId($regencySource, $provinceId);
$districtId = $this->resolveDistrictId($districtSource, $regencyId);
$villageId = $this->resolveVillageId($villageSource, $districtId);
return [
'student_number' => $studentNumber,
'nisn' => $nisn,
'nik' => $nik,
'full_name' => $fullName,
'gender' => $gender,
'place_of_birth' => $placeOfBirth,
'date_of_birth' => $dateOfBirth,
'religion' => $religion,
'nationality' => $nationality,
'family_card_number' => $familyCardNumber,
'child_order' => $childOrder,
'number_of_siblings' => $numberOfSiblings,
'phone_number' => $phoneNumber,
'email' => $email,
'address' => $address,
'province_id' => $provinceId,
'regency_id' => $regencyId,
'district_id' => $districtId,
'village_id' => $villageId,
'province' => $provinceId ? Province::where('id', $provinceId)->value('name') : null,
'city' => $regencyId ? Regency::where('id', $regencyId)->value('name') : null,
'district' => $districtId ? District::where('id', $districtId)->value('name') : null,
'village' => $villageId ? Village::where('id', $villageId)->value('name') : null,
'postal_code' => $postalCode,
'academic_year_id' => $academicYearId,
'department_id' => $departmentId,
'class_id' => $classId,
'entry_date' => $entryDate,
'status' => $status,
'phone_number' => $phoneNumber,
'notes' => $notes,
'parents' => [
'father' => $this->normalizeImportParentData($normalized, 'father', 'ayah'),
'mother' => $this->normalizeImportParentData($normalized, 'mother', 'ibu'),
'guardian' => $this->normalizeImportParentData($normalized, 'guardian', 'wali'),
],
];
}
@@ -1038,6 +1375,181 @@ class StudentController extends Controller
->value('id');
}
protected function resolveProvinceId(mixed $value): mixed
{
if ($value === null || $value === '') {
return null;
}
if (is_numeric($value)) {
return (string) $value;
}
return Province::where('name', trim((string) $value))->value('id');
}
protected function resolveRegencyId(mixed $value, mixed $provinceId = null): mixed
{
if ($value === null || $value === '') {
return null;
}
if (is_numeric($value)) {
return (string) $value;
}
return Regency::query()
->when($provinceId, fn ($query) => $query->where('province_id', $provinceId))
->where('name', trim((string) $value))
->value('id');
}
protected function resolveDistrictId(mixed $value, mixed $regencyId = null): mixed
{
if ($value === null || $value === '') {
return null;
}
if (is_numeric($value)) {
return (string) $value;
}
return District::query()
->when($regencyId, fn ($query) => $query->where('regency_id', $regencyId))
->where('name', trim((string) $value))
->value('id');
}
protected function resolveVillageId(mixed $value, mixed $districtId = null): mixed
{
if ($value === null || $value === '') {
return null;
}
if (is_numeric($value)) {
return (string) $value;
}
return Village::query()
->when($districtId, fn ($query) => $query->where('district_id', $districtId))
->where('name', trim((string) $value))
->value('id');
}
protected function normalizeImportDate(mixed $value): mixed
{
$value = $this->nullIfBlank($value);
if ($value === null) {
return null;
}
if (is_numeric($value)) {
try {
return \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject((float) $value)->format('Y-m-d');
} catch (\Throwable) {
return $value;
}
}
return $value;
}
protected function normalizeImportInteger(mixed $value): mixed
{
$value = $this->nullIfBlank($value);
if ($value === null) {
return null;
}
if (is_numeric($value)) {
return (int) $value;
}
return $value;
}
protected function normalizeImportBoolean(mixed $value): ?bool
{
$value = $this->nullIfBlank($value);
if ($value === null) {
return null;
}
if (is_bool($value)) {
return $value;
}
$normalized = strtolower(trim((string) $value));
return match ($normalized) {
'1', 'true', 'ya', 'y', 'yes', 'hidup', 'masih hidup' => true,
'0', 'false', 'tidak', 't', 'no', 'meninggal', 'wafat' => false,
default => null,
};
}
protected function normalizeImportParentData(array $normalized, string $relationKey, string $legacyPrefix): array
{
return [
'name' => $this->nullIfBlank($normalized["parent_{$relationKey}_name"] ?? ($normalized["nama_{$legacyPrefix}"] ?? null)),
'nik' => $this->nullIfBlank($normalized["parent_{$relationKey}_nik"] ?? ($normalized["nik_{$legacyPrefix}"] ?? null)),
'birth_year' => $this->normalizeImportInteger($normalized["parent_{$relationKey}_birth_year"] ?? ($normalized["tahun_lahir_{$legacyPrefix}"] ?? null)),
'education' => $this->nullIfBlank($normalized["parent_{$relationKey}_education"] ?? ($normalized["pendidikan_{$legacyPrefix}"] ?? null)),
'occupation' => $this->nullIfBlank($normalized["parent_{$relationKey}_occupation"] ?? ($normalized["pekerjaan_{$legacyPrefix}"] ?? null)),
'phone_number' => $this->nullIfBlank($normalized["parent_{$relationKey}_phone_number"] ?? ($normalized["no_hp_{$legacyPrefix}"] ?? null)),
'address' => $this->nullIfBlank($normalized["parent_{$relationKey}_address"] ?? ($normalized["alamat_{$legacyPrefix}"] ?? null)),
'is_alive' => $this->normalizeImportBoolean($normalized["parent_{$relationKey}_is_alive"] ?? ($normalized["status_hidup_{$legacyPrefix}"] ?? null)),
];
}
protected function syncImportedParents(Student $student, array $parents): void
{
foreach (['father', 'mother', 'guardian'] as $relationType) {
$parentData = $parents[$relationType] ?? [];
$hasMeaningfulValue = collect($parentData)
->except('is_alive')
->filter(fn ($value) => filled($value))
->isNotEmpty();
if (! $hasMeaningfulValue && ! array_key_exists('is_alive', $parentData)) {
continue;
}
$existingParent = $student->parents()->where('relation_type', $relationType)->first();
if (! $hasMeaningfulValue) {
if ($existingParent) {
$existingParent->delete();
}
continue;
}
$payload = [
'name' => $parentData['name'] ?? '-',
'nik' => $parentData['nik'] ?? null,
'birth_year' => $parentData['birth_year'] ?? null,
'education' => $parentData['education'] ?? null,
'occupation' => $parentData['occupation'] ?? null,
'phone_number' => $parentData['phone_number'] ?? null,
'address' => $parentData['address'] ?? null,
'is_alive' => $parentData['is_alive'] ?? false,
];
if ($existingParent) {
$existingParent->update($payload);
} else {
$student->parents()->create([
'relation_type' => $relationType,
...$payload,
]);
}
}
}
protected function parseImportFile(string $path, string $originalName): array
{
$extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
+24 -2
View File
@@ -21,7 +21,7 @@ class StudentExitController extends Controller
$departmentId = $request->get('department_id');
$classId = $request->get('class_id');
$studentExits = StudentExit::query()
$studentExitsQuery = StudentExit::query()
->with(['academicYear', 'department', 'classRoom'])
->when($search !== '', function ($query) use ($search) {
$query->where(function ($subQuery) use ($search) {
@@ -32,12 +32,25 @@ class StudentExitController extends Controller
})
->when($academicYearId, fn ($query) => $query->where('academic_year_id', $academicYearId))
->when($departmentId, fn ($query) => $query->where('department_id', $departmentId))
->when($classId, fn ($query) => $query->where('class_id', $classId))
->when($classId, fn ($query) => $query->where('class_id', $classId));
$studentExits = (clone $studentExitsQuery)
->latest('exit_date')
->latest('id')
->paginate(10)
->withQueryString();
$totalExits = StudentExit::count();
$withLetterCount = StudentExit::query()->whereNotNull('exit_letter_file_path')->count();
$withoutLetterCount = max($totalExits - $withLetterCount, 0);
$thisYearCount = StudentExit::query()->whereYear('exit_date', now()->year)->count();
$latestExitDate = StudentExit::query()->max('exit_date');
$recentExits = StudentExit::query()
->latest('exit_date')
->latest('id')
->limit(3)
->get(['id', 'full_name', 'exit_date', 'department_id', 'class_id']);
return view('student-exits.index', [
'studentExits' => $studentExits,
'search' => $search,
@@ -49,6 +62,15 @@ class StudentExitController extends Controller
'academicYears' => AcademicYear::orderBy('name')->pluck('name', 'id'),
'departments' => Department::orderBy('name')->pluck('name', 'id'),
'classes' => SchoolClass::orderBy('name')->pluck('name', 'id'),
'summary' => [
'total' => $totalExits,
'with_letter' => $withLetterCount,
'without_letter' => $withoutLetterCount,
'this_year' => $thisYearCount,
'latest_exit_date' => $latestExitDate,
'filtered' => $studentExits->total(),
],
'recentExits' => $recentExits,
]);
}
@@ -158,8 +158,12 @@ class StudentSelfServiceController extends Controller
protected function generateUniqueCode(): string
{
$characters = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
do {
$code = strtoupper(Str::random(8));
$code = collect(range(1, 4))
->map(fn () => $characters[random_int(0, strlen($characters) - 1)])
->implode('');
} while (Student::where('self_service_access_code', $code)->exists());
return $code;
@@ -21,7 +21,7 @@ class StudentTransferController extends Controller
$departmentId = $request->get('department_id');
$classId = $request->get('class_id');
$studentTransfers = StudentTransfer::query()
$studentTransfersQuery = StudentTransfer::query()
->with(['academicYear', 'department', 'classRoom'])
->when($search !== '', function ($query) use ($search) {
$query->where(function ($subQuery) use ($search) {
@@ -32,12 +32,25 @@ class StudentTransferController extends Controller
})
->when($academicYearId, fn ($query) => $query->where('academic_year_id', $academicYearId))
->when($departmentId, fn ($query) => $query->where('department_id', $departmentId))
->when($classId, fn ($query) => $query->where('class_id', $classId))
->when($classId, fn ($query) => $query->where('class_id', $classId));
$studentTransfers = (clone $studentTransfersQuery)
->latest('transfer_date')
->latest('id')
->paginate(10)
->withQueryString();
$totalTransfers = StudentTransfer::count();
$withLetterCount = StudentTransfer::query()->whereNotNull('transfer_letter_file_path')->count();
$withoutLetterCount = max($totalTransfers - $withLetterCount, 0);
$thisYearCount = StudentTransfer::query()->whereYear('transfer_date', now()->year)->count();
$latestTransferDate = StudentTransfer::query()->max('transfer_date');
$recentTransfers = StudentTransfer::query()
->latest('transfer_date')
->latest('id')
->limit(3)
->get(['id', 'full_name', 'transfer_date', 'department_id', 'class_id']);
return view('student-transfers.index', [
'studentTransfers' => $studentTransfers,
'search' => $search,
@@ -49,6 +62,15 @@ class StudentTransferController extends Controller
'academicYears' => AcademicYear::orderBy('name')->pluck('name', 'id'),
'departments' => Department::orderBy('name')->pluck('name', 'id'),
'classes' => SchoolClass::orderBy('name')->pluck('name', 'id'),
'summary' => [
'total' => $totalTransfers,
'with_letter' => $withLetterCount,
'without_letter' => $withoutLetterCount,
'this_year' => $thisYearCount,
'latest_transfer_date' => $latestTransferDate,
'filtered' => $studentTransfers->total(),
],
'recentTransfers' => $recentTransfers,
]);
}
+2 -6
View File
@@ -44,14 +44,10 @@ class SyncTokenController extends Controller
public function revoke(SyncToken $syncToken): RedirectResponse
{
if (! $syncToken->revoked_at) {
$syncToken->update([
'revoked_at' => now(),
]);
}
$syncToken->delete();
return redirect()
->route('settings.sync-tokens.index')
->with('status', 'Token sinkronisasi berhasil dinonaktifkan.');
->with('status', 'Token sinkronisasi berhasil dihapus.');
}
}
+1
View File
@@ -37,6 +37,7 @@ class Student extends Model
protected $fillable = [
'student_number',
'nisn',
'nik',
'full_name',
'gender',
'place_of_birth',
+12
View File
@@ -29,6 +29,18 @@ class StudentGraduation extends Model
'ijazah_file_path',
'ijazah_mime_type',
'ijazah_file_size',
'transcript_file_name',
'transcript_file_path',
'transcript_mime_type',
'transcript_file_size',
'ukk_certificate_file_name',
'ukk_certificate_file_path',
'ukk_certificate_mime_type',
'ukk_certificate_file_size',
'lsp_certificate_file_name',
'lsp_certificate_file_path',
'lsp_certificate_mime_type',
'lsp_certificate_file_size',
'status',
];
@@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('student_graduations', function (Blueprint $table) {
$table->string('transcript_file_name')->nullable()->after('ijazah_file_size');
$table->string('transcript_file_path')->nullable()->after('transcript_file_name');
$table->string('transcript_mime_type')->nullable()->after('transcript_file_path');
$table->unsignedBigInteger('transcript_file_size')->nullable()->after('transcript_mime_type');
$table->string('skhun_file_name')->nullable()->after('transcript_file_size');
$table->string('skhun_file_path')->nullable()->after('skhun_file_name');
$table->string('skhun_mime_type')->nullable()->after('skhun_file_path');
$table->unsignedBigInteger('skhun_file_size')->nullable()->after('skhun_mime_type');
});
}
public function down(): void
{
Schema::table('student_graduations', function (Blueprint $table) {
$table->dropColumn([
'transcript_file_name',
'transcript_file_path',
'transcript_mime_type',
'transcript_file_size',
'skhun_file_name',
'skhun_file_path',
'skhun_mime_type',
'skhun_file_size',
]);
});
}
};
@@ -0,0 +1,38 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('student_graduations', function (Blueprint $table) {
$table->string('ukk_certificate_file_name')->nullable()->after('skhun_file_size');
$table->string('ukk_certificate_file_path')->nullable()->after('ukk_certificate_file_name');
$table->string('ukk_certificate_mime_type')->nullable()->after('ukk_certificate_file_path');
$table->unsignedBigInteger('ukk_certificate_file_size')->nullable()->after('ukk_certificate_mime_type');
$table->string('lsp_certificate_file_name')->nullable()->after('ukk_certificate_file_size');
$table->string('lsp_certificate_file_path')->nullable()->after('lsp_certificate_file_name');
$table->string('lsp_certificate_mime_type')->nullable()->after('lsp_certificate_file_path');
$table->unsignedBigInteger('lsp_certificate_file_size')->nullable()->after('lsp_certificate_mime_type');
});
}
public function down(): void
{
Schema::table('student_graduations', function (Blueprint $table) {
$table->dropColumn([
'ukk_certificate_file_name',
'ukk_certificate_file_path',
'ukk_certificate_mime_type',
'ukk_certificate_file_size',
'lsp_certificate_file_name',
'lsp_certificate_file_path',
'lsp_certificate_mime_type',
'lsp_certificate_file_size',
]);
});
}
};
@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('student_graduations', function (Blueprint $table) {
$table->dropColumn([
'skhun_file_name',
'skhun_file_path',
'skhun_mime_type',
'skhun_file_size',
]);
});
}
public function down(): void
{
Schema::table('student_graduations', function (Blueprint $table) {
$table->string('skhun_file_name')->nullable()->after('transcript_file_size');
$table->string('skhun_file_path')->nullable()->after('skhun_file_name');
$table->string('skhun_mime_type')->nullable()->after('skhun_file_path');
$table->unsignedBigInteger('skhun_file_size')->nullable()->after('skhun_mime_type');
});
}
};
@@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('students', function (Blueprint $table) {
$table->string('nik', 30)->nullable()->after('nisn');
});
}
public function down(): void
{
Schema::table('students', function (Blueprint $table) {
$table->dropColumn('nik');
});
}
};
+10
View File
@@ -5,6 +5,7 @@
"packages": {
"": {
"dependencies": {
"@fortawesome/fontawesome-free": "^7.2.0",
"sweetalert2": "^11.26.24"
},
"devDependencies": {
@@ -67,6 +68,15 @@
"tslib": "^2.4.0"
}
},
"node_modules/@fortawesome/fontawesome-free": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-7.2.0.tgz",
"integrity": "sha512-3DguDv/oUE+7vjMeTSOjCSG+KeawgVQOHrKRnvUuqYh1mfArrh7s+s8hXW3e4RerBA1+Wh+hBqf8sJNpqNrBWg==",
"license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
"engines": {
"node": ">=6"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+1
View File
@@ -19,6 +19,7 @@
"vite": "^8.0.0"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^7.2.0",
"sweetalert2": "^11.26.24"
}
}
+2
View File
@@ -1,3 +1,5 @@
@import '@fortawesome/fontawesome-free/css/all.min.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
+76
View File
@@ -365,6 +365,82 @@ document.addEventListener('DOMContentLoaded', () => {
confirmText: 'Ya, hapus semua',
});
const bulkUploadInput = document.querySelector('[data-bulk-upload-input]');
const bulkUploadDropzone = document.querySelector('[data-bulk-upload-dropzone]');
const bulkUploadDropzoneInner = document.querySelector('[data-bulk-upload-dropzone-inner]');
const bulkUploadSummary = document.querySelector('[data-bulk-upload-summary]');
const bulkUploadFileList = document.querySelector('[data-bulk-upload-file-list]');
if (bulkUploadInput && bulkUploadDropzone && bulkUploadDropzoneInner) {
const renderBulkUploadFiles = () => {
const files = Array.from(bulkUploadInput.files || []);
if (!bulkUploadSummary || !bulkUploadFileList) {
return;
}
if (files.length === 0) {
bulkUploadSummary.classList.add('hidden');
bulkUploadFileList.classList.add('hidden');
bulkUploadFileList.innerHTML = '';
return;
}
const totalBytes = files.reduce((sum, file) => sum + (file.size || 0), 0);
const totalMb = (totalBytes / (1024 * 1024)).toFixed(2);
bulkUploadSummary.classList.remove('hidden');
bulkUploadSummary.innerHTML = `<div class="font-semibold">${files.length} file siap dipreview</div><div class="mt-1 text-xs text-emerald-700/80">Total ukuran sementara ${totalMb} MB. Batas upload bulk saat ini maksimal 4 MB per file.</div>`;
bulkUploadFileList.classList.remove('hidden');
bulkUploadFileList.innerHTML = `
<div class="mb-3 flex items-center justify-between gap-3">
<div class="text-xs font-semibold uppercase tracking-[0.14em] text-slate-500">File terpilih</div>
<div class="text-xs text-slate-500">Menampilkan ${Math.min(files.length, 8)} dari ${files.length} file</div>
</div>
<div class="space-y-2">
${files.slice(0, 8).map((file) => `
<div class="flex items-center justify-between gap-3 rounded-2xl border border-slate-200 bg-white px-3 py-2 text-xs text-slate-600 shadow-sm ring-1 ring-white/70">
<div class="min-w-0">
<div class="truncate font-semibold text-slate-800">${file.name}</div>
<div class="mt-0.5 text-[11px] text-slate-500">${(file.size / 1024 / 1024).toFixed(2)} MB</div>
</div>
<span class="inline-flex shrink-0 rounded-full bg-indigo-100 px-2.5 py-1 text-[11px] font-semibold text-indigo-700">${(file.name.split('.').pop() || 'FILE').toUpperCase()}</span>
</div>
`).join('')}
${files.length > 8 ? `<div class="rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-3 py-2 text-center text-[11px] font-semibold text-slate-500">+${files.length - 8} file lainnya</div>` : ''}
</div>
`;
};
['dragenter', 'dragover'].forEach((eventName) => {
bulkUploadDropzone.addEventListener(eventName, (event) => {
event.preventDefault();
bulkUploadDropzone.classList.add('border-indigo-400', 'bg-indigo-50/40');
bulkUploadDropzoneInner.classList.add('border-indigo-300', 'bg-indigo-50/50');
});
});
['dragleave', 'drop'].forEach((eventName) => {
bulkUploadDropzone.addEventListener(eventName, (event) => {
event.preventDefault();
bulkUploadDropzone.classList.remove('border-indigo-400', 'bg-indigo-50/40');
bulkUploadDropzoneInner.classList.remove('border-indigo-300', 'bg-indigo-50/50');
});
});
bulkUploadDropzone.addEventListener('drop', (event) => {
const files = event.dataTransfer?.files;
if (files && files.length > 0) {
bulkUploadInput.files = files;
renderBulkUploadFiles();
}
});
bulkUploadInput.addEventListener('change', renderBulkUploadFiles);
renderBulkUploadFiles();
}
const promotionForm = document.querySelector('[data-student-promotion-form]');
if (promotionForm) {
+168 -101
View File
@@ -49,65 +49,53 @@
</div>
<div class="grid w-full grid-cols-1 gap-4 md:grid-cols-2">
<div class="group rounded-3xl border border-indigo-200 bg-white p-5 shadow-sm shadow-indigo-100 transition hover:-translate-y-0.5 hover:shadow-md">
<div class="group rounded-3xl border border-indigo-200 bg-gradient-to-br from-white via-indigo-50/40 to-indigo-100/50 p-5 shadow-sm shadow-indigo-100 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-indigo-200/60">
<div class="flex items-start justify-between">
<div>
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Total Siswa</p>
<div class="mt-3 text-4xl font-bold text-slate-900">{{ $stats['students'] }}</div>
</div>
<div class="rounded-2xl bg-indigo-50 p-3 text-indigo-600 shadow-sm shadow-indigo-100">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M16 7a4 4 0 1 1-8 0 4 4 0 0 1 8 0ZM4 20a6 6 0 1 1 12 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M18 9a3 3 0 0 1 0 6m-1 5a5 5 0 0 0-3-4.58" />
</svg>
<div class="rounded-2xl bg-white/80 p-3 text-indigo-600 shadow-sm shadow-indigo-100 ring-1 ring-indigo-100 backdrop-blur-sm">
<i class="fa-solid fa-users text-lg"></i>
</div>
</div>
<p class="mt-4 text-xs leading-5 text-slate-500">Jumlah seluruh siswa yang tercatat di sistem saat ini.</p>
</div>
<div class="group rounded-3xl border border-rose-200 bg-white p-5 shadow-sm shadow-rose-100 transition hover:-translate-y-0.5 hover:shadow-md">
<div class="group rounded-3xl border border-rose-200 bg-gradient-to-br from-white via-rose-50/40 to-rose-100/50 p-5 shadow-sm shadow-rose-100 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-rose-200/60">
<div class="flex items-start justify-between">
<div>
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-rose-600">Siswa Keluar</p>
<div class="mt-3 text-4xl font-bold text-slate-900">{{ $stats['studentsDropped'] }}</div>
</div>
<div class="rounded-2xl bg-rose-50 p-3 text-rose-600 shadow-sm shadow-rose-100">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M9 12h6" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M12 3C7.03 3 3 7.03 3 12s4.03 9 9 9 9-4.03 9-9-4.03-9-9-9Z" />
</svg>
<div class="rounded-2xl bg-white/80 p-3 text-rose-600 shadow-sm shadow-rose-100 ring-1 ring-rose-100 backdrop-blur-sm">
<i class="fa-solid fa-right-from-bracket text-lg"></i>
</div>
</div>
<p class="mt-4 text-xs leading-5 text-slate-500">Jumlah siswa pada arsip siswa keluar.</p>
</div>
<div class="group rounded-3xl border border-amber-200 bg-white p-5 shadow-sm shadow-amber-100 transition hover:-translate-y-0.5 hover:shadow-md">
<div class="group rounded-3xl border border-amber-200 bg-gradient-to-br from-white via-amber-50/40 to-amber-100/50 p-5 shadow-sm shadow-amber-100 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-amber-200/60">
<div class="flex items-start justify-between">
<div>
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-amber-600">Siswa Mutasi</p>
<div class="mt-3 text-4xl font-bold text-slate-900">{{ $stats['studentsTransferred'] }}</div>
</div>
<div class="rounded-2xl bg-amber-50 p-3 text-amber-600 shadow-sm shadow-amber-100">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M13 7h6m0 0v6m0-6-8 8" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M11 17H7a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h8" />
</svg>
<div class="rounded-2xl bg-white/80 p-3 text-amber-600 shadow-sm shadow-amber-100 ring-1 ring-amber-100 backdrop-blur-sm">
<i class="fa-solid fa-right-left text-lg"></i>
</div>
</div>
<p class="mt-4 text-xs leading-5 text-slate-500">Jumlah siswa pada arsip siswa mutasi.</p>
</div>
<div class="group rounded-3xl border border-sky-200 bg-white p-5 shadow-sm shadow-sky-100 transition hover:-translate-y-0.5 hover:shadow-md">
<div class="group rounded-3xl border border-sky-200 bg-gradient-to-br from-white via-sky-50/40 to-sky-100/50 p-5 shadow-sm shadow-sky-100 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-sky-200/60">
<div class="flex items-start justify-between">
<div>
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-sky-600">Siswa Lulus</p>
<div class="mt-3 text-4xl font-bold text-slate-900">{{ $stats['studentsGraduated'] }}</div>
</div>
<div class="rounded-2xl bg-sky-50 p-3 text-sky-600 shadow-sm shadow-sky-100">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 9l8-4 8 4-8 4-8-4Z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M8 11v3.5c0 1.8 1.8 3.5 4 3.5s4-1.7 4-3.5V11" />
</svg>
<div class="rounded-2xl bg-white/80 p-3 text-sky-600 shadow-sm shadow-sky-100 ring-1 ring-sky-100 backdrop-blur-sm">
<i class="fa-solid fa-graduation-cap text-lg"></i>
</div>
</div>
<p class="mt-4 text-xs leading-5 text-slate-500">Jumlah siswa yang sudah berstatus lulus.</p>
@@ -118,24 +106,49 @@
<x-master-card title="Snapshot Sistem" subtitle="Kondisi data yang paling sering dipakai admin.">
<div class="space-y-4">
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3">
<span class="text-sm text-slate-600">Tahun Angkatan</span>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3 transition hover:bg-slate-100">
<div class="flex items-center gap-3">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-cyan-100 text-cyan-600">
<i class="fa-solid fa-calendar-days text-sm"></i>
</span>
<span class="text-sm text-slate-600">Tahun Angkatan</span>
</div>
<span class="text-sm font-semibold text-slate-900">{{ $stats['academicYears'] }}</span>
</div>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3">
<span class="text-sm text-slate-600">Program Keahlian</span>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3 transition hover:bg-slate-100">
<div class="flex items-center gap-3">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-blue-100 text-blue-600">
<i class="fa-solid fa-building-columns text-sm"></i>
</span>
<span class="text-sm text-slate-600">Program Keahlian</span>
</div>
<span class="text-sm font-semibold text-slate-900">{{ $stats['departments'] }}</span>
</div>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3">
<span class="text-sm text-slate-600">Kelas</span>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3 transition hover:bg-slate-100">
<div class="flex items-center gap-3">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-indigo-100 text-indigo-600">
<i class="fa-solid fa-chalkboard text-sm"></i>
</span>
<span class="text-sm text-slate-600">Kelas</span>
</div>
<span class="text-sm font-semibold text-slate-900">{{ $stats['classes'] }}</span>
</div>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3">
<span class="text-sm text-slate-600">Jenis Dokumen</span>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3 transition hover:bg-slate-100">
<div class="flex items-center gap-3">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-violet-100 text-violet-600">
<i class="fa-solid fa-folder-tree text-sm"></i>
</span>
<span class="text-sm text-slate-600">Jenis Dokumen</span>
</div>
<span class="text-sm font-semibold text-slate-900">{{ $stats['documentTypes'] }}</span>
</div>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3">
<span class="text-sm text-slate-600">Data Prestasi</span>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3 transition hover:bg-slate-100">
<div class="flex items-center gap-3">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-amber-100 text-amber-600">
<i class="fa-solid fa-trophy text-sm"></i>
</span>
<span class="text-sm text-slate-600">Data Prestasi</span>
</div>
<span class="text-sm font-semibold text-slate-900">{{ $stats['studentAchievements'] }}</span>
</div>
</div>
@@ -143,76 +156,66 @@
</div>
<div class="grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-5">
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-200/50">
<div class="group rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/30 p-5 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-200/70">
<div class="flex items-start justify-between">
<div>
<p class="text-sm font-medium text-slate-500">Data Siswa</p>
<h3 class="mt-3 text-3xl font-bold text-slate-900">{{ $stats['students'] }}</h3>
</div>
<div class="rounded-2xl bg-indigo-50 p-3 text-indigo-600">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M16 7a4 4 0 1 1-8 0 4 4 0 0 1 8 0ZM4 20a6 6 0 1 1 12 0" />
</svg>
<div class="rounded-2xl bg-white/80 p-3 text-indigo-600 ring-1 ring-indigo-100 backdrop-blur-sm">
<i class="fa-solid fa-users text-lg"></i>
</div>
</div>
<p class="mt-4 text-xs text-slate-400">Total siswa aktif yang tersimpan di sistem.</p>
</div>
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-200/50">
<div class="group rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-emerald-50/30 p-5 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-200/70">
<div class="flex items-start justify-between">
<div>
<p class="text-sm font-medium text-slate-500">Dokumen Wajib Lengkap</p>
<h3 class="mt-3 text-3xl font-bold text-slate-900">{{ $stats['studentsCompleteDocuments'] }}</h3>
</div>
<div class="rounded-2xl bg-emerald-50 p-3 text-emerald-600">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m5 13 4 4L19 7" />
</svg>
<div class="rounded-2xl bg-white/80 p-3 text-emerald-600 ring-1 ring-emerald-100 backdrop-blur-sm">
<i class="fa-solid fa-circle-check text-lg"></i>
</div>
</div>
<p class="mt-4 text-xs text-slate-400">Siswa yang sudah upload seluruh dokumen wajib.</p>
</div>
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-200/50">
<div class="group rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-amber-50/30 p-5 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-200/70">
<div class="flex items-start justify-between">
<div>
<p class="text-sm font-medium text-slate-500">Belum Lengkap</p>
<h3 class="mt-3 text-3xl font-bold text-slate-900">{{ $stats['studentsIncompleteDocuments'] }}</h3>
</div>
<div class="rounded-2xl bg-amber-50 p-3 text-amber-600">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M12 9v4m0 4h.01M12 3l9 16H3l9-16Z" />
</svg>
<div class="rounded-2xl bg-white/80 p-3 text-amber-600 ring-1 ring-amber-100 backdrop-blur-sm">
<i class="fa-solid fa-triangle-exclamation text-lg"></i>
</div>
</div>
<p class="mt-4 text-xs text-slate-400">Siswa yang masih kurang upload dokumen wajib.</p>
</div>
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-200/50">
<div class="group rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-sky-50/30 p-5 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-200/70">
<div class="flex items-start justify-between">
<div>
<p class="text-sm font-medium text-slate-500">Belum Tervalidasi Penuh</p>
<h3 class="mt-3 text-3xl font-bold text-slate-900">{{ $stats['studentsUnverifiedDocuments'] }}</h3>
</div>
<div class="rounded-2xl bg-sky-50 p-3 text-sky-600">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M12 8v4l3 3m6-3a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
<div class="rounded-2xl bg-white/80 p-3 text-sky-600 ring-1 ring-sky-100 backdrop-blur-sm">
<i class="fa-solid fa-clock text-lg"></i>
</div>
</div>
<p class="mt-4 text-xs text-slate-400">Siswa yang dokumen wajibnya belum valid semua.</p>
</div>
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-200/50">
<div class="group rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-amber-50/30 p-5 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-200/70">
<div class="flex items-start justify-between">
<div>
<p class="text-sm font-medium text-slate-500">Siswa Berprestasi</p>
<h3 class="mt-3 text-3xl font-bold text-slate-900">{{ $stats['studentsWithAchievements'] }}</h3>
</div>
<div class="rounded-2xl bg-amber-50 p-3 text-amber-600">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M12 3 9.5 8H4l4.5 3.3L6.8 17 12 13.8 17.2 17l-1.7-5.7L20 8h-5.5L12 3Z" />
</svg>
<div class="rounded-2xl bg-white/80 p-3 text-amber-600 ring-1 ring-amber-100 backdrop-blur-sm">
<i class="fa-solid fa-trophy text-lg"></i>
</div>
</div>
<p class="mt-4 text-xs text-slate-400">Jumlah siswa yang sudah memiliki minimal satu data prestasi.</p>
@@ -222,55 +225,105 @@
<div class="grid grid-cols-1 gap-6 xl:grid-cols-3">
<x-master-card title="Akses Cepat" subtitle="Masuk langsung ke modul yang paling sering dipakai.">
<div class="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-1">
<a href="{{ route('students.index') }}" class="rounded-2xl border border-slate-200 bg-slate-50 px-5 py-4 text-sm font-medium text-slate-700 transition hover:border-indigo-200 hover:bg-indigo-50 hover:text-indigo-700">
Kelola Data Siswa
<a href="{{ route('students.index') }}" class="group flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-5 py-4 text-sm font-medium text-slate-700 transition hover:border-indigo-200 hover:bg-indigo-50 hover:text-indigo-700">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-indigo-100 text-indigo-600 transition group-hover:bg-indigo-200">
<i class="fa-solid fa-users text-sm"></i>
</span>
<span>Kelola Data Siswa</span>
</a>
@if ($isAdmin)
<a href="{{ route('classes.index') }}" class="rounded-2xl border border-slate-200 bg-slate-50 px-5 py-4 text-sm font-medium text-slate-700 transition hover:border-indigo-200 hover:bg-indigo-50 hover:text-indigo-700">
Kelola Kelas & Generate Tahun Baru
<a href="{{ route('classes.index') }}" class="group flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-5 py-4 text-sm font-medium text-slate-700 transition hover:border-cyan-200 hover:bg-cyan-50 hover:text-cyan-700">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-cyan-100 text-cyan-600 transition group-hover:bg-cyan-200">
<i class="fa-solid fa-chalkboard text-sm"></i>
</span>
<span>Kelola Kelas & Generate Tahun Baru</span>
</a>
<a href="{{ route('document-types.index') }}" class="rounded-2xl border border-slate-200 bg-slate-50 px-5 py-4 text-sm font-medium text-slate-700 transition hover:border-indigo-200 hover:bg-indigo-50 hover:text-indigo-700">
Kelola Jenis Dokumen
<a href="{{ route('document-types.index') }}" class="group flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-5 py-4 text-sm font-medium text-slate-700 transition hover:border-violet-200 hover:bg-violet-50 hover:text-violet-700">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-violet-100 text-violet-600 transition group-hover:bg-violet-200">
<i class="fa-solid fa-folder-tree text-sm"></i>
</span>
<span>Kelola Jenis Dokumen</span>
</a>
@endif
<a href="{{ route('student-achievements.index') }}" class="rounded-2xl border border-slate-200 bg-slate-50 px-5 py-4 text-sm font-medium text-slate-700 transition hover:border-indigo-200 hover:bg-indigo-50 hover:text-indigo-700">
Kelola Siswa Berprestasi
<a href="{{ route('student-achievements.index') }}" class="group flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-5 py-4 text-sm font-medium text-slate-700 transition hover:border-amber-200 hover:bg-amber-50 hover:text-amber-700">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-amber-100 text-amber-600 transition group-hover:bg-amber-200">
<i class="fa-solid fa-trophy text-sm"></i>
</span>
<span>Kelola Siswa Berprestasi</span>
</a>
<a href="{{ route('student-promotions.index') }}" class="rounded-2xl border border-slate-200 bg-slate-50 px-5 py-4 text-sm font-medium text-slate-700 transition hover:border-indigo-200 hover:bg-indigo-50 hover:text-indigo-700">
Buka Modul Promosi Siswa
<a href="{{ route('student-promotions.index') }}" class="group flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-5 py-4 text-sm font-medium text-slate-700 transition hover:border-lime-200 hover:bg-lime-50 hover:text-lime-700">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-lime-100 text-lime-600 transition group-hover:bg-lime-200">
<i class="fa-solid fa-arrow-trend-up text-sm"></i>
</span>
<span>Buka Modul Promosi Siswa</span>
</a>
</div>
</x-master-card>
<x-master-card title="Status Modul" subtitle="Modul yang sudah hidup dan siap dipakai.">
<div class="space-y-4">
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3">
<span class="text-sm text-slate-600">Auth & Login</span>
<span class="rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700">Aktif</span>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3 transition hover:bg-slate-100">
<div class="flex items-center gap-3">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-emerald-100 text-emerald-600">
<i class="fa-solid fa-lock text-sm"></i>
</span>
<span class="text-sm text-slate-600">Auth & Login</span>
</div>
<span class="inline-flex items-center gap-2 rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700"><span class="h-2 w-2 rounded-full bg-emerald-500"></span>Aktif</span>
</div>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3">
<span class="text-sm text-slate-600">Master Data</span>
<span class="rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700">Aktif</span>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3 transition hover:bg-slate-100">
<div class="flex items-center gap-3">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-cyan-100 text-cyan-600">
<i class="fa-solid fa-database text-sm"></i>
</span>
<span class="text-sm text-slate-600">Master Data</span>
</div>
<span class="inline-flex items-center gap-2 rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700"><span class="h-2 w-2 rounded-full bg-emerald-500"></span>Aktif</span>
</div>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3">
<span class="text-sm text-slate-600">Data Siswa</span>
<span class="rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700">Aktif</span>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3 transition hover:bg-slate-100">
<div class="flex items-center gap-3">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-indigo-100 text-indigo-600">
<i class="fa-solid fa-users text-sm"></i>
</span>
<span class="text-sm text-slate-600">Data Siswa</span>
</div>
<span class="inline-flex items-center gap-2 rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700"><span class="h-2 w-2 rounded-full bg-emerald-500"></span>Aktif</span>
</div>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3">
<span class="text-sm text-slate-600">Dokumen & Verifikasi</span>
<span class="rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700">Aktif</span>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3 transition hover:bg-slate-100">
<div class="flex items-center gap-3">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-violet-100 text-violet-600">
<i class="fa-solid fa-folder-check text-sm"></i>
</span>
<span class="text-sm text-slate-600">Dokumen & Verifikasi</span>
</div>
<span class="inline-flex items-center gap-2 rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700"><span class="h-2 w-2 rounded-full bg-emerald-500"></span>Aktif</span>
</div>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3">
<span class="text-sm text-slate-600">Histori Akademik</span>
<span class="rounded-full bg-sky-100 px-3 py-1 text-xs font-semibold text-sky-700">Siap</span>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3 transition hover:bg-slate-100">
<div class="flex items-center gap-3">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-sky-100 text-sky-600">
<i class="fa-solid fa-clock-rotate-left text-sm"></i>
</span>
<span class="text-sm text-slate-600">Histori Akademik</span>
</div>
<span class="inline-flex items-center gap-2 rounded-full bg-sky-100 px-3 py-1 text-xs font-semibold text-sky-700"><span class="h-2 w-2 rounded-full bg-sky-500"></span>Siap</span>
</div>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3">
<span class="text-sm text-slate-600">Promosi Siswa</span>
<span class="rounded-full bg-sky-100 px-3 py-1 text-xs font-semibold text-sky-700">Siap</span>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3 transition hover:bg-slate-100">
<div class="flex items-center gap-3">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-lime-100 text-lime-600">
<i class="fa-solid fa-arrow-trend-up text-sm"></i>
</span>
<span class="text-sm text-slate-600">Promosi Siswa</span>
</div>
<span class="inline-flex items-center gap-2 rounded-full bg-sky-100 px-3 py-1 text-xs font-semibold text-sky-700"><span class="h-2 w-2 rounded-full bg-sky-500"></span>Siap</span>
</div>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3">
<span class="text-sm text-slate-600">Siswa Berprestasi</span>
<span class="rounded-full bg-sky-100 px-3 py-1 text-xs font-semibold text-sky-700">Siap</span>
<div class="flex items-center justify-between rounded-2xl bg-slate-50 px-4 py-3 transition hover:bg-slate-100">
<div class="flex items-center gap-3">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-amber-100 text-amber-600">
<i class="fa-solid fa-trophy text-sm"></i>
</span>
<span class="text-sm text-slate-600">Siswa Berprestasi</span>
</div>
<span class="inline-flex items-center gap-2 rounded-full bg-sky-100 px-3 py-1 text-xs font-semibold text-sky-700"><span class="h-2 w-2 rounded-full bg-sky-500"></span>Siap</span>
</div>
</div>
</x-master-card>
@@ -278,19 +331,33 @@
<x-master-card title="Rekap Data Siswa" subtitle="Ringkasan jumlah siswa pria dan perempuan per tahun angkatan.">
<div class="space-y-4 text-sm text-slate-600">
@forelse ($studentGenderByAcademicYear as $item)
<div class="rounded-2xl bg-slate-50 px-4 py-4">
<div class="rounded-3xl border border-slate-200 bg-slate-50/80 px-4 py-4 shadow-sm transition hover:border-slate-300 hover:bg-white">
<div class="flex items-center justify-between gap-4">
<div class="font-semibold text-slate-800">{{ $item['name'] }}</div>
<div class="text-xs font-semibold uppercase tracking-[0.14em] text-slate-400">Total {{ $item['total'] }}</div>
</div>
<div class="mt-3 grid grid-cols-2 gap-3">
<div class="rounded-2xl border border-sky-100 bg-white px-4 py-3">
<div class="text-xs font-semibold uppercase tracking-[0.14em] text-sky-500">Laki-laki</div>
<div class="mt-1 text-2xl font-bold text-slate-900">{{ $item['male'] }}</div>
<div class="flex items-center gap-3">
<span class="inline-flex h-10 w-10 items-center justify-center rounded-xl bg-indigo-100 text-indigo-600">
<i class="fa-solid fa-calendar-days text-sm"></i>
</span>
<div>
<div class="font-semibold text-slate-800">{{ $item['name'] }}</div>
<div class="text-xs text-slate-400">Distribusi siswa per gender</div>
</div>
</div>
<div class="rounded-2xl border border-rose-100 bg-white px-4 py-3">
<div class="text-xs font-semibold uppercase tracking-[0.14em] text-rose-500">Perempuan</div>
<div class="mt-1 text-2xl font-bold text-slate-900">{{ $item['female'] }}</div>
<div class="rounded-full bg-slate-900 px-3 py-1 text-xs font-semibold uppercase tracking-[0.14em] text-white">Total {{ $item['total'] }}</div>
</div>
<div class="mt-4 grid grid-cols-2 gap-3">
<div class="rounded-2xl border border-sky-100 bg-white px-4 py-3 shadow-sm">
<div class="flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.14em] text-sky-500">
<i class="fa-solid fa-mars text-[11px]"></i>
<span>Laki-laki</span>
</div>
<div class="mt-2 text-2xl font-bold text-slate-900">{{ $item['male'] }}</div>
</div>
<div class="rounded-2xl border border-rose-100 bg-white px-4 py-3 shadow-sm">
<div class="flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.14em] text-rose-500">
<i class="fa-solid fa-venus text-[11px]"></i>
<span>Perempuan</span>
</div>
<div class="mt-2 text-2xl font-bold text-slate-900">{{ $item['female'] }}</div>
</div>
</div>
</div>
@@ -1,132 +1,273 @@
<x-master-page title="Bulk Upload Dokumen Kelulusan" description="Upload SKL atau ijazah dalam jumlah besar dengan pencocokan otomatis berdasarkan NIS atau NISN.">
<x-master-page title="Bulk Upload Dokumen Kelulusan" description="Upload SKL, ijazah, transkrip nilai, sertifikat UKK, atau sertifikat LSP dalam jumlah besar dengan pencocokan otomatis berdasarkan NIS atau NISN.">
<div class="space-y-6">
<x-master-card title="Bulk Upload Dokumen Kelulusan" subtitle="Gunakan nama file sesuai NIS atau NISN agar sistem dapat mencocokkan dokumen secara otomatis ke siswa lulus.">
<div class="mb-6 rounded-2xl border border-sky-200 bg-sky-50 px-4 py-4 text-sm text-sky-700">
<p class="font-semibold">Aturan nama file yang disarankan</p>
<ul class="mt-2 list-disc space-y-1 pl-5 text-sky-700/90">
<li>Jika memilih pencocokan <strong>NIS</strong>, nama file harus berupa NIS siswa. Contoh: <code>2024001.pdf</code></li>
<li>Jika memilih pencocokan <strong>NISN</strong>, nama file harus berupa NISN siswa. Contoh: <code>9988776655.pdf</code></li>
<li>Upload dipisah per jenis dokumen: jalankan batch <strong>SKL</strong> dan <strong>Ijazah</strong> secara terpisah agar lebih aman.</li>
<li>Untuk meminimalkan error, gunakan maksimal <strong>10 file</strong> per batch dengan ukuran maksimal <strong>2 MB</strong> per file.</li>
</ul>
<div class="grid grid-cols-1 gap-4 xl:grid-cols-[1.1fr_0.9fr]">
<div class="rounded-3xl border border-indigo-200 bg-gradient-to-br from-indigo-50 via-white to-sky-50 px-6 py-6 shadow-sm shadow-indigo-100/60 ring-1 ring-white/70">
<div class="flex flex-col gap-5 lg:flex-row lg:items-start lg:justify-between">
<div>
<div class="inline-flex items-center gap-2 rounded-full bg-indigo-100 px-3 py-1 text-xs font-semibold uppercase tracking-[0.18em] text-indigo-700">
<i class="fa-solid fa-file-arrow-up"></i>
Arsip Kelulusan
</div>
<h2 class="mt-4 text-2xl font-bold text-slate-900">Bulk Upload Dokumen Kelulusan</h2>
<p class="mt-2 max-w-2xl text-sm leading-6 text-slate-600">
Upload banyak dokumen sekaligus untuk siswa lulus dengan pencocokan otomatis berbasis NIS atau NISN, supaya proses arsip jauh lebih cepat dan minim input manual.
</p>
</div>
<div class="rounded-3xl border border-white/80 bg-white/80 px-5 py-4 text-right shadow-sm backdrop-blur">
<div class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Batas Batch</div>
<div class="mt-2 text-4xl font-bold text-indigo-700">10</div>
<div class="mt-1 text-xs text-slate-500">file per upload</div>
</div>
</div>
</div>
<form method="POST" action="{{ route('graduated-students.bulk-upload.preview') }}" enctype="multipart/form-data" class="space-y-6">
@csrf
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
<div>
<label for="document_type" class="mb-2 block text-sm font-medium text-slate-700">Jenis Dokumen</label>
<select id="document_type" name="document_type" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100" required>
<option value="">Pilih jenis dokumen</option>
@foreach ($documentTypes as $value => $label)
<option value="{{ $value }}" @selected(old('document_type') === $value)>{{ $label }}</option>
@endforeach
</select>
<x-input-error :messages="$errors->get('document_type')" class="mt-2" />
</div>
<div>
<label for="match_by" class="mb-2 block text-sm font-medium text-slate-700">Cocokkan Berdasarkan</label>
<select id="match_by" name="match_by" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100" required>
@foreach ($matchByOptions as $value => $label)
<option value="{{ $value }}" @selected(old('match_by', 'student_number') === $value)>{{ $label }}</option>
@endforeach
</select>
<x-input-error :messages="$errors->get('match_by')" class="mt-2" />
<div class="grid grid-cols-1 gap-4 md:grid-cols-3 xl:grid-cols-1">
<div class="rounded-3xl border border-emerald-200 bg-gradient-to-br from-white via-emerald-50/60 to-emerald-100/70 p-5 shadow-sm shadow-emerald-100/60 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-emerald-700">Maksimal File</div>
<div class="mt-3 text-3xl font-bold text-emerald-800">10</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-emerald-600 ring-1 ring-emerald-100">
<i class="fa-solid fa-layer-group text-lg"></i>
</div>
</div>
</div>
<div>
<label for="documents" class="mb-2 block text-sm font-medium text-slate-700">Pilih Banyak Dokumen Sekaligus</label>
<input id="documents" name="documents[]" type="file" accept=".pdf,.jpg,.jpeg,.png" multiple class="block w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-700 file:mr-4 file:rounded-xl file:border-0 file:bg-slate-100 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-slate-700 hover:file:bg-slate-200">
<p class="mt-2 text-xs text-slate-500">Format yang didukung: PDF, JPG, JPEG, PNG. Maksimal 2 MB per file dan 10 file per batch upload.</p>
<x-input-error :messages="$errors->get('documents')" class="mt-2" />
<x-input-error :messages="$errors->get('documents.*')" class="mt-2" />
<div class="rounded-3xl border border-amber-200 bg-gradient-to-br from-white via-amber-50/60 to-amber-100/70 p-5 shadow-sm shadow-amber-100/60 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-amber-700">Ukuran per File</div>
<div class="mt-3 text-3xl font-bold text-amber-800">4 MB</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-amber-600 ring-1 ring-amber-100">
<i class="fa-solid fa-weight-scale text-lg"></i>
</div>
</div>
</div>
<div class="flex flex-wrap items-center gap-3">
<button type="submit" class="inline-flex items-center rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
Preview Mapping Dokumen
</button>
<a href="{{ route('graduated-students.index') }}" class="inline-flex items-center rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">
Kembali ke Data Siswa Lulus
</a>
<div class="rounded-3xl border border-sky-200 bg-gradient-to-br from-white via-sky-50/60 to-sky-100/70 p-5 shadow-sm shadow-sky-100/60 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-sky-700">Format</div>
<div class="mt-3 text-xl font-bold text-sky-800">PDF, JPG, PNG</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-sky-600 ring-1 ring-sky-100">
<i class="fa-solid fa-file-circle-check text-lg"></i>
</div>
</div>
</div>
</form>
</x-master-card>
</div>
</div>
<div class="grid grid-cols-1 gap-6 xl:grid-cols-[1.1fr_0.9fr]">
<x-master-card title="Form Bulk Upload" subtitle="Pilih jenis dokumen, metode pencocokan, lalu upload banyak file sekaligus.">
<div class="space-y-5">
<div class="rounded-3xl border border-sky-200 bg-gradient-to-br from-sky-50 via-white to-sky-100/60 p-5 text-sm text-sky-800 shadow-sm ring-1 ring-white/70">
<div class="flex items-start gap-3">
<div class="mt-0.5 flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl bg-white text-sky-600 shadow-sm ring-1 ring-sky-100">
<i class="fa-solid fa-lightbulb"></i>
</div>
<div>
<p class="font-semibold">Aturan nama file yang disarankan</p>
<ul class="mt-3 space-y-2 text-sky-800/90">
<li class="flex gap-2"><span class="mt-1 h-1.5 w-1.5 rounded-full bg-sky-500"></span><span>Jika memilih pencocokan <strong>NIS</strong>, nama file harus berupa NIS siswa. Contoh: <code>2024001.pdf</code></span></li>
<li class="flex gap-2"><span class="mt-1 h-1.5 w-1.5 rounded-full bg-sky-500"></span><span>Jika memilih pencocokan <strong>NISN</strong>, nama file harus berupa NISN siswa. Contoh: <code>9988776655.pdf</code></span></li>
<li class="flex gap-2"><span class="mt-1 h-1.5 w-1.5 rounded-full bg-sky-500"></span><span>Upload dipisah per jenis dokumen, misalnya batch khusus <strong>SKL</strong>, <strong>Ijazah</strong>, <strong>Transkrip Nilai</strong>, <strong>Sertifikat UKK</strong>, atau <strong>Sertifikat LSP</strong>.</span></li>
<li class="flex gap-2"><span class="mt-1 h-1.5 w-1.5 rounded-full bg-sky-500"></span><span>Gunakan maksimal <strong>10 file</strong> per batch dengan ukuran maksimal <strong>4 MB</strong> per file agar konsisten dengan upload manual.</span></li>
</ul>
</div>
</div>
</div>
<form method="POST" action="{{ route('graduated-students.bulk-upload.preview') }}" enctype="multipart/form-data" class="space-y-5">
@csrf
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
<div class="rounded-3xl border border-slate-200 bg-slate-50/70 p-4 shadow-sm ring-1 ring-white/70">
<label for="document_type" class="mb-2 block text-sm font-semibold text-slate-700">Jenis Dokumen</label>
<select id="document_type" name="document_type" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100" required>
<option value="">Pilih jenis dokumen</option>
@foreach ($documentTypes as $value => $label)
<option value="{{ $value }}" @selected(old('document_type') === $value)>{{ $label }}</option>
@endforeach
</select>
<p class="mt-2 text-xs text-slate-500">Tiap batch sebaiknya hanya berisi satu jenis dokumen yang sama.</p>
<x-input-error :messages="$errors->get('document_type')" class="mt-2" />
</div>
<div class="rounded-3xl border border-slate-200 bg-slate-50/70 p-4 shadow-sm ring-1 ring-white/70">
<label for="match_by" class="mb-2 block text-sm font-semibold text-slate-700">Cocokkan Berdasarkan</label>
<select id="match_by" name="match_by" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100" required>
@foreach ($matchByOptions as $value => $label)
<option value="{{ $value }}" @selected(old('match_by', 'student_number') === $value)>{{ $label }}</option>
@endforeach
</select>
<p class="mt-2 text-xs text-slate-500">Pastikan nama file konsisten dengan identitas yang dipilih.</p>
<x-input-error :messages="$errors->get('match_by')" class="mt-2" />
</div>
</div>
<div data-bulk-upload-dropzone class="rounded-3xl border border-dashed border-indigo-300 bg-gradient-to-br from-white via-indigo-50/30 to-sky-50/40 p-5 shadow-sm ring-1 ring-white/70 transition">
<label for="documents" class="mb-2 block text-sm font-semibold text-slate-700">Pilih Banyak Dokumen Sekaligus</label>
<div class="rounded-2xl border border-dashed border-slate-300 bg-white/90 p-5 transition" data-bulk-upload-dropzone-inner>
<div class="flex flex-col items-center justify-center text-center">
<div class="flex h-14 w-14 items-center justify-center rounded-2xl bg-indigo-100 text-indigo-600 shadow-sm ring-1 ring-indigo-100">
<i class="fa-solid fa-cloud-arrow-up text-xl"></i>
</div>
<div class="mt-4 text-sm font-semibold text-slate-800">Tarik file ke area ini atau pilih manual</div>
<div class="mt-1 text-xs text-slate-500">Bisa pilih banyak file sekaligus untuk satu jenis dokumen yang sama, dengan batas maksimal 4 MB per file.</div>
</div>
<input id="documents" name="documents[]" type="file" accept=".pdf,.jpg,.jpeg,.png" multiple class="mt-5 block w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-700 file:mr-4 file:rounded-xl file:border-0 file:bg-indigo-100 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-indigo-700 hover:file:bg-indigo-200" data-bulk-upload-input>
<div class="mt-4 flex flex-wrap gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500">
<span class="inline-flex items-center gap-2 rounded-full bg-white px-3 py-1 ring-1 ring-slate-200"><i class="fa-solid fa-paperclip text-slate-400"></i>Maks. 10 file</span>
<span class="inline-flex items-center gap-2 rounded-full bg-white px-3 py-1 ring-1 ring-slate-200"><i class="fa-solid fa-weight-scale text-slate-400"></i>Maks. 4 MB / file</span>
<span class="inline-flex items-center gap-2 rounded-full bg-white px-3 py-1 ring-1 ring-slate-200"><i class="fa-solid fa-file-lines text-slate-400"></i>PDF, JPG, JPEG, PNG</span>
</div>
<div class="mt-4 hidden rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-700" data-bulk-upload-summary></div>
<div class="mt-3 hidden rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3" data-bulk-upload-file-list></div>
</div>
<x-input-error :messages="$errors->get('documents')" class="mt-3" />
<x-input-error :messages="$errors->get('documents.*')" class="mt-2" />
</div>
<div class="flex flex-wrap items-center gap-3">
<button type="submit" class="inline-flex items-center gap-2 rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
<i class="fa-solid fa-magnifying-glass"></i>
<span>Preview Mapping Dokumen</span>
</button>
<a href="{{ route('graduated-students.index') }}" class="inline-flex items-center gap-2 rounded-2xl border border-slate-200 bg-white px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">
<i class="fa-solid fa-arrow-left"></i>
<span>Kembali ke Data Siswa Lulus</span>
</a>
</div>
</form>
</div>
</x-master-card>
<x-master-card title="Checklist Sebelum Upload" subtitle="Cek cepat supaya batch tidak gagal atau salah pasang dokumen.">
<div class="space-y-4 text-sm text-slate-600">
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-start gap-3">
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl bg-emerald-100 text-emerald-600"><i class="fa-solid fa-check"></i></div>
<div>
<div class="font-semibold text-slate-800">Samakan nama file dengan identitas siswa</div>
<div class="mt-1 text-xs leading-5 text-slate-500">Gunakan NIS atau NISN sesuai pilihan pencocokan. Hindari tambahan teks di nama file.</div>
</div>
</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-start gap-3">
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl bg-amber-100 text-amber-600"><i class="fa-solid fa-layer-group"></i></div>
<div>
<div class="font-semibold text-slate-800">Pisahkan per jenis dokumen</div>
<div class="mt-1 text-xs leading-5 text-slate-500">Jangan campur SKL, ijazah, dan transkrip dalam satu batch agar mapping tetap bersih.</div>
</div>
</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-start gap-3">
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl bg-rose-100 text-rose-600"><i class="fa-solid fa-triangle-exclamation"></i></div>
<div>
<div class="font-semibold text-slate-800">Perhatikan file yang akan replace</div>
<div class="mt-1 text-xs leading-5 text-slate-500">Preview akan memberi tanda jika dokumen lama akan diganti, jadi operator masih bisa cek sebelum proses permanen.</div>
</div>
</div>
</div>
</div>
</x-master-card>
</div>
@if ($preview)
<x-master-card title="Preview Hasil Mapping" :subtitle="'Periksa hasil pencocokan file ' . $preview['document_label'] . ' sebelum diproses permanen.'">
<div class="mb-6 grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-5">
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4">
<div class="rounded-2xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-slate-100/70 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Jenis Dokumen</div>
<div class="mt-2 text-lg font-bold text-slate-900">{{ $preview['document_label'] }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4">
<div class="rounded-2xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-slate-100/70 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Pencocokan</div>
<div class="mt-2 text-lg font-bold text-slate-900">{{ $preview['match_by_label'] }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4">
<div class="rounded-2xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-slate-100/70 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Total File</div>
<div class="mt-2 text-lg font-bold text-slate-900">{{ $preview['summary']['total_files'] }}</div>
</div>
<div class="rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-4">
<div class="rounded-2xl border border-emerald-200 bg-gradient-to-br from-white via-emerald-50/60 to-emerald-100/70 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-emerald-700">Cocok</div>
<div class="mt-2 text-lg font-bold text-emerald-800">{{ $preview['summary']['matched'] }}</div>
</div>
<div class="rounded-2xl border border-amber-200 bg-amber-50 px-4 py-4">
<div class="rounded-2xl border border-amber-200 bg-gradient-to-br from-white via-amber-50/60 to-amber-100/70 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-amber-700">Akan Replace</div>
<div class="mt-2 text-lg font-bold text-amber-800">{{ $preview['summary']['will_replace'] }}</div>
</div>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-slate-200 text-sm">
<thead class="bg-slate-50 text-left text-slate-500">
<tr>
<th class="px-4 py-3 font-semibold">Nama File</th>
<th class="px-4 py-3 font-semibold">Key Terdeteksi</th>
<th class="px-4 py-3 font-semibold">Siswa Lulus</th>
<th class="px-4 py-3 font-semibold">Status</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 bg-white">
@foreach ($preview['rows'] as $row)
<div class="overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-sm shadow-slate-200/50 ring-1 ring-white/70">
<div class="border-b border-slate-200 bg-gradient-to-r from-white via-slate-50 to-emerald-50/30 px-5 py-4">
<div class="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div>
<div class="text-sm font-semibold text-slate-800">Detail Hasil Mapping</div>
<div class="mt-1 text-xs text-slate-500">Review file yang cocok, file baru, dan file yang akan menggantikan dokumen lama.</div>
</div>
<div class="inline-flex items-center gap-2 rounded-full bg-slate-900 px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.16em] text-white shadow-sm">
<i class="fa-solid fa-table-list"></i>
{{ count($preview['rows']) }} baris preview
</div>
</div>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-slate-200 text-sm">
<thead class="bg-slate-50 text-left text-slate-500">
<tr>
<td class="px-4 py-3 text-slate-700">{{ $row['original_name'] }}</td>
<td class="px-4 py-3 text-slate-600">{{ $row['match_key'] ?: '-' }}</td>
<td class="px-4 py-3 text-slate-700">
@if ($row['graduation_id'])
<div class="font-semibold text-slate-900">{{ $row['student_name'] }}</div>
<div class="text-xs text-slate-500">{{ $preview['match_by_label'] }}: {{ $row['identifier_value'] ?: '-' }}</div>
@else
<span class="text-slate-400">Tidak ditemukan</span>
@endif
</td>
<td class="px-4 py-3">
@if ($row['status'] === 'matched')
<div class="flex flex-wrap gap-2">
<span class="inline-flex rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700">Siap Diproses</span>
@if ($row['will_replace'])
<span class="inline-flex rounded-full bg-amber-100 px-3 py-1 text-xs font-semibold text-amber-700">Replace Dokumen Lama</span>
@endif
</div>
@else
<span class="inline-flex rounded-full bg-rose-100 px-3 py-1 text-xs font-semibold text-rose-700">Tidak Cocok</span>
@endif
</td>
<th class="px-4 py-4 font-semibold uppercase tracking-[0.16em]">Nama File</th>
<th class="px-4 py-4 font-semibold uppercase tracking-[0.16em]">Key Terdeteksi</th>
<th class="px-4 py-4 font-semibold uppercase tracking-[0.16em]">Siswa Lulus</th>
<th class="px-4 py-4 font-semibold uppercase tracking-[0.16em]">Status</th>
</tr>
@endforeach
</tbody>
</table>
</thead>
<tbody class="divide-y divide-slate-100 bg-white">
@foreach ($preview['rows'] as $row)
<tr class="transition hover:bg-slate-50/80">
<td class="px-4 py-4 text-slate-700">
<div class="font-semibold text-slate-800">{{ $row['original_name'] }}</div>
</td>
<td class="px-4 py-4 text-slate-600">
<span class="inline-flex rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-700">{{ $row['match_key'] ?: '-' }}</span>
</td>
<td class="px-4 py-4 text-slate-700">
@if ($row['graduation_id'])
<div class="font-semibold text-slate-900">{{ $row['student_name'] }}</div>
<div class="mt-1 text-xs text-slate-500">{{ $preview['match_by_label'] }}: {{ $row['identifier_value'] ?: '-' }}</div>
@else
<span class="inline-flex rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-500">Tidak ditemukan</span>
@endif
</td>
<td class="px-4 py-4">
@if ($row['status'] === 'matched')
<div class="flex flex-wrap gap-2">
<span class="inline-flex rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700">Siap Diproses</span>
@if ($row['will_replace'])
<span class="inline-flex rounded-full bg-amber-100 px-3 py-1 text-xs font-semibold text-amber-700">Replace Dokumen Lama</span>
@else
<span class="inline-flex rounded-full bg-sky-100 px-3 py-1 text-xs font-semibold text-sky-700">Dokumen Baru</span>
@endif
</div>
@else
<span class="inline-flex rounded-full bg-rose-100 px-3 py-1 text-xs font-semibold text-rose-700">Tidak Cocok</span>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<form method="POST" action="{{ route('graduated-students.bulk-upload.store') }}" class="mt-6 flex flex-wrap items-center gap-3">
@csrf
<input type="hidden" name="preview_token" value="{{ $preview['token'] }}">
<button type="submit" class="inline-flex items-center rounded-2xl bg-emerald-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-emerald-200 transition hover:bg-emerald-700">
Proses Bulk Upload
<button type="submit" class="inline-flex items-center gap-2 rounded-2xl bg-emerald-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-emerald-200 transition hover:bg-emerald-700">
<i class="fa-solid fa-cloud-arrow-up"></i>
<span>Proses Bulk Upload</span>
</button>
</form>
</x-master-card>
@@ -16,68 +16,108 @@
};
@endphp
<div class="grid grid-cols-1 gap-4 xl:grid-cols-3">
<div class="xl:col-span-2 rounded-[28px] border border-sky-200 bg-gradient-to-br from-sky-50 via-white to-indigo-50 px-6 py-6 shadow-sm">
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-5">
<div class="group rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-sky-50/40 p-6 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-200/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="inline-flex items-center rounded-full bg-sky-100 px-3 py-1 text-xs font-semibold uppercase tracking-[0.18em] text-sky-700">
Arsip Kelulusan
</div>
<h2 class="mt-4 text-2xl font-bold text-slate-800">Data Siswa Lulus</h2>
<p class="mt-2 max-w-2xl text-sm leading-6 text-slate-600">
Modul arsip khusus lulusan untuk menelusuri identitas akhir siswa, akademik terakhir, tanggal kelulusan, serta kelengkapan dokumen SKL dan ijazah.
</p>
<div class="text-sm font-medium text-slate-500">Total Arsip Lulus</div>
<div class="mt-3 text-3xl font-bold text-slate-900">{{ $summary['total'] }}</div>
</div>
<div class="rounded-3xl border border-white/70 bg-white/80 px-5 py-4 text-right shadow-sm backdrop-blur">
<div class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Total Lulusan</div>
<div class="mt-2 text-4xl font-bold text-sky-700">{{ $summary['total'] ?? $graduations->total() }}</div>
<div class="mt-1 text-xs text-slate-500">Arsip siswa lulus</div>
<div class="rounded-2xl bg-white/80 p-3 text-sky-600 ring-1 ring-sky-100 backdrop-blur-sm">
<i class="fa-solid fa-user-graduate text-lg"></i>
</div>
</div>
</div>
<div class="rounded-[28px] border border-emerald-200 bg-white px-6 py-6 shadow-sm">
<div class="text-xs font-semibold uppercase tracking-[0.18em] text-emerald-600">Kelengkapan Dokumen</div>
<div class="mt-4 space-y-3">
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3">
<div class="text-sm font-semibold text-slate-800">SKL tersedia</div>
<div class="mt-1 text-xs text-slate-500">{{ $summary['with_skl'] ?? 0 }} arsip</div>
<div class="group rounded-3xl border border-emerald-200 bg-gradient-to-br from-white via-emerald-50/50 to-emerald-100/60 p-6 shadow-sm shadow-emerald-100/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-emerald-200/60">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-emerald-700">SKL Tersedia</div>
<div class="mt-3 text-3xl font-bold text-emerald-800">{{ $summary['with_skl'] }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3">
<div class="text-sm font-semibold text-slate-800">Ijazah tersedia</div>
<div class="mt-1 text-xs text-slate-500">{{ $summary['with_ijazah'] ?? 0 }} arsip</div>
<div class="rounded-2xl bg-white/80 p-3 text-emerald-600 ring-1 ring-emerald-100 backdrop-blur-sm">
<i class="fa-solid fa-file-circle-check text-lg"></i>
</div>
</div>
</div>
<div class="group rounded-3xl border border-indigo-200 bg-gradient-to-br from-white via-indigo-50/50 to-indigo-100/60 p-6 shadow-sm shadow-indigo-100/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-indigo-200/60">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-indigo-700">Ijazah Tersedia</div>
<div class="mt-3 text-3xl font-bold text-indigo-800">{{ $summary['with_ijazah'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-indigo-600 ring-1 ring-indigo-100 backdrop-blur-sm">
<i class="fa-solid fa-graduation-cap text-lg"></i>
</div>
</div>
</div>
<div class="group rounded-3xl border border-fuchsia-200 bg-gradient-to-br from-white via-fuchsia-50/50 to-fuchsia-100/60 p-6 shadow-sm shadow-fuchsia-100/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-fuchsia-200/60">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-fuchsia-700">Lulus Tahun Ini</div>
<div class="mt-3 text-3xl font-bold text-fuchsia-800">{{ $summary['this_year'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-fuchsia-600 ring-1 ring-fuchsia-100 backdrop-blur-sm">
<i class="fa-solid fa-calendar-check text-lg"></i>
</div>
</div>
</div>
<div class="group rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-slate-100/70 p-6 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-200/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-slate-500">Hasil Filter</div>
<div class="mt-3 text-3xl font-bold text-slate-900">{{ $summary['filtered'] }}</div>
<div class="mt-2 text-xs text-slate-500">{{ $summary['latest_graduation_date'] ? 'Update terakhir ' . \Illuminate\Support\Carbon::parse($summary['latest_graduation_date'])->format('d M Y') : 'Belum ada tanggal lulus' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-slate-600 ring-1 ring-slate-200 backdrop-blur-sm">
<i class="fa-solid fa-filter text-lg"></i>
</div>
</div>
</div>
</div>
<x-master-card title="Filter Arsip Kelulusan" subtitle="Cari lulusan berdasarkan nama, identitas siswa, jurusan, kelas terakhir, dan tahun ajaran.">
<form method="GET" action="{{ $indexRoute }}" class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<x-form-input name="search" label="Pencarian" :value="$search" placeholder="Nama / NIS / NISN" />
<x-form-select name="academic_year_id" label="Tahun Ajaran" :options="$academicYears->all()" :value="$filters['academic_year_id']" />
<x-form-select name="department_id" label="Program Keahlian" :options="$departments->all()" :value="$filters['department_id']" />
<x-form-select name="class_id" label="Kelas Terakhir" :options="$classes->all()" :value="$filters['class_id']" />
<x-master-card title="Filter Arsip Kelulusan" subtitle="Cari lulusan berdasarkan nama, identitas siswa, jurusan, kelas terakhir, tahun ajaran, dan status dokumen.">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-sky-50/30 p-4 shadow-sm ring-1 ring-white/70">
<form method="GET" action="{{ $indexRoute }}" class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-5">
<x-form-input name="search" label="Pencarian" :value="$search" placeholder="Nama / NIS / NISN" />
<x-form-select name="academic_year_id" label="Tahun Ajaran" :options="$academicYears->all()" :value="$filters['academic_year_id']" />
<x-form-select name="department_id" label="Program Keahlian" :options="$departments->all()" :value="$filters['department_id']" />
<x-form-select name="class_id" label="Kelas Terakhir" :options="$classes->all()" :value="$filters['class_id']" />
<x-form-select name="document_status" label="Status Dokumen" :options="$documentStatusOptions" :value="$filters['document_status']" />
<input type="hidden" name="sort" value="{{ $sort }}">
<input type="hidden" name="direction" value="{{ $direction }}">
<input type="hidden" name="sort" value="{{ $sort }}">
<input type="hidden" name="direction" value="{{ $direction }}">
<div class="flex items-end gap-3 md:col-span-2 xl:col-span-4">
<button type="submit" class="rounded-2xl bg-sky-600 px-5 py-3 text-sm font-semibold text-white transition hover:bg-sky-700">Terapkan Filter</button>
<a href="{{ $indexRoute }}" class="rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">Reset</a>
</div>
</form>
<div class="flex flex-wrap items-end gap-3 md:col-span-2 xl:col-span-5">
<button type="submit" class="inline-flex items-center gap-2 rounded-2xl bg-sky-600 px-5 py-3 text-sm font-semibold text-white transition hover:bg-sky-700"><i class="fa-solid fa-filter"></i><span>Terapkan Filter</span></button>
<a href="{{ $indexRoute }}" class="inline-flex items-center gap-2 rounded-2xl border border-slate-200 bg-white px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50"><i class="fa-solid fa-rotate-left"></i><span>Reset</span></a>
<a href="{{ route('graduated-students.bulk-upload.index') }}" class="inline-flex items-center gap-2 rounded-2xl border border-indigo-200 bg-indigo-50 px-5 py-3 text-sm font-semibold text-indigo-700 transition hover:bg-indigo-100"><i class="fa-solid fa-file-arrow-up"></i><span>Bulk Upload</span></a>
</div>
</form>
</div>
</x-master-card>
<div class="rounded-[28px] border border-slate-200 bg-white shadow-sm shadow-slate-200/50">
<div class="flex flex-col gap-3 border-b border-slate-200 px-6 py-5 lg:flex-row lg:items-center lg:justify-between">
<div>
<div class="text-lg font-semibold text-slate-800">Daftar Arsip Siswa Lulus</div>
<div class="mt-1 text-sm text-slate-500">Fokus pada identitas lulusan, akademik terakhir, tanggal lulus, dan dokumen kelulusan.</div>
</div>
<div class="flex flex-col items-start gap-3 lg:items-end">
<a href="{{ route('graduated-students.bulk-upload.index') }}" class="inline-flex items-center rounded-2xl bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">Bulk Upload SKL / Ijazah</a>
<div class="text-sm text-slate-500">
Menampilkan <span class="font-semibold text-slate-800">{{ $graduations->firstItem() ?? 0 }}</span> sampai <span class="font-semibold text-slate-800">{{ $graduations->lastItem() ?? 0 }}</span> dari <span class="font-semibold text-slate-800">{{ $graduations->total() }}</span> arsip lulusan.
<div class="overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-sm shadow-slate-200/50 ring-1 ring-white/70">
<div class="border-b border-slate-200 bg-gradient-to-r from-white via-slate-50 to-sky-50/30 px-6 py-5">
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div>
<div class="text-lg font-semibold text-slate-800">Daftar Arsip Siswa Lulus</div>
<div class="mt-1 text-sm text-slate-500">Fokus pada identitas lulusan, akademik terakhir, tanggal lulus, dan dokumen kelulusan.</div>
</div>
<div class="flex flex-wrap items-center gap-2">
<div class="inline-flex items-center gap-2 rounded-full bg-slate-900 px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.16em] text-white shadow-sm">
<i class="fa-solid fa-box-archive"></i>
{{ $graduations->total() }} data terfilter
</div>
@if ($search || collect($filters)->filter()->isNotEmpty())
<div class="inline-flex items-center gap-2 rounded-full bg-sky-100 px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.16em] text-sky-700 shadow-sm">
<i class="fa-solid fa-filter-circle-xmark"></i>
Filter aktif
</div>
@endif
</div>
</div>
</div>
@@ -101,7 +141,7 @@
</thead>
<tbody class="divide-y divide-slate-100 bg-white">
@forelse ($graduations as $graduation)
<tr class="hover:bg-sky-50/40">
<tr class="transition hover:bg-sky-50/40">
<td class="px-6 py-4">
<div class="flex items-start gap-3">
<div class="flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl bg-sky-100 text-sm font-bold text-sky-700">
@@ -121,31 +161,65 @@
<td class="px-6 py-4 text-sm text-slate-600">
<div class="inline-flex rounded-full bg-sky-100 px-3 py-1 text-xs font-semibold text-sky-700">Lulus</div>
<div class="mt-2 text-xs text-slate-500">Tanggal lulus: {{ optional($graduation->graduation_date)->format('d-m-Y') ?: '-' }}</div>
<div class="mt-3 rounded-2xl border border-slate-200 bg-white px-3 py-3">
<div class="flex items-center justify-between gap-3">
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Progress Dokumen</div>
<div class="text-xs font-semibold text-slate-700">{{ $graduation->documents_available }}/{{ $graduation->documents_total }}</div>
</div>
<div class="mt-2 h-2.5 overflow-hidden rounded-full bg-slate-100">
<div class="h-full rounded-full {{ $graduation->documents_progress_percentage >= 100 ? 'bg-emerald-500' : ($graduation->documents_progress_percentage >= 50 ? 'bg-amber-400' : 'bg-rose-400') }}" style="width: {{ $graduation->documents_progress_percentage }}%"></div>
</div>
<div class="mt-2 text-[11px] text-slate-500">{{ $graduation->documents_progress_percentage }}% lengkap, {{ $graduation->documents_missing }} dokumen belum ada.</div>
</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div class="flex flex-col gap-2">
<div class="flex flex-wrap items-center gap-2">
<span class="inline-flex min-w-[88px] items-center justify-center rounded-full px-3 py-1 text-xs font-semibold {{ $graduation->skl_file_path ? 'bg-emerald-100 text-emerald-700' : 'bg-slate-100 text-slate-500' }}">SKL</span>
@unless ($graduation->skl_file_path)
<span class="text-xs text-slate-500">Belum ada</span>
@endunless
@if ($graduation->skl_file_path)
<a href="{{ route('graduated-students.preview-document', [$graduation, 'skl']) }}" class="inline-flex items-center rounded-full border border-sky-200 bg-sky-50 px-3 py-1 text-xs font-semibold text-sky-700 transition hover:border-sky-300 hover:bg-sky-100 hover:text-sky-800">Preview</a>
@endif
</div>
<div class="flex flex-wrap items-center gap-2">
<span class="inline-flex min-w-[88px] items-center justify-center rounded-full px-3 py-1 text-xs font-semibold {{ $graduation->ijazah_file_path ? 'bg-emerald-100 text-emerald-700' : 'bg-slate-100 text-slate-500' }}">Ijazah</span>
@unless ($graduation->ijazah_file_path)
<span class="text-xs text-slate-500">Belum ada</span>
@endunless
@if ($graduation->ijazah_file_path)
<a href="{{ route('graduated-students.preview-document', [$graduation, 'ijazah']) }}" class="inline-flex items-center rounded-full border border-sky-200 bg-sky-50 px-3 py-1 text-xs font-semibold text-sky-700 transition hover:border-sky-300 hover:bg-sky-100 hover:text-sky-800">Preview</a>
@endif
</div>
@php
$documentGroups = [
[
'title' => 'Akademik',
'badgeClass' => 'bg-sky-100 text-sky-700',
'items' => [
['label' => 'SKL', 'path' => 'skl_file_path', 'route' => 'skl', 'icon' => 'fa-solid fa-file-lines'],
['label' => 'Ijazah', 'path' => 'ijazah_file_path', 'route' => 'ijazah', 'icon' => 'fa-solid fa-graduation-cap'],
['label' => 'Transkrip', 'path' => 'transcript_file_path', 'route' => 'transcript', 'icon' => 'fa-solid fa-table-list'],
],
],
[
'title' => 'Sertifikasi',
'badgeClass' => 'bg-violet-100 text-violet-700',
'items' => [
['label' => 'Sertifikat UKK', 'path' => 'ukk_certificate_file_path', 'route' => 'ukk-certificate', 'icon' => 'fa-solid fa-award'],
['label' => 'Sertifikat LSP', 'path' => 'lsp_certificate_file_path', 'route' => 'lsp-certificate', 'icon' => 'fa-solid fa-certificate'],
],
],
];
@endphp
<div class="space-y-3">
@foreach ($documentGroups as $group)
<div class="rounded-2xl border border-slate-200 bg-slate-50/70 px-3 py-3">
<div class="mb-2 flex items-center justify-between gap-2">
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">{{ $group['title'] }}</div>
<span class="inline-flex rounded-full px-2.5 py-1 text-[11px] font-semibold shadow-sm {{ $group['badgeClass'] }}">{{ $group['title'] }}</span>
</div>
<div class="flex flex-wrap gap-2">
@foreach ($group['items'] as $item)
@php $hasFile = filled($graduation->{$item['path']}); @endphp
<div class="flex flex-wrap items-center gap-2 rounded-full border px-2.5 py-1.5 transition hover:-translate-y-0.5 {{ $hasFile ? 'border-emerald-200 bg-emerald-50 shadow-sm shadow-emerald-50' : 'border-slate-200 bg-white hover:border-slate-300' }}">
<span class="inline-flex items-center gap-1.5 text-xs font-semibold {{ $hasFile ? 'text-emerald-700' : 'text-slate-500' }}"><i class="{{ $item['icon'] }} text-[11px]"></i>{{ $item['label'] }}</span>
@if ($hasFile)
<a href="{{ route('graduated-students.preview-document', [$graduation, $item['route']]) }}" class="inline-flex items-center rounded-full border border-sky-200 bg-sky-50 px-2.5 py-1 text-[11px] font-semibold text-sky-700 transition hover:border-sky-300 hover:bg-sky-100 hover:text-sky-800">Preview</a>
@else
<span class="text-[11px] text-slate-400">Belum ada</span>
@endif
</div>
@endforeach
</div>
</div>
@endforeach
</div>
</td>
<td class="px-6 py-4">
<a href="{{ route('graduated-students.show', $graduation) }}" class="rounded-xl border border-sky-200 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-50">Detail Arsip</a>
<a href="{{ route('graduated-students.show', $graduation) }}" class="inline-flex items-center gap-2 rounded-xl border border-sky-200 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-50"><i class="fa-solid fa-eye"></i><span>Detail Arsip</span></a>
</td>
</tr>
@empty
@@ -158,12 +232,7 @@
</div>
</div>
<div class="flex flex-col gap-3 rounded-2xl border border-slate-200 bg-white px-5 py-4 shadow-sm lg:flex-row lg:items-center lg:justify-between">
<div class="text-sm text-slate-600">
Arsip lulusan dipisahkan agar pengelolaan dokumen kelulusan lebih rapi dan tidak bercampur dengan data siswa aktif.
</div>
<div>
{{ $graduations->onEachSide(1)->links() }}
</div>
<div class="mt-6 flex justify-end">
{{ $graduations->onEachSide(1)->links() }}
</div>
</x-master-page>
@@ -1,43 +1,173 @@
<x-master-page :title="'Preview Dokumen ' . $documentLabel" description="Lihat dokumen siswa lulus langsung di browser bila format mendukung.">
<div class="grid grid-cols-1 gap-6 xl:grid-cols-3">
<x-master-card :title="'Informasi ' . $documentLabel" subtitle="Metadata dokumen siswa lulus.">
<div class="space-y-4 text-sm text-slate-600">
<div><span class="font-semibold text-slate-800">Nama Siswa:</span> {{ $graduation->full_name }}</div>
<div><span class="font-semibold text-slate-800">Jenis Dokumen:</span> {{ $documentLabel }}</div>
<div><span class="font-semibold text-slate-800">Nama File:</span> {{ $fileName ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Tipe MIME:</span> {{ $mimeType ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Ukuran:</span> {{ ($documentType === 'skl' ? $graduation->skl_file_size : $graduation->ijazah_file_size) ? number_format((($documentType === 'skl' ? $graduation->skl_file_size : $graduation->ijazah_file_size) / 1024), 1) . ' KB' : '-' }}</div>
<div><span class="font-semibold text-slate-800">Tanggal Lulus:</span> {{ $graduation->graduation_date?->format('d-m-Y') ?: '-' }}</div>
@if ($graduation->graduation_note)
<div class="rounded-2xl bg-slate-50 px-4 py-3 text-xs text-slate-500">
<span class="font-semibold text-slate-700">Catatan Kelulusan:</span> {{ $graduation->graduation_note }}
</div>
@endif
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-sky-50/40 p-6 shadow-sm shadow-slate-200/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-slate-500">Nama Siswa</div>
<div class="mt-3 text-lg font-bold text-slate-900">{{ $graduation->full_name }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-sky-600 ring-1 ring-sky-100 backdrop-blur-sm">
<i class="fa-solid fa-user-graduate text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-sky-200 bg-gradient-to-br from-white via-sky-50/50 to-sky-100/60 p-6 shadow-sm shadow-sky-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-sky-700">Format File</div>
<div class="mt-3 text-lg font-bold text-sky-800">{{ strtoupper(pathinfo($fileName ?: '', PATHINFO_EXTENSION) ?: 'FILE') }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-sky-600 ring-1 ring-sky-100 backdrop-blur-sm">
<i class="fa-solid fa-file-lines text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-emerald-200 bg-gradient-to-br from-white via-emerald-50/50 to-emerald-100/60 p-6 shadow-sm shadow-emerald-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-emerald-700">Ukuran File</div>
<div class="mt-3 text-lg font-bold text-emerald-800">{{ match($documentType) {
'skl' => $graduation->skl_file_size,
'ijazah' => $graduation->ijazah_file_size,
'transcript' => $graduation->transcript_file_size,
'ukk-certificate' => $graduation->ukk_certificate_file_size,
'lsp-certificate' => $graduation->lsp_certificate_file_size,
default => null,
} ? number_format((match($documentType) {
'skl' => $graduation->skl_file_size,
'ijazah' => $graduation->ijazah_file_size,
'transcript' => $graduation->transcript_file_size,
'ukk-certificate' => $graduation->ukk_certificate_file_size,
'lsp-certificate' => $graduation->lsp_certificate_file_size,
default => null,
} / 1024), 1) . ' KB' : '-' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-emerald-600 ring-1 ring-emerald-100 backdrop-blur-sm">
<i class="fa-solid fa-weight-scale text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-violet-200 bg-gradient-to-br from-white via-violet-50/50 to-violet-100/60 p-6 shadow-sm shadow-violet-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-violet-700">Tanggal Lulus</div>
<div class="mt-3 text-lg font-bold text-violet-800">{{ $graduation->graduation_date?->format('d M Y') ?: 'Belum diisi' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-violet-600 ring-1 ring-violet-100 backdrop-blur-sm">
<i class="fa-solid fa-calendar-check text-lg"></i>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 gap-6 xl:grid-cols-[380px_minmax(0,1fr)]">
<x-master-card :title="'Informasi ' . $documentLabel" subtitle="Metadata file dan ringkasan arsip kelulusan.">
<div class="mb-4 flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex h-11 w-11 items-center justify-center rounded-2xl bg-sky-100 text-sky-600">
<i class="fa-solid fa-folder-open text-lg"></i>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Metadata Dokumen</div>
<div class="mt-1 text-sm font-semibold text-slate-800">Informasi cepat sebelum file dibuka atau diunduh</div>
</div>
</div>
<div class="space-y-3 text-sm text-slate-600">
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-user"></i><span>Nama Siswa</span></div>
<div class="mt-2 font-semibold text-slate-800">{{ $graduation->full_name }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-folder-tree"></i><span>Jenis Dokumen</span></div>
<div class="mt-2 font-semibold text-slate-800">{{ $documentLabel }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-file-signature"></i><span>Nama File</span></div>
<div class="mt-2 break-words font-semibold text-slate-800">{{ $fileName ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-code"></i><span>Tipe MIME</span></div>
<div class="mt-2 text-slate-700">{{ $mimeType ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-weight-scale"></i><span>Ukuran File</span></div>
<div class="mt-2 text-slate-700">{{ match($documentType) {
'skl' => $graduation->skl_file_size,
'ijazah' => $graduation->ijazah_file_size,
'transcript' => $graduation->transcript_file_size,
'ukk-certificate' => $graduation->ukk_certificate_file_size,
'lsp-certificate' => $graduation->lsp_certificate_file_size,
default => null,
} ? number_format((match($documentType) {
'skl' => $graduation->skl_file_size,
'ijazah' => $graduation->ijazah_file_size,
'transcript' => $graduation->transcript_file_size,
'ukk-certificate' => $graduation->ukk_certificate_file_size,
'lsp-certificate' => $graduation->lsp_certificate_file_size,
default => null,
} / 1024), 1) . ' KB' : '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-calendar-days"></i><span>Tanggal Lulus</span></div>
<div class="mt-2 text-slate-700">{{ $graduation->graduation_date?->format('d-m-Y') ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-align-left"></i><span>Catatan Kelulusan</span></div>
<div class="mt-2 leading-6 text-slate-700">{{ $graduation->graduation_note ?: '-' }}</div>
</div>
</div>
<div class="mt-6 flex flex-wrap gap-3">
<a href="{{ $fileUrl }}" target="_blank" rel="noopener noreferrer" class="rounded-2xl bg-sky-600 px-4 py-3 text-sm font-semibold text-white transition hover:bg-sky-700">Buka File Asli</a>
<a href="{{ route('graduated-students.index') }}" class="rounded-2xl border border-slate-200 px-4 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">Kembali ke Arsip</a>
<a href="{{ $fileUrl }}" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 rounded-2xl bg-sky-600 px-4 py-3 text-sm font-semibold text-white transition hover:bg-sky-700">
<i class="fa-solid fa-up-right-from-square"></i>
<span>Buka File Asli</span>
</a>
<a href="{{ route('graduated-students.show', $graduation) }}" class="inline-flex items-center gap-2 rounded-2xl border border-slate-200 px-4 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">
<i class="fa-solid fa-arrow-left"></i>
<span>Kembali ke Detail</span>
</a>
</div>
</x-master-card>
<div class="xl:col-span-2">
<x-master-card :title="'Tampilan ' . $documentLabel" subtitle="Preview langsung untuk gambar dan PDF.">
@if ($isImage)
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-slate-50 p-4">
<img src="{{ $fileUrl }}" alt="{{ $fileName ?: $documentLabel }}" class="mx-auto max-h-[70vh] rounded-xl object-contain shadow-sm">
<x-master-card :title="'Tampilan ' . $documentLabel" subtitle="Preview langsung untuk gambar dan PDF.">
<div class="mb-4 flex items-center justify-between gap-3 rounded-2xl border border-slate-200 bg-gradient-to-r from-white via-slate-50 to-sky-50/30 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Mode Preview</div>
<div class="mt-1 text-sm font-semibold text-slate-800">
@if ($isImage)
Gambar langsung di browser
@elseif ($isPdf)
PDF embed di browser
@else
Preview tidak didukung
@endif
</div>
@elseif ($isPdf)
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-slate-50">
<iframe src="{{ $fileUrl }}" class="h-[75vh] w-full rounded-2xl" title="Preview {{ $documentLabel }}"></iframe>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-sky-600 ring-1 ring-sky-100 backdrop-blur-sm">
<i class="fa-solid {{ $isImage ? 'fa-image' : ($isPdf ? 'fa-file-pdf' : 'fa-circle-info') }} text-lg"></i>
</div>
</div>
@if ($isImage)
<div class="overflow-hidden rounded-3xl border border-slate-200 bg-slate-50 p-4 shadow-sm ring-1 ring-white/70">
<img src="{{ $fileUrl }}" alt="{{ $fileName ?: $documentLabel }}" class="mx-auto max-h-[72vh] rounded-2xl object-contain shadow-sm">
</div>
@elseif ($isPdf)
<div class="overflow-hidden rounded-3xl border border-slate-200 bg-slate-50 shadow-sm ring-1 ring-white/70">
<iframe src="{{ $fileUrl }}" class="h-[78vh] w-full rounded-3xl" title="Preview {{ $documentLabel }}"></iframe>
</div>
@else
<div class="rounded-3xl border border-dashed border-slate-300 bg-slate-50 p-10 text-center shadow-sm ring-1 ring-white/70">
<div class="mx-auto flex h-14 w-14 items-center justify-center rounded-2xl bg-white text-slate-500 shadow-sm ring-1 ring-slate-200">
<i class="fa-solid fa-file-circle-question text-xl"></i>
</div>
@else
<div class="rounded-2xl border border-dashed border-slate-300 bg-slate-50 p-10 text-center">
<div class="text-lg font-semibold text-slate-800">Preview tidak tersedia</div>
<p class="mt-2 text-sm text-slate-500">Format file ini belum didukung untuk preview langsung di browser. Silakan gunakan tombol buka file asli.</p>
</div>
@endif
</x-master-card>
</div>
<div class="mt-4 text-lg font-semibold text-slate-800">Preview tidak tersedia</div>
<p class="mt-2 text-sm text-slate-500">Format file ini belum didukung untuk preview langsung di browser. Gunakan tombol buka file asli untuk melihat dokumen.</p>
</div>
@endif
</x-master-card>
</div>
</x-master-page>
+245 -68
View File
@@ -1,97 +1,274 @@
<x-master-page title="Detail Arsip Siswa Lulus" description="Lihat ringkasan arsip lulusan dan kelola dokumen SKL maupun ijazah tanpa bercampur dengan siswa aktif.">
<div class="grid grid-cols-1 gap-6 xl:grid-cols-3">
<div class="xl:col-span-2 space-y-6">
<x-master-page title="Detail Arsip Siswa Lulus" description="Lihat ringkasan arsip lulusan dan kelola dokumen kelulusan tanpa bercampur dengan siswa aktif.">
@php
$documentGroups = [
[
'title' => 'Dokumen Akademik Utama',
'badge' => 'Akademik',
'badgeClass' => 'bg-sky-100 text-sky-700',
'items' => [
['label' => 'SKL', 'path' => 'skl_file_path', 'name' => 'skl_file_name', 'route' => 'skl', 'icon' => 'fa-solid fa-file-lines'],
['label' => 'Ijazah', 'path' => 'ijazah_file_path', 'name' => 'ijazah_file_name', 'route' => 'ijazah', 'icon' => 'fa-solid fa-graduation-cap'],
['label' => 'Transkrip Nilai', 'path' => 'transcript_file_path', 'name' => 'transcript_file_name', 'route' => 'transcript', 'icon' => 'fa-solid fa-table-list'],
],
],
[
'title' => 'Dokumen Sertifikasi',
'badge' => 'Sertifikasi',
'badgeClass' => 'bg-violet-100 text-violet-700',
'items' => [
['label' => 'Sertifikat UKK', 'path' => 'ukk_certificate_file_path', 'name' => 'ukk_certificate_file_name', 'route' => 'ukk-certificate', 'icon' => 'fa-solid fa-award'],
['label' => 'Sertifikat LSP', 'path' => 'lsp_certificate_file_path', 'name' => 'lsp_certificate_file_name', 'route' => 'lsp-certificate', 'icon' => 'fa-solid fa-certificate'],
],
],
];
$allDocuments = collect($documentGroups)->flatMap(fn ($group) => $group['items']);
$availableDocuments = $allDocuments->filter(fn ($item) => filled($graduation->{$item['path']}))->count();
$totalDocuments = $allDocuments->count();
$missingDocuments = max($totalDocuments - $availableDocuments, 0);
@endphp
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-sky-50/40 p-6 shadow-sm shadow-slate-200/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-slate-500">Status Arsip</div>
<div class="mt-3 text-lg font-bold text-slate-900">Siswa Lulus</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-sky-600 ring-1 ring-sky-100 backdrop-blur-sm">
<i class="fa-solid fa-user-graduate text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-sky-200 bg-gradient-to-br from-white via-sky-50/50 to-sky-100/60 p-6 shadow-sm shadow-sky-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-sky-700">Tanggal Lulus</div>
<div class="mt-3 text-lg font-bold text-sky-800">{{ $graduation->graduation_date?->format('d M Y') ?: 'Belum diisi' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-sky-600 ring-1 ring-sky-100 backdrop-blur-sm">
<i class="fa-solid fa-calendar-check text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border {{ $availableDocuments === $totalDocuments && $totalDocuments > 0 ? 'border-emerald-200 bg-gradient-to-br from-white via-emerald-50/50 to-emerald-100/60' : 'border-amber-200 bg-gradient-to-br from-white via-amber-50/50 to-amber-100/60' }} p-6 shadow-sm {{ $availableDocuments === $totalDocuments && $totalDocuments > 0 ? 'shadow-emerald-100/50' : 'shadow-amber-100/50' }} ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium {{ $availableDocuments === $totalDocuments && $totalDocuments > 0 ? 'text-emerald-700' : 'text-amber-700' }}">Kelengkapan Dokumen</div>
<div class="mt-3 text-lg font-bold {{ $availableDocuments === $totalDocuments && $totalDocuments > 0 ? 'text-emerald-800' : 'text-amber-800' }}">{{ $availableDocuments }}/{{ $totalDocuments }} tersedia</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 {{ $availableDocuments === $totalDocuments && $totalDocuments > 0 ? 'text-emerald-600 ring-1 ring-emerald-100' : 'text-amber-600 ring-1 ring-amber-100' }} backdrop-blur-sm">
<i class="fa-solid {{ $availableDocuments === $totalDocuments && $totalDocuments > 0 ? 'fa-file-circle-check' : 'fa-file-circle-exclamation' }} text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-violet-200 bg-gradient-to-br from-white via-violet-50/50 to-violet-100/60 p-6 shadow-sm shadow-violet-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-violet-700">Kelas Terakhir</div>
<div class="mt-3 text-lg font-bold text-violet-800">{{ $graduation->classRoom?->name ?: '-' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-violet-600 ring-1 ring-violet-100 backdrop-blur-sm">
<i class="fa-solid fa-school text-lg"></i>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 gap-6 xl:grid-cols-[1.05fr_0.95fr]">
<div class="space-y-6">
<x-master-card title="Identitas Siswa" subtitle="Snapshot data terakhir saat siswa dipindahkan ke arsip lulusan.">
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Nama Lengkap</div>
<div class="mt-1 text-sm font-semibold text-slate-900">{{ $graduation->full_name }}</div>
<div class="mb-4 flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex h-11 w-11 items-center justify-center rounded-2xl bg-sky-100 text-sky-600">
<i class="fa-solid fa-id-card text-lg"></i>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Status Arsip</div>
<div class="mt-1"><span class="inline-flex rounded-full bg-sky-100 px-3 py-1 text-xs font-semibold text-sky-700">Siswa Lulus</span></div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Snapshot Identitas</div>
<div class="mt-1 text-sm font-semibold text-slate-800">Identitas dasar siswa saat masuk arsip lulusan</div>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">NIS</div>
<div class="mt-1 text-sm text-slate-700">{{ $graduation->student_number ?: '-' }}</div>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-user"></i><span>Nama Lengkap</span></div>
<div class="mt-2 text-sm font-semibold text-slate-900">{{ $graduation->full_name }}</div>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">NISN</div>
<div class="mt-1 text-sm text-slate-700">{{ $graduation->nisn ?: '-' }}</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-badge-check"></i><span>Status Arsip</span></div>
<div class="mt-2"><span class="inline-flex items-center gap-2 rounded-full bg-sky-100 px-3 py-1 text-xs font-semibold text-sky-700"><i class="fa-solid fa-user-graduate"></i><span>Siswa Lulus</span></span></div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-hashtag"></i><span>NIS</span></div>
<div class="mt-2 text-sm text-slate-700">{{ $graduation->student_number ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-fingerprint"></i><span>NISN</span></div>
<div class="mt-2 text-sm text-slate-700">{{ $graduation->nisn ?: '-' }}</div>
</div>
</div>
</x-master-card>
<x-master-card title="Akademik Terakhir" subtitle="Data akademik terakhir sebelum siswa dinyatakan lulus.">
<div class="grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-4">
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Tahun Angkatan</div>
<div class="mt-1 text-sm text-slate-700">{{ $graduation->academicYear?->name ?: '-' }}</div>
<div class="mb-4 flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex h-11 w-11 items-center justify-center rounded-2xl bg-amber-100 text-amber-600">
<i class="fa-solid fa-graduation-cap text-lg"></i>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Program Keahlian</div>
<div class="mt-1 text-sm text-slate-700">{{ $graduation->department?->name ?: '-' }}</div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Posisi Akademik Terakhir</div>
<div class="mt-1 text-sm font-semibold text-slate-800">Rombel dan relasi akademik sebelum siswa lulus dari sekolah</div>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Kelas Terakhir</div>
<div class="mt-1 text-sm text-slate-700">{{ $graduation->classRoom?->name ?: '-' }}</div>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-calendar-days"></i><span>Tahun Angkatan</span></div>
<div class="mt-2 text-sm text-slate-700">{{ $graduation->academicYear?->name ?: '-' }}</div>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Tanggal Masuk</div>
<div class="mt-1 text-sm text-slate-700">{{ $graduation->entry_date?->format('d-m-Y') ?: '-' }}</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-sitemap"></i><span>Program Keahlian</span></div>
<div class="mt-2 text-sm text-slate-700">{{ $graduation->department?->name ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-school"></i><span>Kelas Terakhir</span></div>
<div class="mt-2 text-sm text-slate-700">{{ $graduation->classRoom?->name ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-door-open"></i><span>Tanggal Masuk</span></div>
<div class="mt-2 text-sm text-slate-700">{{ $graduation->entry_date?->format('d-m-Y') ?: '-' }}</div>
</div>
</div>
</x-master-card>
</div>
<div class="space-y-6">
<x-master-card title="Dokumen Kelulusan" subtitle="Upload atau ganti SKL dan ijazah langsung dari modul arsip lulusan.">
<div class="mb-4">
<a href="{{ route('graduated-students.bulk-upload.index') }}" class="inline-flex items-center rounded-2xl border border-indigo-200 bg-indigo-50 px-4 py-2.5 text-sm font-semibold text-indigo-700 transition hover:bg-indigo-100">
Bulk Upload SKL / Ijazah
</a>
<x-master-card title="Dokumen Kelulusan" subtitle="Ringkasan status dokumen lulusan yang sudah tersimpan di arsip.">
<div class="mb-4 rounded-2xl border {{ $missingDocuments === 0 && $totalDocuments > 0 ? 'border-emerald-200 bg-gradient-to-r from-emerald-50 to-white' : 'border-amber-200 bg-gradient-to-r from-amber-50 to-white' }} px-4 py-4 shadow-sm {{ $missingDocuments === 0 && $totalDocuments > 0 ? 'shadow-emerald-100/40' : 'shadow-amber-100/40' }}">
<div class="flex items-start gap-3">
<div class="flex h-10 w-10 items-center justify-center rounded-2xl bg-white {{ $missingDocuments === 0 && $totalDocuments > 0 ? 'text-emerald-600 ring-1 ring-emerald-100' : 'text-amber-600 ring-1 ring-amber-100' }} shadow-sm">
<i class="fa-solid {{ $missingDocuments === 0 && $totalDocuments > 0 ? 'fa-folder-open' : 'fa-triangle-exclamation' }}"></i>
</div>
<div>
<div class="text-sm font-semibold {{ $missingDocuments === 0 && $totalDocuments > 0 ? 'text-emerald-700' : 'text-amber-700' }}">
{{ $missingDocuments === 0 && $totalDocuments > 0 ? 'Dokumen kelulusan sudah lengkap' : 'Masih ada dokumen yang perlu dilengkapi' }}
</div>
<div class="mt-1 text-xs {{ $missingDocuments === 0 && $totalDocuments > 0 ? 'text-emerald-600' : 'text-amber-600' }}">
{{ $availableDocuments }} dari {{ $totalDocuments }} dokumen tersedia{{ $missingDocuments > 0 ? ', sisa ' . $missingDocuments . ' dokumen belum ada.' : '.' }}
</div>
</div>
</div>
</div>
<div class="space-y-4">
<div class="rounded-2xl border {{ $graduation->skl_file_path ? 'border-emerald-200 bg-emerald-50' : 'border-amber-200 bg-amber-50' }} px-4 py-4">
<div class="text-sm font-semibold {{ $graduation->skl_file_path ? 'text-emerald-700' : 'text-amber-700' }}">SKL {{ $graduation->skl_file_path ? 'sudah tersedia' : 'belum diupload' }}</div>
@if ($graduation->skl_file_name)
<div class="mt-1 text-xs {{ $graduation->skl_file_path ? 'text-emerald-600' : 'text-amber-600' }}">{{ $graduation->skl_file_name }}</div>
@endif
<div class="grid grid-cols-1 gap-4 xl:grid-cols-2">
@foreach ($documentGroups as $group)
<div class="rounded-3xl border border-slate-200 bg-slate-50/70 p-4">
<div class="mb-3 flex items-center justify-between gap-3">
<div>
<div class="text-sm font-semibold text-slate-800">{{ $group['title'] }}</div>
<div class="text-xs text-slate-500">Status arsip dokumen pada kategori ini.</div>
</div>
<span class="inline-flex rounded-full px-3 py-1 text-xs font-semibold {{ $group['badgeClass'] }}">{{ $group['badge'] }}</span>
</div>
<div class="grid grid-cols-1 gap-3">
@foreach ($group['items'] as $item)
@php
$hasFile = filled($graduation->{$item['path']});
@endphp
<div class="rounded-2xl border {{ $hasFile ? 'border-emerald-200 bg-emerald-50 shadow-sm shadow-emerald-50' : 'border-amber-200 bg-amber-50' }} px-4 py-4 transition hover:-translate-y-0.5">
<div class="flex items-start justify-between gap-3">
<div class="min-w-0">
<div class="inline-flex items-center gap-2 text-sm font-semibold {{ $hasFile ? 'text-emerald-700' : 'text-amber-700' }}"><i class="{{ $item['icon'] }} w-4 text-center"></i><span>{{ $item['label'] }} {{ $hasFile ? 'sudah tersedia' : 'belum diupload' }}</span></div>
@if ($graduation->{$item['name']})
<div class="mt-1 break-all text-xs {{ $hasFile ? 'text-emerald-600' : 'text-amber-600' }}">{{ $graduation->{$item['name']} }}</div>
@endif
</div>
@if ($hasFile)
<a href="{{ route('graduated-students.preview-document', [$graduation, $item['route']]) }}" class="inline-flex items-center gap-2 rounded-xl border border-sky-200 bg-sky-50 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-100">
<i class="fa-solid fa-eye"></i>
<span>Preview</span>
</a>
@endif
</div>
</div>
@endforeach
</div>
</div>
@endforeach
</div>
</x-master-card>
<x-master-card title="Form Update Arsip Kelulusan" subtitle="Kelola tanggal lulus, catatan, dan upload dokumen dalam panel terpisah agar layout lebih proporsional.">
<form method="POST" action="{{ route('graduated-students.update', $graduation) }}" enctype="multipart/form-data" class="space-y-5">
@csrf
@method('PATCH')
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-sky-50/20 p-4 shadow-sm ring-1 ring-white/70">
<div class="grid grid-cols-1 gap-4">
<x-form-input name="graduation_date" label="Tanggal Lulus" type="date" :value="optional($graduation->graduation_date)->format('Y-m-d')" />
<x-form-textarea name="graduation_note" label="Catatan Kelulusan" :value="$graduation->graduation_note" rows="4" placeholder="Masukkan catatan tambahan bila perlu" />
</div>
</div>
<div class="rounded-2xl border {{ $graduation->ijazah_file_path ? 'border-emerald-200 bg-emerald-50' : 'border-amber-200 bg-amber-50' }} px-4 py-4">
<div class="text-sm font-semibold {{ $graduation->ijazah_file_path ? 'text-emerald-700' : 'text-amber-700' }}">Ijazah {{ $graduation->ijazah_file_path ? 'sudah tersedia' : 'belum diupload' }}</div>
@if ($graduation->ijazah_file_name)
<div class="mt-1 text-xs {{ $graduation->ijazah_file_path ? 'text-emerald-600' : 'text-amber-600' }}">{{ $graduation->ijazah_file_name }}</div>
@endif
<div class="grid grid-cols-1 gap-5 xl:grid-cols-2">
<div class="rounded-3xl border border-slate-200 bg-slate-50/70 p-4">
<div class="mb-3 flex items-center justify-between gap-3">
<div>
<div class="text-sm font-semibold text-slate-800">Upload Dokumen Akademik</div>
<div class="text-xs text-slate-500">Untuk arsip kelulusan inti dan dokumen nilai.</div>
</div>
<span class="inline-flex rounded-full bg-sky-100 px-3 py-1 text-xs font-semibold text-sky-700 shadow-sm">Akademik</span>
</div>
<div class="space-y-4">
<div>
<label for="skl_file" class="mb-2 block text-sm font-medium text-slate-700">Upload / Ganti SKL</label>
<input id="skl_file" name="skl_file" type="file" accept=".pdf,.jpg,.jpeg,.png" class="block w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-700 file:mr-4 file:rounded-xl file:border-0 file:bg-slate-100 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-slate-700 hover:file:bg-slate-200">
<p class="mt-2 text-xs text-slate-500">Format: PDF, JPG, JPEG, PNG. Maksimal 4 MB.</p>
<x-input-error :messages="$errors->get('skl_file')" class="mt-2" />
</div>
<div>
<label for="ijazah_file" class="mb-2 block text-sm font-medium text-slate-700">Upload / Ganti Ijazah</label>
<input id="ijazah_file" name="ijazah_file" type="file" accept=".pdf,.jpg,.jpeg,.png" class="block w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-700 file:mr-4 file:rounded-xl file:border-0 file:bg-slate-100 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-slate-700 hover:file:bg-slate-200">
<p class="mt-2 text-xs text-slate-500">Format: PDF, JPG, JPEG, PNG. Maksimal 4 MB.</p>
<x-input-error :messages="$errors->get('ijazah_file')" class="mt-2" />
</div>
<div>
<label for="transcript_file" class="mb-2 block text-sm font-medium text-slate-700">Upload / Ganti Transkrip Nilai</label>
<input id="transcript_file" name="transcript_file" type="file" accept=".pdf,.jpg,.jpeg,.png" class="block w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-700 file:mr-4 file:rounded-xl file:border-0 file:bg-slate-100 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-slate-700 hover:file:bg-slate-200">
<p class="mt-2 text-xs text-slate-500">Format: PDF, JPG, JPEG, PNG. Maksimal 4 MB.</p>
<x-input-error :messages="$errors->get('transcript_file')" class="mt-2" />
</div>
</div>
</div>
<div class="rounded-3xl border border-slate-200 bg-slate-50/70 p-4">
<div class="mb-3 flex items-center justify-between gap-3">
<div>
<div class="text-sm font-semibold text-slate-800">Upload Dokumen Sertifikasi</div>
<div class="text-xs text-slate-500">Untuk bukti kompetensi dan sertifikasi lulusan.</div>
</div>
<span class="inline-flex rounded-full bg-violet-100 px-3 py-1 text-xs font-semibold text-violet-700 shadow-sm">Sertifikasi</span>
</div>
<div class="space-y-4">
<div>
<label for="ukk_certificate_file" class="mb-2 block text-sm font-medium text-slate-700">Upload / Ganti Sertifikat UKK</label>
<input id="ukk_certificate_file" name="ukk_certificate_file" type="file" accept=".pdf,.jpg,.jpeg,.png" class="block w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-700 file:mr-4 file:rounded-xl file:border-0 file:bg-slate-100 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-slate-700 hover:file:bg-slate-200">
<p class="mt-2 text-xs text-slate-500">Format: PDF, JPG, JPEG, PNG. Maksimal 4 MB.</p>
<x-input-error :messages="$errors->get('ukk_certificate_file')" class="mt-2" />
</div>
<div>
<label for="lsp_certificate_file" class="mb-2 block text-sm font-medium text-slate-700">Upload / Ganti Sertifikat LSP</label>
<input id="lsp_certificate_file" name="lsp_certificate_file" type="file" accept=".pdf,.jpg,.jpeg,.png" class="block w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-700 file:mr-4 file:rounded-xl file:border-0 file:bg-slate-100 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-slate-700 hover:file:bg-slate-200">
<p class="mt-2 text-xs text-slate-500">Format: PDF, JPG, JPEG, PNG. Maksimal 4 MB.</p>
<x-input-error :messages="$errors->get('lsp_certificate_file')" class="mt-2" />
</div>
</div>
</div>
</div>
<form method="POST" action="{{ route('graduated-students.update', $graduation) }}" enctype="multipart/form-data" class="space-y-4">
@csrf
@method('PATCH')
<x-form-input name="graduation_date" label="Tanggal Lulus" type="date" :value="optional($graduation->graduation_date)->format('Y-m-d')" />
<x-form-textarea name="graduation_note" label="Catatan Kelulusan" :value="$graduation->graduation_note" rows="4" placeholder="Masukkan catatan tambahan bila perlu" />
<div>
<label for="skl_file" class="mb-2 block text-sm font-medium text-slate-700">Upload / Ganti SKL</label>
<input id="skl_file" name="skl_file" type="file" accept=".pdf,.jpg,.jpeg,.png" class="block w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-700 file:mr-4 file:rounded-xl file:border-0 file:bg-slate-100 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-slate-700 hover:file:bg-slate-200">
<p class="mt-2 text-xs text-slate-500">Format: PDF, JPG, JPEG, PNG. Maksimal 4 MB.</p>
<x-input-error :messages="$errors->get('skl_file')" class="mt-2" />
</div>
<div>
<label for="ijazah_file" class="mb-2 block text-sm font-medium text-slate-700">Upload / Ganti Ijazah</label>
<input id="ijazah_file" name="ijazah_file" type="file" accept=".pdf,.jpg,.jpeg,.png" class="block w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-700 file:mr-4 file:rounded-xl file:border-0 file:bg-slate-100 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-slate-700 hover:file:bg-slate-200">
<p class="mt-2 text-xs text-slate-500">Format: PDF, JPG, JPEG, PNG. Maksimal 4 MB.</p>
<x-input-error :messages="$errors->get('ijazah_file')" class="mt-2" />
</div>
<button type="submit" class="inline-flex items-center rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
Simpan Arsip Kelulusan
<div class="flex justify-end">
<button type="submit" class="inline-flex items-center gap-2 rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
<i class="fa-solid fa-floppy-disk"></i>
<span>Simpan Arsip Kelulusan</span>
</button>
</form>
</div>
</div>
</form>
</x-master-card>
</div>
</div>
+1
View File
@@ -24,6 +24,7 @@
<!-- Scripts -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
@stack('styles')
</head>
<body class="bg-slate-50 font-sans antialiased text-slate-900">
@include('sweetalert::alert')
+43 -91
View File
@@ -64,11 +64,9 @@
<nav class="space-y-2">
<a href="{{ route('dashboard') }}"
class="flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('dashboard') ? 'bg-indigo-50 text-indigo-700' : 'text-slate-600 hover:bg-slate-100 hover:text-slate-900' }}">
<span class="inline-flex h-9 w-9 items-center justify-center rounded-lg {{ request()->routeIs('dashboard') ? 'bg-indigo-100 text-indigo-700' : 'bg-slate-100 text-slate-500' }}">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M3 10.5L12 3l9 7.5V21a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1v-10.5Z" />
</svg>
class="group flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('dashboard') ? 'bg-indigo-50 text-indigo-700' : 'text-slate-600 hover:bg-slate-100 hover:text-slate-900' }}">
<span class="inline-flex h-9 w-9 items-center justify-center rounded-lg {{ request()->routeIs('dashboard') ? 'bg-indigo-100 text-indigo-700 shadow-sm shadow-indigo-100' : 'bg-slate-100 text-indigo-500 group-hover:text-indigo-600' }}">
<i class="fa-solid fa-house text-base"></i>
</span>
<span>Dashboard</span>
</a>
@@ -81,11 +79,8 @@
class="flex w-full items-center justify-between rounded-xl px-4 py-3 text-sm font-medium transition {{ $masterDataOpen ? 'bg-indigo-50 text-indigo-700' : 'text-slate-600 hover:bg-slate-100 hover:text-slate-900' }}"
>
<div class="flex items-center gap-3">
<span class="inline-flex h-9 w-9 items-center justify-center rounded-lg {{ $masterDataOpen ? 'bg-indigo-100 text-indigo-700' : 'bg-slate-100 text-slate-500' }}">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 7.5A1.5 1.5 0 0 1 5.5 6h13A1.5 1.5 0 0 1 20 7.5v9a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 4 16.5v-9Z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M8 10h8M8 14h5" />
</svg>
<span class="inline-flex h-9 w-9 items-center justify-center rounded-lg {{ $masterDataOpen ? 'bg-cyan-100 text-cyan-700 shadow-sm shadow-cyan-100' : 'bg-slate-100 text-cyan-500' }}">
<i class="fa-solid fa-database text-base"></i>
</span>
<span>Master Data</span>
</div>
@@ -96,32 +91,23 @@
<div x-show="masterDataOpen" x-collapse class="space-y-1 pl-4">
<a href="{{ route('academic-years.index') }}"
class="flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('academic-years.*') ? 'bg-indigo-50 text-indigo-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M8 3v3M16 3v3M4 9h16M5.5 5.5h13A1.5 1.5 0 0 1 20 7v11.5A1.5 1.5 0 0 1 18.5 20h-13A1.5 1.5 0 0 1 4 18.5V7A1.5 1.5 0 0 1 5.5 5.5Z" />
</svg>
class="group flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('academic-years.*') ? 'bg-cyan-50 text-cyan-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<i class="fa-solid fa-calendar-days text-sm {{ request()->routeIs('academic-years.*') ? 'text-cyan-600' : 'text-cyan-500 group-hover:text-cyan-600' }}"></i>
<span>Tahun Angkatan</span>
</a>
<a href="{{ route('departments.index') }}"
class="flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('departments.*') ? 'bg-indigo-50 text-indigo-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 19h16M6 19V9l6-4 6 4v10M10 13h4M10 16h4" />
</svg>
class="group flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('departments.*') ? 'bg-cyan-50 text-cyan-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<i class="fa-solid fa-building-columns text-sm {{ request()->routeIs('departments.*') ? 'text-cyan-600' : 'text-cyan-500 group-hover:text-cyan-600' }}"></i>
<span>Program Keahlian</span>
</a>
<a href="{{ route('classes.index') }}"
class="flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('classes.*') ? 'bg-indigo-50 text-indigo-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M3 7.5 12 3l9 4.5-9 4.5-9-4.5Zm3 4.5v4.5c0 .7 2.7 2.5 6 2.5s6-1.8 6-2.5V12" />
</svg>
class="group flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('classes.*') ? 'bg-cyan-50 text-cyan-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<i class="fa-solid fa-chalkboard text-sm {{ request()->routeIs('classes.*') ? 'text-cyan-600' : 'text-cyan-500 group-hover:text-cyan-600' }}"></i>
<span>Kelas</span>
</a>
<a href="{{ route('document-types.index') }}"
class="flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('document-types.*') ? 'bg-indigo-50 text-indigo-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M8 3.5h6l4 4V20a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V4.5a1 1 0 0 1 1-1Z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M14 3.5V8h4" />
</svg>
class="group flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('document-types.*') ? 'bg-cyan-50 text-cyan-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<i class="fa-solid fa-folder-tree text-sm {{ request()->routeIs('document-types.*') ? 'text-cyan-600' : 'text-cyan-500 group-hover:text-cyan-600' }}"></i>
<span>Jenis Dokumen</span>
</a>
</div>
@@ -135,10 +121,8 @@
class="flex w-full items-center justify-between rounded-xl px-4 py-3 text-sm font-medium transition {{ $kesiswaanOpen ? 'bg-indigo-50 text-indigo-700' : 'text-slate-600 hover:bg-slate-100 hover:text-slate-900' }}"
>
<div class="flex items-center gap-3">
<span class="inline-flex h-9 w-9 items-center justify-center rounded-lg {{ $kesiswaanOpen ? 'bg-indigo-100 text-indigo-700' : 'bg-slate-100 text-slate-500' }}">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M16 7a4 4 0 1 1-8 0 4 4 0 0 1 8 0ZM4 20a6 6 0 1 1 12 0" />
</svg>
<span class="inline-flex h-9 w-9 items-center justify-center rounded-lg {{ $kesiswaanOpen ? 'bg-emerald-100 text-emerald-700 shadow-sm shadow-emerald-100' : 'bg-slate-100 text-emerald-500' }}">
<i class="fa-solid fa-user-graduate text-base"></i>
</span>
<span>Kesiswaan</span>
</div>
@@ -149,59 +133,38 @@
<div x-show="kesiswaanOpen" x-collapse class="space-y-1 pl-4">
<a href="{{ route('students.index') }}"
class="flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('students.index') || request()->routeIs('students.create') || request()->routeIs('students.edit') || request()->routeIs('students.show') || request()->routeIs('students.import.*') ? 'bg-indigo-50 text-indigo-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M16 7a4 4 0 1 1-8 0 4 4 0 0 1 8 0ZM4 20a6 6 0 1 1 12 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M18 9a3 3 0 0 1 0 6m-1 5a5 5 0 0 0-3-4.58" />
</svg>
class="group flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('students.index') || request()->routeIs('students.create') || request()->routeIs('students.edit') || request()->routeIs('students.show') || request()->routeIs('students.import.*') ? 'bg-emerald-50 text-emerald-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<i class="fa-solid fa-users text-sm {{ request()->routeIs('students.index') || request()->routeIs('students.create') || request()->routeIs('students.edit') || request()->routeIs('students.show') || request()->routeIs('students.import.*') ? 'text-emerald-600' : 'text-emerald-500 group-hover:text-emerald-600' }}"></i>
<span>Data Siswa</span>
</a>
<a href="{{ route('student-exits.index') }}"
class="flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('student-exits.*') ? 'bg-indigo-50 text-indigo-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M17 7 7 17" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M7 7h10v10" />
</svg>
class="group flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('student-exits.*') ? 'bg-amber-50 text-amber-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<i class="fa-solid fa-right-from-bracket text-sm {{ request()->routeIs('student-exits.*') ? 'text-amber-600' : 'text-amber-500 group-hover:text-amber-600' }}"></i>
<span>Data Siswa Keluar</span>
</a>
<a href="{{ route('student-transfers.index') }}"
class="flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('student-transfers.*') ? 'bg-indigo-50 text-indigo-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 12h11" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m11 5 7 7-7 7" />
</svg>
class="group flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('student-transfers.*') ? 'bg-blue-50 text-blue-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<i class="fa-solid fa-right-left text-sm {{ request()->routeIs('student-transfers.*') ? 'text-blue-600' : 'text-blue-500 group-hover:text-blue-600' }}"></i>
<span>Data Siswa Mutasi</span>
</a>
<a href="{{ route('student-achievements.index') }}"
class="flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('student-achievements.*') ? 'bg-indigo-50 text-indigo-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M12 3 9.5 8H4l4.5 3.3L6.8 17 12 13.8 17.2 17l-1.7-5.7L20 8h-5.5L12 3Z" />
</svg>
class="group flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('student-achievements.*') ? 'bg-yellow-50 text-yellow-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<i class="fa-solid fa-trophy text-sm {{ request()->routeIs('student-achievements.*') ? 'text-yellow-600' : 'text-yellow-500 group-hover:text-yellow-600' }}"></i>
<span>Data Siswa Berprestasi</span>
</a>
<a href="{{ route('student-promotions.index') }}"
class="flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('student-promotions.*') ? 'bg-indigo-50 text-indigo-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M7 17 17 7M9 7h8v8" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M5 12v7h7" />
</svg>
class="group flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('student-promotions.*') ? 'bg-lime-50 text-lime-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<i class="fa-solid fa-arrow-trend-up text-sm {{ request()->routeIs('student-promotions.*') ? 'text-lime-600' : 'text-lime-500 group-hover:text-lime-600' }}"></i>
<span>Kenaikan Kelas</span>
</a>
<a href="{{ route('student-self-service.index') }}"
class="flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('student-self-service.*') ? 'bg-indigo-50 text-indigo-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M12 4v16" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M4 12h16" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M7.5 7.5h9v9h-9z" />
</svg>
class="group flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('student-self-service.*') ? 'bg-fuchsia-50 text-fuchsia-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<i class="fa-solid fa-id-card-clip text-sm {{ request()->routeIs('student-self-service.*') ? 'text-fuchsia-600' : 'text-fuchsia-500 group-hover:text-fuchsia-600' }}"></i>
<span>Self Servis Siswa</span>
</a>
<a href="{{ route('graduated-students.index') }}"
class="flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('graduated-students.*') ? 'bg-indigo-50 text-indigo-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M3 9.5 12 5l9 4.5-9 4.5-9-4.5Z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M7 12.5v3.5c0 .8 2.2 2.5 5 2.5s5-1.7 5-2.5v-3.5" />
</svg>
class="group flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('graduated-students.*') ? 'bg-sky-50 text-sky-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<i class="fa-solid fa-user-check text-sm {{ request()->routeIs('graduated-students.*') ? 'text-sky-600' : 'text-sky-500 group-hover:text-sky-600' }}"></i>
<span>Data Siswa Lulus</span>
</a>
@@ -216,11 +179,8 @@
class="flex w-full items-center justify-between rounded-xl px-4 py-3 text-sm font-medium transition {{ $settingsOpen ? 'bg-indigo-50 text-indigo-700' : 'text-slate-600 hover:bg-slate-100 hover:text-slate-900' }}"
>
<div class="flex items-center gap-3">
<span class="inline-flex h-9 w-9 items-center justify-center rounded-lg {{ $settingsOpen ? 'bg-indigo-100 text-indigo-700' : 'bg-slate-100 text-slate-500' }}">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M11.25 3h1.5l.62 2.18a7.52 7.52 0 0 1 1.78.74l2.05-.98 1.06 1.06-.98 2.05c.29.56.54 1.16.74 1.78L21 11.25v1.5l-2.18.62a7.52 7.52 0 0 1-.74 1.78l.98 2.05-1.06 1.06-2.05-.98a7.52 7.52 0 0 1-1.78.74L12.75 21h-1.5l-.62-2.18a7.52 7.52 0 0 1-1.78-.74l-2.05.98-1.06-1.06.98-2.05a7.52 7.52 0 0 1-.74-1.78L3 12.75v-1.5l2.18-.62c.2-.62.45-1.22.74-1.78l-.98-2.05 1.06-1.06 2.05.98c.56-.29 1.16-.54 1.78-.74L11.25 3Z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
<span class="inline-flex h-9 w-9 items-center justify-center rounded-lg {{ $settingsOpen ? 'bg-violet-100 text-violet-700 shadow-sm shadow-violet-100' : 'bg-slate-100 text-violet-500' }}">
<i class="fa-solid fa-gear text-base"></i>
</span>
<span>Pengaturan</span>
</div>
@@ -231,33 +191,23 @@
<div x-show="settingsOpen" x-collapse class="space-y-1 pl-4">
<a href="{{ route('settings.users.index') }}"
class="flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('settings.users.*') ? 'bg-indigo-50 text-indigo-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M16 7a4 4 0 1 1-8 0 4 4 0 0 1 8 0ZM4 20a6 6 0 1 1 12 0" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M19 8v6M22 11h-6" />
</svg>
class="group flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('settings.users.*') ? 'bg-violet-50 text-violet-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<i class="fa-solid fa-user-shield text-sm {{ request()->routeIs('settings.users.*') ? 'text-violet-600' : 'text-violet-500 group-hover:text-violet-600' }}"></i>
<span>Manajemen Pengguna</span>
</a>
<a href="{{ route('settings.branding.index') }}"
class="flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('settings.branding.*') ? 'bg-indigo-50 text-indigo-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M12 3l2.7 5.46 6.03.88-4.36 4.25 1.03 6.01L12 16.77 6.6 19.6l1.03-6.01L3.27 9.34l6.03-.88L12 3Z" />
</svg>
class="group flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('settings.branding.*') ? 'bg-pink-50 text-pink-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<i class="fa-solid fa-palette text-sm {{ request()->routeIs('settings.branding.*') ? 'text-pink-600' : 'text-pink-500 group-hover:text-pink-600' }}"></i>
<span>Branding Aplikasi</span>
</a>
<a href="{{ route('settings.sync-tokens.index') }}"
class="flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('settings.sync-tokens.*') ? 'bg-emerald-50 text-emerald-700' : 'text-slate-500 hover:bg-slate-100 hover:text-slate-900' }}">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M9 12l2 2 4-4" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M20 12A8 8 0 1 1 4 12a8 8 0 0 1 16 0Z" />
</svg>
<i class="fa-solid fa-rotate text-sm"></i>
<span>Sinkron Database</span>
</a>
<a href="{{ route('settings.reset-data.index') }}"
class="flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('settings.reset-data.*') ? 'bg-rose-50 text-rose-700' : 'text-slate-500 hover:bg-rose-50 hover:text-rose-700' }}">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M6 7h12M9 7V5.5A1.5 1.5 0 0 1 10.5 4h3A1.5 1.5 0 0 1 15 5.5V7m-8 0 1 12a1.5 1.5 0 0 0 1.5 1.5h5A1.5 1.5 0 0 0 16 19l1-12" />
</svg>
<i class="fa-solid fa-trash-can text-sm"></i>
<span>Kosongkan Data</span>
</a>
</div>
@@ -330,13 +280,15 @@
<div class="mt-1 text-xs text-slate-500">{{ $currentUserEmail }}</div>
</div>
<div class="p-2">
<a href="{{ route('profile.edit') }}" class="flex items-center rounded-xl px-3 py-3 text-sm font-medium text-slate-700 transition hover:bg-slate-50 hover:text-indigo-700">
Profil Administrator
<a href="{{ route('profile.edit') }}" class="flex items-center gap-3 rounded-xl px-3 py-3 text-sm font-medium text-slate-700 transition hover:bg-slate-50 hover:text-indigo-700">
<i class="fa-solid fa-id-badge text-sm"></i>
<span>Profil Administrator</span>
</a>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit" class="flex w-full items-center rounded-xl px-3 py-3 text-sm font-medium text-rose-600 transition hover:bg-rose-50">
Logout
<button type="submit" class="flex w-full items-center gap-3 rounded-xl px-3 py-3 text-sm font-medium text-rose-600 transition hover:bg-rose-50">
<i class="fa-solid fa-right-from-bracket text-sm"></i>
<span>Logout</span>
</button>
</form>
</div>
@@ -82,13 +82,13 @@
}
</script>
<div class="mb-8 flex flex-col gap-4 rounded-[2rem] bg-white p-6 shadow-sm shadow-slate-200 lg:flex-row lg:items-center lg:justify-between">
<div class="mb-8 flex flex-col gap-4 rounded-[2rem] border border-white/70 bg-white/95 p-6 shadow-sm shadow-slate-200 ring-1 ring-white/70 lg:flex-row lg:items-center lg:justify-between">
<div>
<div class="text-sm font-semibold uppercase tracking-[0.22em] text-indigo-600">Form Pelengkapan Data</div>
<h1 class="mt-2 text-3xl font-bold text-slate-900">{{ $student->full_name }}</h1>
<p class="mt-2 text-sm text-slate-500">Lengkapi data diri dengan benar. Data akademik inti ditampilkan sebagai referensi dan tidak bisa diubah dari sini.</p>
</div>
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-5 py-4 text-sm text-slate-600">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/30 px-5 py-4 text-sm text-slate-600 shadow-sm ring-1 ring-white/70">
<div><strong>NIS:</strong> {{ $student->student_number ?: '-' }}</div>
<div><strong>NISN:</strong> {{ $student->nisn ?: '-' }}</div>
<div><strong>Tahun Angkatan:</strong> {{ $student->academicYear?->name ?: '-' }}</div>
@@ -117,7 +117,7 @@
@csrf
<div class="grid grid-cols-1 gap-6 xl:grid-cols-2">
<div class="rounded-[2rem] bg-white p-6 shadow-sm shadow-slate-200">
<div class="rounded-[2rem] border border-white/70 bg-white/95 p-6 shadow-sm shadow-slate-200 ring-1 ring-white/70">
<h2 class="text-lg font-semibold text-slate-900">Identitas Siswa</h2>
<div class="mt-5 grid grid-cols-1 gap-5 md:grid-cols-2">
<div class="md:col-span-2">
@@ -154,6 +154,10 @@
<label class="mb-2 block text-sm font-medium text-slate-700">Kewarganegaraan</label>
<input name="nationality" type="text" value="{{ old('nationality', $student->nationality) }}" class="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm">
</div>
<div>
<label class="mb-2 block text-sm font-medium text-slate-700">NIK</label>
<input name="nik" type="text" value="{{ old('nik', $student->nik) }}" class="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm">
</div>
<div>
<label class="mb-2 block text-sm font-medium text-slate-700">No. Kartu Keluarga</label>
<input name="family_card_number" type="text" value="{{ old('family_card_number', $student->family_card_number) }}" class="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm">
@@ -177,7 +181,7 @@
</div>
</div>
<div class="rounded-[2rem] bg-white p-6 shadow-sm shadow-slate-200">
<div class="rounded-[2rem] border border-white/70 bg-white/95 p-6 shadow-sm shadow-slate-200 ring-1 ring-white/70">
<h2 class="text-lg font-semibold text-slate-900">Alamat</h2>
<div class="mt-5 grid grid-cols-1 gap-5 md:grid-cols-2">
<div>
@@ -228,14 +232,14 @@
</div>
</div>
<div class="rounded-[2rem] bg-white p-6 shadow-sm shadow-slate-200">
<div class="rounded-[2rem] border border-white/70 bg-white/95 p-6 shadow-sm shadow-slate-200 ring-1 ring-white/70">
<h2 class="text-lg font-semibold text-slate-900">Data Orang Tua / Wali</h2>
<div class="mt-6 grid grid-cols-1 gap-6 xl:grid-cols-3">
@foreach ($parentRelationLabels as $relationType => $label)
@php
$parent = $student->parents->firstWhere('relation_type', $relationType);
@endphp
<div class="rounded-3xl border border-slate-200 bg-slate-50 p-5">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-slate-100/70 p-5 shadow-sm ring-1 ring-white/70">
<h3 class="text-base font-semibold text-slate-800">{{ $label }}</h3>
<div class="mt-4 space-y-4">
<div>
@@ -282,12 +286,14 @@
</div>
</div>
<div class="flex flex-col gap-3 rounded-[2rem] bg-white p-6 shadow-sm shadow-slate-200 sm:flex-row sm:justify-end">
<button type="submit" name="submit_action" value="draft" class="inline-flex items-center justify-center rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">
Simpan Draft
<div class="flex flex-col gap-3 rounded-[2rem] border border-white/70 bg-white/95 p-6 shadow-sm shadow-slate-200 ring-1 ring-white/70 sm:flex-row sm:justify-end">
<button type="submit" name="submit_action" value="draft" class="inline-flex items-center justify-center gap-2 rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">
<i class="fa-solid fa-floppy-disk"></i>
<span>Simpan Draft</span>
</button>
<button type="submit" name="submit_action" value="submit" class="inline-flex items-center justify-center rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
Kirim Data
<button type="submit" name="submit_action" value="submit" class="inline-flex items-center justify-center gap-2 rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
<i class="fa-solid fa-paper-plane"></i>
<span>Kirim Data</span>
</button>
</div>
</form>
@@ -43,19 +43,42 @@
<div class="flex min-h-[48vh] items-center justify-center">
<div class="mx-auto w-full max-w-4xl overflow-hidden rounded-[32px] border border-white/80 bg-white/95 shadow-[0_30px_80px_-35px_rgba(15,23,42,0.25)]">
<div class="border-b border-slate-100 bg-gradient-to-r from-indigo-50 via-white to-sky-50 px-6 py-6 sm:px-8">
<div class="max-w-2xl">
<div class="text-xs font-semibold uppercase tracking-[0.18em] text-indigo-500">Verifikasi Identitas</div>
<h2 class="mt-2 text-2xl font-bold text-slate-900">Lengkapi data siswa secara mandiri</h2>
<p class="mt-2 text-sm leading-6 text-slate-500">
Masukkan NIS atau NISN dan kode akses dari sekolah untuk melanjutkan ke form pelengkapan data siswa.
</p>
<div class="flex flex-col gap-5 lg:flex-row lg:items-start lg:justify-between">
<div class="max-w-2xl">
<div class="text-xs font-semibold uppercase tracking-[0.18em] text-indigo-500">Verifikasi Identitas</div>
<h2 class="mt-2 text-2xl font-bold text-slate-900">Lengkapi data siswa secara mandiri</h2>
<p class="mt-2 text-sm leading-6 text-slate-500">
Masukkan NIS atau NISN dan kode akses dari sekolah untuk melanjutkan ke form pelengkapan data siswa.
</p>
</div>
<div class="grid grid-cols-1 gap-2 sm:grid-cols-3 lg:w-[340px]">
<div class="rounded-2xl border border-indigo-100 bg-white/90 px-3 py-3 text-center shadow-sm">
<div class="text-indigo-600"><i class="fa-solid fa-id-card-clip"></i></div>
<div class="mt-1 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500">Identitas</div>
</div>
<div class="rounded-2xl border border-amber-100 bg-white/90 px-3 py-3 text-center shadow-sm">
<div class="text-amber-600"><i class="fa-solid fa-pen-to-square"></i></div>
<div class="mt-1 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500">Lengkapi</div>
</div>
<div class="rounded-2xl border border-emerald-100 bg-white/90 px-3 py-3 text-center shadow-sm">
<div class="text-emerald-600"><i class="fa-solid fa-paper-plane"></i></div>
<div class="mt-1 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500">Kirim</div>
</div>
</div>
</div>
</div>
<div class="p-6 sm:p-8">
<div class="mb-6 rounded-2xl border border-indigo-100 bg-indigo-50/70 px-4 py-4 text-sm text-indigo-700">
<div class="font-semibold text-indigo-900">Petunjuk singkat</div>
<p class="mt-1">Gunakan data yang benar sesuai dokumen resmi. Jika ada data yang tidak cocok, hubungi admin sekolah atau wali kelas.</p>
<div class="mb-6 rounded-3xl border border-indigo-100 bg-gradient-to-r from-indigo-50/80 to-white px-4 py-4 text-sm text-indigo-700 shadow-sm">
<div class="flex items-start gap-3">
<div class="mt-0.5 inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl bg-white text-indigo-600 shadow-sm">
<i class="fa-solid fa-circle-info"></i>
</div>
<div>
<div class="font-semibold text-indigo-900">Petunjuk singkat</div>
<p class="mt-1">Gunakan data yang benar sesuai dokumen resmi. Jika ada data yang tidak cocok, hubungi admin sekolah atau wali kelas.</p>
</div>
</div>
</div>
@if ($errors->any())
@@ -96,8 +119,9 @@
>
</div>
<div class="lg:pb-[1px]">
<button type="submit" class="rounded-xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white transition hover:bg-indigo-700">
Lanjut
<button type="submit" class="inline-flex items-center gap-2 rounded-xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white transition hover:bg-indigo-700">
<i class="fa-solid fa-arrow-right"></i>
<span>Lanjut</span>
</button>
</div>
</div>
@@ -45,7 +45,7 @@
</form>
</x-master-card>
<x-master-card title="Daftar Token Sinkronisasi" subtitle="Token yang sudah dibuat untuk integrasi eksternal. Token yang direvoke tidak boleh dipakai lagi.">
<x-master-card title="Daftar Token Sinkronisasi" subtitle="Token yang sudah dibuat untuk integrasi eksternal. Token yang dihapus akan hilang dari daftar dan database.">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-slate-200 text-sm">
<thead class="bg-slate-50 text-left text-slate-500">
@@ -66,24 +66,16 @@
<td class="px-4 py-3 text-slate-600">{{ $token->creator?->name ?? '-' }}</td>
<td class="px-4 py-3 text-slate-600">{{ $token->last_used_at?->format('d M Y H:i') ?? 'Belum pernah' }}</td>
<td class="px-4 py-3">
@if ($token->revoked_at)
<span class="inline-flex rounded-full bg-rose-100 px-3 py-1 text-xs font-semibold text-rose-700">Nonaktif</span>
@else
<span class="inline-flex rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700">Aktif</span>
@endif
<span class="inline-flex rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700">Aktif</span>
</td>
<td class="px-4 py-3 text-right">
@if (! $token->revoked_at)
<form method="POST" action="{{ route('settings.sync-tokens.revoke', $token) }}" onsubmit="return confirm('Nonaktifkan token ini?')" class="inline-flex">
@csrf
@method('PATCH')
<button type="submit" class="inline-flex items-center rounded-xl border border-rose-200 bg-rose-50 px-3 py-2 text-xs font-semibold text-rose-700 transition hover:bg-rose-100">
Revoke
</button>
</form>
@else
<span class="text-xs text-slate-400">Sudah direvoke</span>
@endif
<form method="POST" action="{{ route('settings.sync-tokens.revoke', $token) }}" onsubmit="return confirm('Hapus token ini dari database?')" class="inline-flex">
@csrf
@method('PATCH')
<button type="submit" class="inline-flex items-center rounded-xl border border-rose-200 bg-rose-50 px-3 py-2 text-xs font-semibold text-rose-700 transition hover:bg-rose-100">
Hapus
</button>
</form>
</td>
</tr>
@empty
@@ -1,86 +1,139 @@
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
<div class="md:col-span-2">
<label for="student_id_search" class="mb-2 block text-sm font-medium text-slate-700">Siswa</label>
<div class="relative" data-searchable-student-select>
<input
id="student_id_search"
type="text"
value="{{ optional($students->firstWhere('id', old('student_id', $achievement->student_id ?? null)))->full_name ? optional($students->firstWhere('id', old('student_id', $achievement->student_id ?? null)))->full_name . ' — NIS: ' . (optional($students->firstWhere('id', old('student_id', $achievement->student_id ?? null)))->student_number ?: '-') . ' — NISN: ' . (optional($students->firstWhere('id', old('student_id', $achievement->student_id ?? null)))->nisn ?: '-') : '' }}"
class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 pr-12 text-sm text-slate-700 placeholder:text-slate-400 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100"
placeholder="Ketik nama siswa / NIS / NISN"
autocomplete="off"
data-student-search-input
required
>
<input type="hidden" name="student_id" value="{{ old('student_id', $achievement->student_id ?? '') }}" data-student-search-value>
<button type="button" class="absolute inset-y-0 right-0 flex items-center px-4 text-slate-400 transition hover:text-slate-600" data-student-search-toggle aria-label="Tampilkan daftar siswa">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m6 9 6 6 6-6" />
</svg>
</button>
<div class="absolute z-30 mt-2 hidden max-h-72 w-full overflow-auto rounded-2xl border border-slate-200 bg-white p-2 shadow-xl shadow-slate-200/60" data-student-search-dropdown>
@foreach ($students as $studentOption)
<button
type="button"
class="flex w-full flex-col rounded-xl px-3 py-3 text-left text-sm transition hover:bg-slate-50"
data-student-option
data-value="{{ $studentOption->id }}"
data-label="{{ $studentOption->full_name }} — NIS: {{ $studentOption->student_number ?: '-' }} — NISN: {{ $studentOption->nisn ?: '-' }}"
data-search="{{ strtolower($studentOption->full_name . ' ' . ($studentOption->student_number ?: '') . ' ' . ($studentOption->nisn ?: '')) }}"
>
<span class="font-semibold text-slate-800">{{ $studentOption->full_name }}</span>
<span class="mt-1 text-xs text-slate-500">NIS: {{ $studentOption->student_number ?: '-' }} · NISN: {{ $studentOption->nisn ?: '-' }}</span>
</button>
@endforeach
<div class="hidden px-3 py-3 text-sm text-slate-500" data-student-empty-state>Tidak ada siswa yang cocok.</div>
@php
$selectedStudent = $students->firstWhere('id', old('student_id', $achievement->student_id ?? null));
@endphp
<div class="space-y-6">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-amber-50/25 p-5 shadow-sm ring-1 ring-white/70">
<div class="mb-5 flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
<div>
<h3 class="text-base font-semibold text-slate-900">Identitas Prestasi</h3>
<p class="mt-1 text-sm text-slate-500">Pilih siswa lalu lengkapi inti informasi prestasinya terlebih dahulu.</p>
</div>
<div class="inline-flex items-center rounded-full bg-amber-100 px-3 py-1 text-xs font-semibold text-amber-700">
Field inti dulu, lampiran bisa menyusul
</div>
</div>
@error('student_id')
<p class="mt-2 text-sm text-rose-600">{{ $message }}</p>
@enderror
</div>
<x-form-input name="achievement_name" label="Nama Prestasi" :value="old('achievement_name', $achievement->achievement_name ?? '')" required />
<x-form-input name="category" label="Kategori Prestasi" :value="old('category', $achievement->category ?? '')" placeholder="Akademik / Non Akademik / lainnya" />
<x-form-select name="level" label="Tingkat Prestasi" :options="$levelOptions" :value="old('level', $achievement->level ?? '')" placeholder="-- Pilih tingkat --" />
<x-form-input name="rank" label="Peringkat / Predikat" :value="old('rank', $achievement->rank ?? '')" placeholder="Juara 1 / Finalis / Best Speaker" />
<x-form-input name="organizer" label="Penyelenggara" :value="old('organizer', $achievement->organizer ?? '')" placeholder="Nama sekolah / instansi / event" />
<x-form-input name="achievement_year" label="Tahun Prestasi" type="number" :value="old('achievement_year', $achievement->achievement_year ?? '')" placeholder="2026" />
<x-form-input name="achievement_date" label="Tanggal Prestasi" type="date" :value="old('achievement_date', isset($achievement->achievement_date) && $achievement->achievement_date ? $achievement->achievement_date->format('Y-m-d') : '')" />
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
<div class="md:col-span-2">
<label for="student_id_search" class="mb-2 block text-sm font-medium text-slate-700">Siswa</label>
<div class="relative" data-searchable-student-select>
<input
id="student_id_search"
type="text"
value="{{ $selectedStudent?->full_name ? $selectedStudent->full_name . ' — NIS: ' . ($selectedStudent->student_number ?: '-') . ' — NISN: ' . ($selectedStudent->nisn ?: '-') : '' }}"
class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 pr-12 text-sm text-slate-700 placeholder:text-slate-400 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100"
placeholder="Ketik nama siswa / NIS / NISN"
autocomplete="off"
data-student-search-input
required
>
<input type="hidden" name="student_id" value="{{ old('student_id', $achievement->student_id ?? '') }}" data-student-search-value>
<button type="button" class="absolute inset-y-0 right-0 flex items-center px-4 text-slate-400 transition hover:text-slate-600" data-student-search-toggle aria-label="Tampilkan daftar siswa">
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="m6 9 6 6 6-6" />
</svg>
</button>
<div class="absolute z-30 mt-2 hidden max-h-72 w-full overflow-auto rounded-2xl border border-slate-200 bg-white p-2 shadow-xl shadow-slate-200/60" data-student-search-dropdown>
@foreach ($students as $studentOption)
<button
type="button"
class="flex w-full flex-col rounded-xl px-3 py-3 text-left text-sm transition hover:bg-slate-50"
data-student-option
data-value="{{ $studentOption->id }}"
data-label="{{ $studentOption->full_name }} — NIS: {{ $studentOption->student_number ?: '-' }} — NISN: {{ $studentOption->nisn ?: '-' }}"
data-search="{{ strtolower($studentOption->full_name . ' ' . ($studentOption->student_number ?: '') . ' ' . ($studentOption->nisn ?: '')) }}"
>
<span class="font-semibold text-slate-800">{{ $studentOption->full_name }}</span>
<span class="mt-1 text-xs text-slate-500">NIS: {{ $studentOption->student_number ?: '-' }} · NISN: {{ $studentOption->nisn ?: '-' }}</span>
</button>
@endforeach
<div class="hidden px-3 py-3 text-sm text-slate-500" data-student-empty-state>Tidak ada siswa yang cocok.</div>
</div>
</div>
@if ($selectedStudent)
<div class="mt-3 rounded-2xl border border-indigo-100 bg-indigo-50/70 px-4 py-3 text-xs text-indigo-700 shadow-sm ring-1 ring-white/70">
Siswa terpilih: <span class="font-semibold">{{ $selectedStudent->full_name }}</span>
@if ($selectedStudent->student_number)
· NIS {{ $selectedStudent->student_number }}
@endif
@if ($selectedStudent->nisn)
· NISN {{ $selectedStudent->nisn }}
@endif
</div>
@endif
@error('student_id')
<p class="mt-2 text-sm text-rose-600">{{ $message }}</p>
@enderror
</div>
<div class="md:col-span-2">
<label for="description" class="mb-2 block text-sm font-medium text-slate-700">Keterangan</label>
<textarea id="description" name="description" rows="4" class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100" placeholder="Catatan tambahan tentang prestasi siswa">{{ old('description', $achievement->description ?? '') }}</textarea>
@error('description')
<p class="mt-2 text-sm text-rose-600">{{ $message }}</p>
@enderror
</div>
<x-form-input name="achievement_name" label="Nama Prestasi" :value="old('achievement_name', $achievement->achievement_name ?? '')" required />
<x-form-input name="category" label="Kategori Prestasi" :value="old('category', $achievement->category ?? '')" placeholder="Akademik / Non Akademik / lainnya" />
<x-form-select name="level" label="Tingkat Prestasi" :options="$levelOptions" :value="old('level', $achievement->level ?? '')" placeholder="-- Pilih tingkat --" />
<x-form-input name="rank" label="Peringkat / Predikat" :value="old('rank', $achievement->rank ?? '')" placeholder="Juara 1 / Finalis / Best Speaker" />
<x-form-input name="organizer" label="Penyelenggara" :value="old('organizer', $achievement->organizer ?? '')" placeholder="Nama sekolah / instansi / event" />
<div class="rounded-3xl border border-slate-200 bg-slate-50 p-5">
<label for="certificate_file" class="mb-2 block text-sm font-medium text-slate-700">Upload Piagam</label>
<div class="flex min-h-[3rem] items-center rounded-2xl border border-slate-200 bg-white px-3 py-2 transition focus-within:border-indigo-300 focus-within:ring-4 focus-within:ring-indigo-100">
<input id="certificate_file" name="certificate_file" type="file" class="block w-full text-sm text-slate-700 file:mr-3 file:rounded-xl file:border-0 file:bg-indigo-50 file:px-4 file:py-2.5 file:text-sm file:font-semibold file:text-indigo-700 hover:file:bg-indigo-100" accept=".pdf,.jpg,.jpeg,.png,.webp">
<div class="grid grid-cols-1 gap-5 md:col-span-2 md:grid-cols-2">
<x-form-input name="achievement_year" label="Tahun Prestasi" type="number" :value="old('achievement_year', $achievement->achievement_year ?? '')" placeholder="2026" />
<x-form-input name="achievement_date" label="Tanggal Prestasi" type="date" :value="old('achievement_date', isset($achievement->achievement_date) && $achievement->achievement_date ? $achievement->achievement_date->format('Y-m-d') : '')" />
</div>
<div class="md:col-span-2">
<label for="description" class="mb-2 block text-sm font-medium text-slate-700">Keterangan</label>
<textarea id="description" name="description" rows="4" class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100" placeholder="Catatan tambahan tentang prestasi siswa, misalnya konteks lomba atau capaian khusus.">{{ old('description', $achievement->description ?? '') }}</textarea>
<p class="mt-2 text-xs text-slate-500">Opsional, tapi berguna saat admin lain perlu memahami konteks prestasi ini.</p>
@error('description')
<p class="mt-2 text-sm text-rose-600">{{ $message }}</p>
@enderror
</div>
</div>
<p class="mt-2 text-xs text-slate-500">Format: PDF, JPG, JPEG, PNG, WEBP · maksimal 5 MB.</p>
@if (!empty($achievement->certificate_file))
<p class="mt-2 text-xs text-slate-500">File tersimpan: <a href="{{ asset('storage/' . $achievement->certificate_file) }}" target="_blank" class="font-semibold text-indigo-600 hover:text-indigo-700">Lihat Piagam</a></p>
@endif
@error('certificate_file')
<p class="mt-2 text-sm text-rose-600">{{ $message }}</p>
@enderror
</div>
<div class="rounded-3xl border border-slate-200 bg-slate-50 p-5">
<label for="activity_photo" class="mb-2 block text-sm font-medium text-slate-700">Upload Foto Kegiatan</label>
<div class="flex min-h-[3rem] items-center rounded-2xl border border-slate-200 bg-white px-3 py-2 transition focus-within:border-indigo-300 focus-within:ring-4 focus-within:ring-indigo-100">
<input id="activity_photo" name="activity_photo" type="file" class="block w-full text-sm text-slate-700 file:mr-3 file:rounded-xl file:border-0 file:bg-emerald-50 file:px-4 file:py-2.5 file:text-sm file:font-semibold file:text-emerald-700 hover:file:bg-emerald-100" accept=".jpg,.jpeg,.png,.webp">
<div class="grid grid-cols-1 gap-5 lg:grid-cols-2">
<div class="rounded-3xl border border-slate-200 bg-slate-50 p-5 shadow-sm ring-1 ring-white/70">
<div class="mb-4 flex items-start justify-between gap-3">
<div>
<label for="certificate_file" class="block text-sm font-medium text-slate-700">Upload Piagam</label>
<p class="mt-1 text-xs text-slate-500">Simpan file sertifikat atau piagam bila sudah tersedia.</p>
</div>
<span class="rounded-full bg-indigo-100 px-3 py-1 text-[11px] font-semibold text-indigo-700">PDF / Gambar</span>
</div>
<div class="flex min-h-[3rem] items-center rounded-2xl border border-slate-200 bg-white px-3 py-2 transition focus-within:border-indigo-300 focus-within:ring-4 focus-within:ring-indigo-100">
<input id="certificate_file" name="certificate_file" type="file" class="block w-full text-sm text-slate-700 file:mr-3 file:rounded-xl file:border-0 file:bg-indigo-50 file:px-4 file:py-2.5 file:text-sm file:font-semibold file:text-indigo-700 hover:file:bg-indigo-100" accept=".pdf,.jpg,.jpeg,.png,.webp">
</div>
<p class="mt-2 text-xs text-slate-500">Format: PDF, JPG, JPEG, PNG, WEBP · maksimal 5 MB.</p>
@if (!empty($achievement->certificate_file))
<div class="mt-3 rounded-2xl border border-indigo-100 bg-white px-4 py-3 text-xs text-slate-500 shadow-sm">
File tersimpan:
<a href="{{ asset('storage/' . $achievement->certificate_file) }}" target="_blank" class="font-semibold text-indigo-600 hover:text-indigo-700">Lihat Piagam</a>
</div>
@endif
@error('certificate_file')
<p class="mt-2 text-sm text-rose-600">{{ $message }}</p>
@enderror
</div>
<div class="rounded-3xl border border-slate-200 bg-slate-50 p-5 shadow-sm ring-1 ring-white/70">
<div class="mb-4 flex items-start justify-between gap-3">
<div>
<label for="activity_photo" class="block text-sm font-medium text-slate-700">Upload Foto Kegiatan</label>
<p class="mt-1 text-xs text-slate-500">Tambahkan dokumentasi kegiatan jika ingin tampilan data lebih kaya.</p>
</div>
<span class="rounded-full bg-emerald-100 px-3 py-1 text-[11px] font-semibold text-emerald-700">Gambar</span>
</div>
<div class="flex min-h-[3rem] items-center rounded-2xl border border-slate-200 bg-white px-3 py-2 transition focus-within:border-indigo-300 focus-within:ring-4 focus-within:ring-indigo-100">
<input id="activity_photo" name="activity_photo" type="file" class="block w-full text-sm text-slate-700 file:mr-3 file:rounded-xl file:border-0 file:bg-emerald-50 file:px-4 file:py-2.5 file:text-sm file:font-semibold file:text-emerald-700 hover:file:bg-emerald-100" accept=".jpg,.jpeg,.png,.webp">
</div>
<p class="mt-2 text-xs text-slate-500">Format: JPG, JPEG, PNG, WEBP · maksimal 5 MB.</p>
@if (!empty($achievement->activity_photo))
<div class="mt-3 rounded-2xl border border-emerald-100 bg-white px-4 py-3 text-xs text-slate-500 shadow-sm">
File tersimpan:
<a href="{{ asset('storage/' . $achievement->activity_photo) }}" target="_blank" class="font-semibold text-emerald-600 hover:text-emerald-700">Lihat Foto</a>
</div>
@endif
@error('activity_photo')
<p class="mt-2 text-sm text-rose-600">{{ $message }}</p>
@enderror
</div>
<p class="mt-2 text-xs text-slate-500">Format: JPG, JPEG, PNG, WEBP · maksimal 5 MB.</p>
@if (!empty($achievement->activity_photo))
<p class="mt-2 text-xs text-slate-500">File tersimpan: <a href="{{ asset('storage/' . $achievement->activity_photo) }}" target="_blank" class="font-semibold text-indigo-600 hover:text-indigo-700">Lihat Foto</a></p>
@endif
@error('activity_photo')
<p class="mt-2 text-sm text-rose-600">{{ $message }}</p>
@enderror
</div>
</div>
@@ -1,14 +1,86 @@
<x-master-page title="Tambah Data Siswa Berprestasi" description="Catat prestasi siswa baru ke dalam sistem kesiswaan.">
<x-master-card title="Form Prestasi Siswa" subtitle="Lengkapi data prestasi siswa lalu simpan.">
<form method="POST" action="{{ route('student-achievements.store') }}" enctype="multipart/form-data" class="space-y-6">
@csrf
@include('student-achievements._form')
<div class="flex items-center justify-end gap-3 pt-2">
<a href="{{ route('student-achievements.index') }}" class="rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">Batal</a>
<button type="submit" class="rounded-2xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-slate-800">Simpan</button>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/40 p-6 shadow-sm shadow-slate-200/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-slate-500">Mode Form</div>
<div class="mt-3 text-lg font-bold text-slate-900">Tambah Prestasi</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-indigo-600 ring-1 ring-indigo-100 backdrop-blur-sm">
<i class="fa-solid fa-plus text-lg"></i>
</div>
</div>
</form>
</x-master-card>
</div>
<div class="rounded-3xl border border-amber-200 bg-gradient-to-br from-white via-amber-50/50 to-amber-100/60 p-6 shadow-sm shadow-amber-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-amber-700">Fokus Input</div>
<div class="mt-3 text-lg font-bold text-amber-800">Identitas + Capaian</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-amber-600 ring-1 ring-amber-100 backdrop-blur-sm">
<i class="fa-solid fa-award text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-emerald-200 bg-gradient-to-br from-white via-emerald-50/50 to-emerald-100/60 p-6 shadow-sm shadow-emerald-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-emerald-700">Lampiran</div>
<div class="mt-3 text-lg font-bold text-emerald-800">Opsional</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-emerald-600 ring-1 ring-emerald-100 backdrop-blur-sm">
<i class="fa-solid fa-paperclip text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-fuchsia-200 bg-gradient-to-br from-white via-fuchsia-50/50 to-fuchsia-100/60 p-6 shadow-sm shadow-fuchsia-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-fuchsia-700">Target</div>
<div class="mt-3 text-lg font-bold text-fuchsia-800">Entry Cepat & Rapi</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-fuchsia-600 ring-1 ring-fuchsia-100 backdrop-blur-sm">
<i class="fa-solid fa-wand-magic-sparkles text-lg"></i>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,1.8fr)_360px]">
<x-master-card title="Form Prestasi Siswa" subtitle="Lengkapi data inti terlebih dahulu, lalu tambahkan lampiran bila sudah tersedia.">
<form method="POST" action="{{ route('student-achievements.store') }}" enctype="multipart/form-data" class="space-y-6">
@csrf
@include('student-achievements._form')
<div class="flex items-center justify-end gap-3 border-t border-slate-200 pt-5">
<a href="{{ route('student-achievements.index') }}" class="inline-flex items-center gap-2 rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">
<i class="fa-solid fa-arrow-left"></i>
<span>Batal</span>
</a>
<button type="submit" class="inline-flex items-center gap-2 rounded-2xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-slate-800">
<i class="fa-solid fa-floppy-disk"></i>
<span>Simpan</span>
</button>
</div>
</form>
</x-master-card>
<x-master-card title="Tips Input Cepat" subtitle="Biar admin tidak perlu mikir ulang saat entry data prestasi.">
<div class="space-y-4 text-sm text-slate-600">
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4">
<div class="flex items-center gap-2 font-semibold text-slate-800"><i class="fa-solid fa-user-check text-amber-500"></i><span>1. Pilih siswa dulu</span></div>
<p class="mt-1 text-slate-500">Gunakan pencarian nama, NIS, atau NISN supaya tidak salah kaitkan prestasi.</p>
</div>
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4">
<div class="flex items-center gap-2 font-semibold text-slate-800"><i class="fa-solid fa-star text-indigo-500"></i><span>2. Isi nama prestasi yang spesifik</span></div>
<p class="mt-1 text-slate-500">Contoh yang bagus: Olimpiade Matematika Kabupaten, bukan hanya "Lomba Matematika".</p>
</div>
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4">
<div class="flex items-center gap-2 font-semibold text-slate-800"><i class="fa-solid fa-file-circle-plus text-emerald-500"></i><span>3. Lampiran tidak wajib langsung</span></div>
<p class="mt-1 text-slate-500">Kalau piagam atau foto belum ada, data tetap bisa disimpan dulu lalu diedit belakangan.</p>
</div>
</div>
</x-master-card>
</div>
</x-master-page>
@@ -1,15 +1,90 @@
<x-master-page title="Edit Data Siswa Berprestasi" description="Perbarui data prestasi siswa yang sudah tersimpan.">
<x-master-card title="Form Edit Prestasi Siswa" subtitle="Sesuaikan data prestasi lalu simpan perubahan.">
<form method="POST" action="{{ route('student-achievements.update', $achievement) }}" enctype="multipart/form-data" class="space-y-6">
@csrf
@method('PUT')
@include('student-achievements._form')
<div class="flex items-center justify-end gap-3 pt-2">
<a href="{{ route('student-achievements.index') }}" class="rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">Batal</a>
<button type="submit" class="rounded-2xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-slate-800">Simpan Perubahan</button>
@php
$filterReviewIcon = 'fa-filter';
@endphp
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/40 p-6 shadow-sm shadow-slate-200/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-slate-500">Mode Form</div>
<div class="mt-3 text-lg font-bold text-slate-900">Edit Prestasi</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-indigo-600 ring-1 ring-indigo-100 backdrop-blur-sm">
<i class="fa-solid fa-pen text-lg"></i>
</div>
</div>
</form>
</x-master-card>
</div>
<div class="rounded-3xl border border-amber-200 bg-gradient-to-br from-white via-amber-50/50 to-amber-100/60 p-6 shadow-sm shadow-amber-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-amber-700">Fokus Review</div>
<div class="mt-3 text-lg font-bold text-amber-800">Kategori & Tingkat</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-amber-600 ring-1 ring-amber-100 backdrop-blur-sm">
<i class="fa-solid fa-layer-group text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-emerald-200 bg-gradient-to-br from-white via-emerald-50/50 to-emerald-100/60 p-6 shadow-sm shadow-emerald-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-emerald-700">Lampiran Lama</div>
<div class="mt-3 text-lg font-bold text-emerald-800">Tetap Aman</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-emerald-600 ring-1 ring-emerald-100 backdrop-blur-sm">
<i class="fa-solid fa-shield-check text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-fuchsia-200 bg-gradient-to-br from-white via-fuchsia-50/50 to-fuchsia-100/60 p-6 shadow-sm shadow-fuchsia-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-fuchsia-700">Target</div>
<div class="mt-3 text-lg font-bold text-fuchsia-800">Update Konsisten</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-fuchsia-600 ring-1 ring-fuchsia-100 backdrop-blur-sm">
<i class="fa-solid fa-arrows-rotate text-lg"></i>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,1.8fr)_360px]">
<x-master-card title="Form Edit Prestasi Siswa" subtitle="Sesuaikan data inti, cek kembali lampiran, lalu simpan perubahan.">
<form method="POST" action="{{ route('student-achievements.update', $achievement) }}" enctype="multipart/form-data" class="space-y-6">
@csrf
@method('PUT')
@include('student-achievements._form')
<div class="flex items-center justify-end gap-3 border-t border-slate-200 pt-5">
<a href="{{ route('student-achievements.index') }}" class="inline-flex items-center gap-2 rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">
<i class="fa-solid fa-arrow-left"></i>
<span>Batal</span>
</a>
<button type="submit" class="inline-flex items-center gap-2 rounded-2xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-slate-800">
<i class="fa-solid fa-floppy-disk"></i>
<span>Simpan Perubahan</span>
</button>
</div>
</form>
</x-master-card>
<x-master-card title="Checklist Review" subtitle="Biar perubahan tidak cuma rapi, tapi juga konsisten.">
<div class="space-y-4 text-sm text-slate-600">
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4">
<div class="flex items-center gap-2 font-semibold text-slate-800"><i class="fa-solid fa-link text-amber-500"></i><span>Pastikan siswa tetap benar</span></div>
<p class="mt-1 text-slate-500">Kalau prestasi awalnya salah tempel ke siswa, perbaiki relasinya dari sini.</p>
</div>
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4">
<div class="flex items-center gap-2 font-semibold text-slate-800"><i class="fa-solid {{ $filterReviewIcon }} text-indigo-500"></i><span>Cek tingkat dan kategori</span></div>
<p class="mt-1 text-slate-500">Dua field ini penting untuk filter, rekap, dan kualitas data ke depan.</p>
</div>
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4">
<div class="flex items-center gap-2 font-semibold text-slate-800"><i class="fa-solid fa-lock text-emerald-500"></i><span>Lampiran lama tetap aman</span></div>
<p class="mt-1 text-slate-500">File lama hanya terganti jika admin mengunggah file baru saat edit.</p>
</div>
</div>
</x-master-card>
</div>
</x-master-page>
@@ -1,98 +1,271 @@
<x-master-page title="Data Siswa Berprestasi" description="Kelola data prestasi siswa berdasarkan capaian akademik maupun non-akademik." action-label="Tambah Data Prestasi" :action-url="route('student-achievements.create')">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div class="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm shadow-slate-200/50">
<div class="text-sm font-medium text-slate-500">Total Prestasi</div>
<div class="mt-3 text-3xl font-bold text-slate-900">{{ $summary['total'] }}</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-5">
<div class="group rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/40 p-6 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-200/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-slate-500">Total Prestasi</div>
<div class="mt-3 text-3xl font-bold text-slate-900">{{ $summary['total'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-indigo-600 ring-1 ring-indigo-100 backdrop-blur-sm">
<i class="fa-solid fa-trophy text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-indigo-200 bg-indigo-50 p-6 shadow-sm shadow-indigo-100/50">
<div class="text-sm font-medium text-indigo-700">Hasil Filter</div>
<div class="mt-3 text-3xl font-bold text-indigo-800">{{ $summary['filtered'] }}</div>
<div class="group rounded-3xl border border-amber-200 bg-gradient-to-br from-white via-amber-50/50 to-amber-100/60 p-6 shadow-sm shadow-amber-100/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-amber-200/60">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-amber-700">Siswa Berprestasi</div>
<div class="mt-3 text-3xl font-bold text-amber-800">{{ $summary['students'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-amber-600 ring-1 ring-amber-100 backdrop-blur-sm">
<i class="fa-solid fa-medal text-lg"></i>
</div>
</div>
</div>
<div class="group rounded-3xl border border-emerald-200 bg-gradient-to-br from-white via-emerald-50/50 to-emerald-100/60 p-6 shadow-sm shadow-emerald-100/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-emerald-200/60">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-emerald-700">Ada Piagam</div>
<div class="mt-3 text-3xl font-bold text-emerald-800">{{ $summary['with_certificate'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-emerald-600 ring-1 ring-emerald-100 backdrop-blur-sm">
<i class="fa-solid fa-file-circle-check text-lg"></i>
</div>
</div>
</div>
<div class="group rounded-3xl border border-fuchsia-200 bg-gradient-to-br from-white via-fuchsia-50/50 to-fuchsia-100/60 p-6 shadow-sm shadow-fuchsia-100/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-fuchsia-200/60">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-fuchsia-700">Prestasi Tahun Ini</div>
<div class="mt-3 text-3xl font-bold text-fuchsia-800">{{ $summary['this_year'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-fuchsia-600 ring-1 ring-fuchsia-100 backdrop-blur-sm">
<i class="fa-solid fa-calendar-check text-lg"></i>
</div>
</div>
</div>
<div class="group rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-slate-100/70 p-6 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-200/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-slate-500">Hasil Filter</div>
<div class="mt-3 text-3xl font-bold text-slate-900">{{ $summary['filtered'] }}</div>
<div class="mt-2 text-xs text-slate-500">{{ $summary['latest_year'] ? 'Tahun terbaru ' . $summary['latest_year'] : 'Belum ada tahun prestasi' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-slate-600 ring-1 ring-slate-200 backdrop-blur-sm">
<i class="fa-solid fa-filter text-lg"></i>
</div>
</div>
</div>
</div>
<x-master-card title="Filter Data Siswa Berprestasi" subtitle="Cari berdasarkan nama siswa, NIS, NISN, kategori, atau tingkat prestasi.">
<form method="GET" action="{{ route('student-achievements.index') }}" class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<x-form-input name="search" label="Pencarian" :value="$search" placeholder="Nama / NIS / NISN / prestasi" />
<x-form-select name="academic_year_id" label="Tahun Angkatan" :options="$academicYears->all()" :value="$filters['academic_year_id']" />
<x-form-select name="class_id" label="Kelas" :options="$classes->all()" :value="$filters['class_id']" />
<div class="grid grid-cols-1 gap-6 xl:grid-cols-[1.15fr_0.85fr]">
<x-master-card title="Filter Data Siswa Berprestasi" subtitle="Cari lebih cepat berdasarkan siswa, rombel, kategori, atau tingkat prestasi.">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/30 p-4 shadow-sm ring-1 ring-white/70">
<form method="GET" action="{{ route('student-achievements.index') }}" class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-5">
<x-form-input name="search" label="Pencarian" :value="$search" placeholder="Nama / NIS / NISN / prestasi" />
<x-form-select name="academic_year_id" label="Tahun Angkatan" :options="$academicYears->all()" :value="$filters['academic_year_id']" />
<x-form-select name="class_id" label="Kelas" :options="$classes->all()" :value="$filters['class_id']" />
<x-form-select name="category" label="Kategori" :options="$categoryOptions->all()" :value="$filters['category']" placeholder="Semua kategori" />
<x-form-select name="level" label="Tingkat Prestasi" :options="$levelOptions" :value="$filters['level']" placeholder="Semua tingkat" />
<div class="flex items-end gap-3 md:col-span-2 xl:col-span-4">
<button type="submit" class="rounded-2xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-slate-800">Cari</button>
<a href="{{ route('student-achievements.index') }}" class="rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">Reset</a>
<div class="flex flex-wrap items-end gap-3 md:col-span-2 xl:col-span-5">
<button type="submit" class="inline-flex items-center gap-2 rounded-2xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-slate-800">
<i class="fa-solid fa-magnifying-glass"></i>
<span>Terapkan Filter</span>
</button>
<a href="{{ route('student-achievements.index') }}" class="inline-flex items-center gap-2 rounded-2xl border border-slate-200 bg-white px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">
<i class="fa-solid fa-rotate-left"></i>
<span>Reset</span>
</a>
</div>
</form>
</div>
</form>
</x-master-card>
</x-master-card>
<x-data-table :headers="['Siswa', 'Prestasi', 'Akademik', 'Lampiran', 'Waktu', 'Aksi']">
@forelse ($achievements as $achievement)
<tr>
<td class="px-6 py-4 text-sm text-slate-600">
<div class="font-semibold text-slate-800">{{ $achievement->student?->full_name ?: '-' }}</div>
<div class="mt-1 text-xs text-slate-500">NIS: {{ $achievement->student?->student_number ?: '-' }} · NISN: {{ $achievement->student?->nisn ?: '-' }}</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div class="font-semibold text-slate-800">{{ $achievement->achievement_name }}</div>
<div class="mt-1 text-xs text-slate-500">
{{ $achievement->category ?: 'Tanpa kategori' }}
@if($achievement->rank)
· {{ $achievement->rank }}
@endif
</div>
<div class="mt-1 text-xs text-slate-500"><span class="font-semibold text-slate-600">Tingkat:</span> {{ $achievement->level ?: '-' }}</div>
<div class="mt-1 text-xs text-slate-400"><span class="font-semibold text-slate-500">Penyelenggara:</span> {{ $achievement->organizer ?: 'Belum diisi' }}</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div>{{ $achievement->student?->academicYear?->name ?: '-' }}</div>
<div>{{ $achievement->student?->department?->name ?: '-' }}</div>
<div>{{ $achievement->student?->classRoom?->name ?: '-' }}</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div class="flex flex-col gap-2">
@if($achievement->certificate_file)
@php
$certificateUrl = asset('storage/' . $achievement->certificate_file);
$certificateExtension = strtolower(pathinfo($achievement->certificate_file, PATHINFO_EXTENSION));
$certificateIsImage = in_array($certificateExtension, ['jpg', 'jpeg', 'png', 'webp']);
@endphp
@if($certificateIsImage)
<button type="button" data-lightbox-trigger data-lightbox-src="{{ $certificateUrl }}" data-lightbox-title="Piagam — {{ $achievement->achievement_name }}" class="inline-flex w-fit items-center rounded-xl border border-indigo-200 bg-indigo-50 px-3 py-2 text-xs font-semibold text-indigo-700 transition hover:bg-indigo-100">Lihat Piagam</button>
@else
<a href="{{ $certificateUrl }}" target="_blank" class="inline-flex w-fit items-center rounded-xl border border-indigo-200 bg-indigo-50 px-3 py-2 text-xs font-semibold text-indigo-700 transition hover:bg-indigo-100">Lihat Piagam</a>
@endif
@else
<span class="text-xs text-slate-400">Piagam belum diunggah</span>
@endif
<x-master-card title="Aktivitas Terbaru" subtitle="Tiga catatan prestasi yang paling akhir masuk ke sistem.">
@if ($recentAchievements->isEmpty())
<div class="rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-4 py-6 text-sm text-slate-500">
Belum ada data prestasi terbaru untuk ditampilkan.
</div>
@else
<div class="space-y-3">
@foreach ($recentAchievements as $recentAchievement)
<div class="rounded-2xl border border-slate-200 bg-slate-50/80 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-semibold text-slate-800">{{ $recentAchievement->achievement_name }}</div>
<div class="mt-1 text-xs text-slate-500">{{ $recentAchievement->student?->full_name ?: '-' }}</div>
</div>
@if ($recentAchievement->level)
<span class="rounded-full bg-amber-100 px-3 py-1 text-[11px] font-semibold text-amber-700">{{ $recentAchievement->level }}</span>
@endif
</div>
<div class="mt-3 flex flex-wrap gap-2 text-xs text-slate-500">
<span class="rounded-full bg-white px-3 py-1 ring-1 ring-slate-200">{{ $recentAchievement->category ?: 'Tanpa kategori' }}</span>
<span class="rounded-full bg-white px-3 py-1 ring-1 ring-slate-200">{{ $recentAchievement->achievement_year ?: 'Tahun belum diisi' }}</span>
@if ($recentAchievement->achievement_date)
<span class="rounded-full bg-white px-3 py-1 ring-1 ring-slate-200">{{ $recentAchievement->achievement_date->format('d M Y') }}</span>
@endif
</div>
</div>
@endforeach
</div>
@endif
</x-master-card>
</div>
@if($achievement->activity_photo)
<button type="button" data-lightbox-trigger data-lightbox-src="{{ asset('storage/' . $achievement->activity_photo) }}" data-lightbox-title="Foto Kegiatan — {{ $achievement->achievement_name }}" class="inline-flex w-fit items-center rounded-xl border border-emerald-200 bg-emerald-50 px-3 py-2 text-xs font-semibold text-emerald-700 transition hover:bg-emerald-100">Lihat Foto Kegiatan</button>
@else
<span class="text-xs text-slate-400">Foto kegiatan belum diunggah</span>
@endif
</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div>Tahun: {{ $achievement->achievement_year ?: '-' }}</div>
<div class="mt-1">Tanggal: {{ $achievement->achievement_date?->format('d-m-Y') ?: '-' }}</div>
</td>
<td class="px-6 py-4">
<div class="flex flex-wrap items-center gap-2">
<a href="{{ route('student-achievements.edit', $achievement) }}" class="rounded-xl border border-slate-200 px-3 py-2 text-xs font-semibold text-slate-700 transition hover:bg-slate-50">Edit</a>
<form action="{{ route('student-achievements.destroy', $achievement) }}" method="POST" class="delete-form" data-entity="data prestasi {{ $achievement->achievement_name }}">
@csrf
@method('DELETE')
<button type="submit" class="rounded-xl border border-rose-200 px-3 py-2 text-xs font-semibold text-rose-600 transition hover:bg-rose-50">Hapus</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-6 py-10 text-center text-sm text-slate-500">Belum ada data siswa berprestasi.</td>
</tr>
@endforelse
</x-data-table>
<div class="overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-sm shadow-slate-200/50 ring-1 ring-white/70">
<div class="border-b border-slate-200 bg-gradient-to-r from-white via-slate-50 to-indigo-50/30 px-6 py-5">
<div class="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div>
<div class="text-lg font-semibold text-slate-800">Daftar Prestasi</div>
<div class="mt-1 text-sm text-slate-500">Tabel utama untuk review, edit, dan bersih-bersih data prestasi siswa.</div>
</div>
<div class="flex flex-wrap items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">
<span class="inline-flex items-center gap-2 rounded-full bg-slate-900 px-3 py-1.5 text-white shadow-sm">
<i class="fa-solid fa-table-list"></i>
{{ $achievements->total() }} data cocok
</span>
@if ($filters['category'])
<span class="rounded-full bg-amber-50 px-3 py-1.5 text-amber-700">Kategori: {{ $filters['category'] }}</span>
@endif
@if ($filters['level'])
<span class="rounded-full bg-emerald-50 px-3 py-1.5 text-emerald-700">Tingkat: {{ $filters['level'] }}</span>
@endif
</div>
</div>
</div>
<div>
{{ $achievements->links() }}
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-slate-200">
<thead class="bg-slate-50">
<tr>
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Siswa</th>
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Prestasi</th>
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Akademik</th>
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Lampiran</th>
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Waktu</th>
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Aksi</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 bg-white">
@forelse ($achievements as $achievement)
<tr class="transition hover:bg-slate-50/80">
<td class="px-6 py-4 text-sm text-slate-600">
<div class="flex items-start gap-3">
<div class="flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl bg-indigo-100 text-sm font-bold text-indigo-700">
{{ strtoupper(mb_substr($achievement->student?->full_name ?: '-', 0, 1)) }}
</div>
<div>
<div class="font-semibold text-slate-800">{{ $achievement->student?->full_name ?: '-' }}</div>
<div class="mt-1 text-xs text-slate-500">NIS: {{ $achievement->student?->student_number ?: '-' }} · NISN: {{ $achievement->student?->nisn ?: '-' }}</div>
</div>
</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div class="font-semibold text-slate-800">{{ $achievement->achievement_name }}</div>
<div class="mt-2 flex flex-wrap gap-2 text-xs">
<span class="rounded-full bg-slate-100 px-3 py-1 font-medium text-slate-600">{{ $achievement->category ?: 'Tanpa kategori' }}</span>
@if($achievement->level)
<span class="rounded-full bg-amber-100 px-3 py-1 font-medium text-amber-700">{{ $achievement->level }}</span>
@endif
@if($achievement->rank)
<span class="rounded-full bg-indigo-100 px-3 py-1 font-medium text-indigo-700">{{ $achievement->rank }}</span>
@endif
</div>
<div class="mt-2 text-xs text-slate-500"><span class="font-semibold text-slate-600">Penyelenggara:</span> {{ $achievement->organizer ?: 'Belum diisi' }}</div>
@if ($achievement->description)
<div class="mt-2 line-clamp-2 text-xs text-slate-400">{{ $achievement->description }}</div>
@endif
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div class="font-medium text-slate-700">{{ $achievement->student?->academicYear?->name ?: '-' }}</div>
<div>{{ $achievement->student?->department?->name ?: '-' }}</div>
<div class="text-xs text-slate-500">{{ $achievement->student?->classRoom?->name ?: '-' }}</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div class="flex flex-col gap-2">
@if($achievement->certificate_file)
@php
$certificateUrl = asset('storage/' . $achievement->certificate_file);
$certificateExtension = strtolower(pathinfo($achievement->certificate_file, PATHINFO_EXTENSION));
$certificateIsImage = in_array($certificateExtension, ['jpg', 'jpeg', 'png', 'webp']);
@endphp
@if($certificateIsImage)
<button type="button" data-lightbox-trigger data-lightbox-src="{{ $certificateUrl }}" data-lightbox-title="Piagam — {{ $achievement->achievement_name }}" class="inline-flex w-fit items-center gap-2 rounded-xl border border-indigo-200 bg-indigo-50 px-3 py-2 text-xs font-semibold text-indigo-700 transition hover:bg-indigo-100">
<i class="fa-solid fa-file-image"></i>
<span>Lihat Piagam</span>
</button>
@else
<a href="{{ $certificateUrl }}" target="_blank" class="inline-flex w-fit items-center gap-2 rounded-xl border border-indigo-200 bg-indigo-50 px-3 py-2 text-xs font-semibold text-indigo-700 transition hover:bg-indigo-100">
<i class="fa-solid fa-file-pdf"></i>
<span>Buka Piagam</span>
</a>
@endif
@else
<span class="inline-flex w-fit items-center gap-2 rounded-full bg-amber-100 px-3 py-1 text-xs font-semibold text-amber-700">
<i class="fa-solid fa-triangle-exclamation"></i>
<span>Piagam belum ada</span>
</span>
@endif
@if($achievement->activity_photo)
<button type="button" data-lightbox-trigger data-lightbox-src="{{ asset('storage/' . $achievement->activity_photo) }}" data-lightbox-title="Foto Kegiatan — {{ $achievement->achievement_name }}" class="inline-flex w-fit items-center gap-2 rounded-xl border border-emerald-200 bg-emerald-50 px-3 py-2 text-xs font-semibold text-emerald-700 transition hover:bg-emerald-100">
<i class="fa-solid fa-camera"></i>
<span>Lihat Foto</span>
</button>
@else
<span class="inline-flex w-fit items-center gap-2 rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-600">
<i class="fa-solid fa-image"></i>
<span>Foto belum ada</span>
</span>
@endif
</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div class="inline-flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-3 py-2">
<i class="fa-solid fa-calendar-days text-xs text-slate-400"></i>
<span>{{ $achievement->achievement_year ?: '-' }}</span>
</div>
<div class="mt-2 text-xs text-slate-500">{{ $achievement->achievement_date?->format('d-m-Y') ?: 'Tanggal belum diisi' }}</div>
</td>
<td class="px-6 py-4">
<div class="flex flex-wrap items-center gap-2">
<a href="{{ route('student-achievements.edit', $achievement) }}" class="inline-flex items-center gap-2 rounded-xl border border-slate-200 px-3 py-2 text-xs font-semibold text-slate-700 transition hover:bg-slate-50">
<i class="fa-solid fa-pen"></i>
<span>Edit</span>
</a>
<form action="{{ route('student-achievements.destroy', $achievement) }}" method="POST" class="delete-form" data-entity="data prestasi {{ $achievement->achievement_name }}">
@csrf
@method('DELETE')
<button type="submit" class="inline-flex items-center gap-2 rounded-xl border border-rose-200 px-3 py-2 text-xs font-semibold text-rose-600 transition hover:bg-rose-50">
<i class="fa-solid fa-trash"></i>
<span>Hapus</span>
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-6 py-12 text-center text-sm text-slate-500">
<div class="mx-auto max-w-md">
<div class="text-base font-semibold text-slate-700">Belum ada data siswa berprestasi.</div>
<div class="mt-2 text-sm text-slate-500">Mulai dari menambahkan prestasi pertama, atau longgarkan filter jika data seharusnya sudah ada.</div>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
<div class="mt-6 flex justify-end">
{{ $achievements->onEachSide(1)->links() }}
</div>
</x-master-page>
+170 -29
View File
@@ -1,19 +1,129 @@
<x-master-page title="Data Siswa Keluar" description="Data siswa yang sudah dipindahkan ke tabel arsip siswa keluar saat statusnya diubah menjadi Keluar.">
<x-master-card title="Filter Data Siswa Keluar" subtitle="Cari data siswa keluar berdasarkan identitas dan akademik terakhir.">
<form method="GET" action="{{ route('student-exits.index') }}" class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<x-form-input name="search" label="Pencarian" :value="$search" placeholder="Nama / NIS / NISN" />
<x-form-select name="academic_year_id" label="Tahun Angkatan" :options="$academicYears->all()" :value="$filters['academic_year_id']" />
<x-form-select name="department_id" label="Program Keahlian" :options="$departments->all()" :value="$filters['department_id']" />
<x-form-select name="class_id" label="Kelas Terakhir" :options="$classes->all()" :value="$filters['class_id']" />
<div class="flex items-end gap-3 md:col-span-2 xl:col-span-4">
<button type="submit" class="rounded-2xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-slate-800">Cari</button>
<a href="{{ route('student-exits.index') }}" class="rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">Reset</a>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-5">
<div class="group rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-rose-50/40 p-6 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-200/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-slate-500">Total Arsip Keluar</div>
<div class="mt-3 text-3xl font-bold text-slate-900">{{ $summary['total'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-rose-600 ring-1 ring-rose-100 backdrop-blur-sm">
<i class="fa-solid fa-right-from-bracket text-lg"></i>
</div>
</div>
</form>
</x-master-card>
</div>
<div class="group rounded-3xl border border-emerald-200 bg-gradient-to-br from-white via-emerald-50/50 to-emerald-100/60 p-6 shadow-sm shadow-emerald-100/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-emerald-200/60">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-emerald-700">Sudah Ada Surat</div>
<div class="mt-3 text-3xl font-bold text-emerald-800">{{ $summary['with_letter'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-emerald-600 ring-1 ring-emerald-100 backdrop-blur-sm">
<i class="fa-solid fa-file-circle-check text-lg"></i>
</div>
</div>
</div>
<div class="group rounded-3xl border {{ $summary['without_letter'] > 0 ? 'border-amber-200 bg-gradient-to-br from-white via-amber-50/50 to-amber-100/60' : 'border-sky-200 bg-gradient-to-br from-white via-sky-50/50 to-sky-100/60' }} p-6 shadow-sm {{ $summary['without_letter'] > 0 ? 'shadow-amber-100/50' : 'shadow-sky-100/50' }} ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg {{ $summary['without_letter'] > 0 ? 'hover:shadow-amber-200/60' : 'hover:shadow-sky-200/60' }}">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium {{ $summary['without_letter'] > 0 ? 'text-amber-700' : 'text-sky-700' }}">Belum Ada Surat</div>
<div class="mt-3 text-3xl font-bold {{ $summary['without_letter'] > 0 ? 'text-amber-800' : 'text-sky-800' }}">{{ $summary['without_letter'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 {{ $summary['without_letter'] > 0 ? 'text-amber-600 ring-1 ring-amber-100' : 'text-sky-600 ring-1 ring-sky-100' }} backdrop-blur-sm">
<i class="fa-solid {{ $summary['without_letter'] > 0 ? 'fa-file-circle-xmark' : 'fa-folder-open' }} text-lg"></i>
</div>
</div>
</div>
<div class="group rounded-3xl border border-fuchsia-200 bg-gradient-to-br from-white via-fuchsia-50/50 to-fuchsia-100/60 p-6 shadow-sm shadow-fuchsia-100/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-fuchsia-200/60">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-fuchsia-700">Keluar Tahun Ini</div>
<div class="mt-3 text-3xl font-bold text-fuchsia-800">{{ $summary['this_year'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-fuchsia-600 ring-1 ring-fuchsia-100 backdrop-blur-sm">
<i class="fa-solid fa-calendar-check text-lg"></i>
</div>
</div>
</div>
<div class="group rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-slate-100/70 p-6 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-200/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-slate-500">Hasil Filter</div>
<div class="mt-3 text-3xl font-bold text-slate-900">{{ $summary['filtered'] }}</div>
<div class="mt-2 text-xs text-slate-500">{{ $summary['latest_exit_date'] ? 'Update terakhir ' . \Illuminate\Support\Carbon::parse($summary['latest_exit_date'])->format('d M Y') : 'Belum ada tanggal keluar' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-slate-600 ring-1 ring-slate-200 backdrop-blur-sm">
<i class="fa-solid fa-filter text-lg"></i>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 gap-6 xl:grid-cols-[1.15fr_0.85fr]">
<x-master-card title="Filter Data Siswa Keluar" subtitle="Cari data siswa keluar berdasarkan identitas dan akademik terakhir.">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-rose-50/30 p-4 shadow-sm ring-1 ring-white/70">
<form method="GET" action="{{ route('student-exits.index') }}" class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<x-form-input name="search" label="Pencarian" :value="$search" placeholder="Nama / NIS / NISN" />
<x-form-select name="academic_year_id" label="Tahun Angkatan" :options="$academicYears->all()" :value="$filters['academic_year_id']" />
<x-form-select name="department_id" label="Program Keahlian" :options="$departments->all()" :value="$filters['department_id']" />
<x-form-select name="class_id" label="Kelas Terakhir" :options="$classes->all()" :value="$filters['class_id']" />
<div class="flex flex-wrap items-end gap-3 md:col-span-2 xl:col-span-4">
<button type="submit" class="inline-flex items-center gap-2 rounded-2xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-slate-800">
<i class="fa-solid fa-magnifying-glass"></i>
<span>Cari</span>
</button>
<a href="{{ route('student-exits.index') }}" class="inline-flex items-center gap-2 rounded-2xl border border-slate-200 bg-white px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">
<i class="fa-solid fa-rotate-left"></i>
<span>Reset</span>
</a>
</div>
</form>
</div>
</x-master-card>
<x-master-card title="Aktivitas Terbaru" subtitle="Tiga arsip siswa keluar terbaru yang masuk ke sistem.">
@if ($recentExits->isEmpty())
<div class="rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-4 py-6 text-sm text-slate-500">
Belum ada arsip siswa keluar terbaru untuk ditampilkan.
</div>
@else
<div class="space-y-3">
@foreach ($recentExits as $recentExit)
<div class="rounded-2xl border border-slate-200 bg-slate-50/80 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-semibold text-slate-800">{{ $recentExit->full_name }}</div>
<div class="mt-1 text-xs text-slate-500">{{ $recentExit->department?->name ?: '-' }} · {{ $recentExit->classRoom?->name ?: '-' }}</div>
</div>
<span class="rounded-full bg-white px-3 py-1 text-[11px] font-semibold text-slate-600 ring-1 ring-slate-200">
{{ $recentExit->exit_date?->format('d M Y') ?: 'Tanggal belum diisi' }}
</span>
</div>
</div>
@endforeach
</div>
@endif
</x-master-card>
</div>
<div class="overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-sm shadow-slate-200/50 ring-1 ring-white/70">
<div class="border-b border-slate-200 bg-gradient-to-r from-white via-slate-50 to-rose-50/30 px-6 py-5">
<div class="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
<div>
<div class="text-lg font-semibold text-slate-800">Daftar Siswa Keluar</div>
<div class="mt-1 text-sm text-slate-500">Arsip ini terisi otomatis saat status siswa diubah menjadi keluar.</div>
</div>
<div class="inline-flex items-center gap-2 rounded-full bg-slate-900 px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.16em] text-white shadow-sm">
<i class="fa-solid fa-box-archive"></i>
{{ $studentExits->total() }} data terfilter
</div>
</div>
</div>
<x-master-card title="Daftar Siswa Keluar" subtitle="Arsip ini terisi otomatis saat status siswa diubah menjadi Keluar.">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-slate-200">
<thead class="bg-slate-50">
@@ -28,44 +138,75 @@
</thead>
<tbody class="divide-y divide-slate-100 bg-white">
@forelse ($studentExits as $studentExit)
<tr>
<tr class="transition hover:bg-slate-50/80">
<td class="px-6 py-4 text-sm text-slate-700">
<div class="font-semibold text-slate-800">{{ $studentExit->full_name }}</div>
<div class="mt-1 text-xs text-slate-500">NIS: {{ $studentExit->student_number ?: '-' }} · NISN: {{ $studentExit->nisn ?: '-' }}</div>
<div class="flex items-start gap-3">
<div class="flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl bg-rose-100 text-sm font-bold text-rose-700">
{{ strtoupper(mb_substr($studentExit->full_name, 0, 1)) }}
</div>
<div>
<div class="font-semibold text-slate-800">{{ $studentExit->full_name }}</div>
<div class="mt-1 text-xs text-slate-500">NIS: {{ $studentExit->student_number ?: '-' }} · NISN: {{ $studentExit->nisn ?: '-' }}</div>
</div>
</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div>{{ $studentExit->academicYear?->name ?: '-' }}</div>
<div class="font-medium text-slate-700">{{ $studentExit->academicYear?->name ?: '-' }}</div>
<div>{{ $studentExit->department?->name ?: '-' }}</div>
<div>{{ $studentExit->classRoom?->name ?: '-' }}</div>
<div class="text-xs text-slate-500">{{ $studentExit->classRoom?->name ?: '-' }}</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div class="inline-flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-3 py-2">
<i class="fa-solid fa-calendar-days text-xs text-slate-400"></i>
<span>{{ $studentExit->exit_date?->format('d-m-Y') ?: '-' }}</span>
</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">{{ $studentExit->exit_date?->format('d-m-Y') ?: '-' }}</td>
<td class="px-6 py-4 text-sm text-slate-600">
@if ($studentExit->exit_letter_file_path)
<span class="inline-flex rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700">Sudah diupload</span>
<span class="inline-flex items-center gap-2 rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700">
<i class="fa-solid fa-circle-check"></i>
<span>Sudah diupload</span>
</span>
<div class="mt-2 text-xs text-slate-500">{{ $studentExit->exit_letter_file_name }}</div>
<div class="mt-3">
<a href="{{ route('student-exits.preview-letter', $studentExit) }}" class="inline-flex items-center rounded-xl border border-sky-200 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-50">Preview Surat</a>
<a href="{{ route('student-exits.preview-letter', $studentExit) }}" class="inline-flex items-center gap-2 rounded-xl border border-sky-200 bg-sky-50 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-100">
<i class="fa-solid fa-eye"></i>
<span>Preview Surat</span>
</a>
</div>
@else
<span class="inline-flex rounded-full bg-amber-100 px-3 py-1 text-xs font-semibold text-amber-700">Surat Keluar belum di upload</span>
<span class="inline-flex items-center gap-2 rounded-full bg-amber-100 px-3 py-1 text-xs font-semibold text-amber-700">
<i class="fa-solid fa-triangle-exclamation"></i>
<span>Belum upload surat</span>
</span>
@endif
</td>
<td class="px-6 py-4 text-sm text-slate-600">{{ $studentExit->exit_reason ?: '-' }}</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div class="line-clamp-3">{{ $studentExit->exit_reason ?: '-' }}</div>
</td>
<td class="px-6 py-4 text-sm">
<a href="{{ route('student-exits.show', $studentExit) }}" class="rounded-xl border border-sky-200 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-50">Detail Arsip</a>
<a href="{{ route('student-exits.show', $studentExit) }}" class="inline-flex items-center gap-2 rounded-xl border border-sky-200 bg-sky-50 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-100">
<i class="fa-solid fa-arrow-up-right-from-square"></i>
<span>Detail Arsip</span>
</a>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-6 py-10 text-center text-sm text-slate-500">Belum ada data siswa keluar.</td>
<td colspan="6" class="px-6 py-12 text-center text-sm text-slate-500">
<div class="mx-auto max-w-md">
<div class="text-base font-semibold text-slate-700">Belum ada data siswa keluar.</div>
<div class="mt-2 text-sm text-slate-500">Arsip keluar akan muncul otomatis saat status siswa diubah menjadi keluar.</div>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
<div class="mt-5">
{{ $studentExits->links() }}
</div>
</x-master-card>
<div class="mt-6 flex justify-end">
{{ $studentExits->onEachSide(1)->links() }}
</div>
</x-master-page>
@@ -1,38 +1,141 @@
<x-master-page title="Preview Surat Keluar" description="Lihat surat keluar siswa langsung di browser bila format mendukung.">
<div class="grid grid-cols-1 gap-6 xl:grid-cols-3">
<x-master-card title="Informasi Surat Keluar" subtitle="Metadata surat keluar siswa.">
<div class="space-y-4 text-sm text-slate-600">
<div><span class="font-semibold text-slate-800">Nama Siswa:</span> {{ $studentExit->full_name }}</div>
<div><span class="font-semibold text-slate-800">Nama File:</span> {{ $studentExit->exit_letter_file_name ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Tipe MIME:</span> {{ $mimeType ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Ukuran:</span> {{ $studentExit->exit_letter_file_size ? number_format($studentExit->exit_letter_file_size / 1024, 1) . ' KB' : '-' }}</div>
<div><span class="font-semibold text-slate-800">Tanggal Keluar:</span> {{ $studentExit->exit_date?->format('d-m-Y') ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Catatan:</span> {{ $studentExit->exit_reason ?: '-' }}</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-rose-50/40 p-6 shadow-sm shadow-slate-200/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-slate-500">Nama Siswa</div>
<div class="mt-3 text-lg font-bold text-slate-900">{{ $studentExit->full_name }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-rose-600 ring-1 ring-rose-100 backdrop-blur-sm">
<i class="fa-solid fa-user text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-sky-200 bg-gradient-to-br from-white via-sky-50/50 to-sky-100/60 p-6 shadow-sm shadow-sky-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-sky-700">Format File</div>
<div class="mt-3 text-lg font-bold text-sky-800">{{ strtoupper(pathinfo($studentExit->exit_letter_file_name ?: '', PATHINFO_EXTENSION) ?: 'FILE') }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-sky-600 ring-1 ring-sky-100 backdrop-blur-sm">
<i class="fa-solid fa-file-lines text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-emerald-200 bg-gradient-to-br from-white via-emerald-50/50 to-emerald-100/60 p-6 shadow-sm shadow-emerald-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-emerald-700">Ukuran File</div>
<div class="mt-3 text-lg font-bold text-emerald-800">{{ $studentExit->exit_letter_file_size ? number_format($studentExit->exit_letter_file_size / 1024, 1) . ' KB' : '-' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-emerald-600 ring-1 ring-emerald-100 backdrop-blur-sm">
<i class="fa-solid fa-weight-scale text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-fuchsia-200 bg-gradient-to-br from-white via-fuchsia-50/50 to-fuchsia-100/60 p-6 shadow-sm shadow-fuchsia-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-fuchsia-700">Tanggal Keluar</div>
<div class="mt-3 text-lg font-bold text-fuchsia-800">{{ $studentExit->exit_date?->format('d M Y') ?: 'Belum diisi' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-fuchsia-600 ring-1 ring-fuchsia-100 backdrop-blur-sm">
<i class="fa-solid fa-calendar-right text-lg"></i>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 gap-6 xl:grid-cols-[380px_minmax(0,1fr)]">
<x-master-card title="Informasi Surat Keluar" subtitle="Metadata file dan ringkasan arsip siswa keluar.">
<div class="mb-4 flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex h-11 w-11 items-center justify-center rounded-2xl bg-rose-100 text-rose-600">
<i class="fa-solid fa-folder-open text-lg"></i>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Metadata Dokumen</div>
<div class="mt-1 text-sm font-semibold text-slate-800">Informasi cepat sebelum file dibuka atau diunduh</div>
</div>
</div>
<div class="space-y-3 text-sm text-slate-600">
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-user"></i><span>Nama Siswa</span></div>
<div class="mt-2 font-semibold text-slate-800">{{ $studentExit->full_name }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-file-signature"></i><span>Nama File</span></div>
<div class="mt-2 break-words font-semibold text-slate-800">{{ $studentExit->exit_letter_file_name ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-code"></i><span>Tipe MIME</span></div>
<div class="mt-2 text-slate-700">{{ $mimeType ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-weight-scale"></i><span>Ukuran File</span></div>
<div class="mt-2 text-slate-700">{{ $studentExit->exit_letter_file_size ? number_format($studentExit->exit_letter_file_size / 1024, 1) . ' KB' : '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-calendar-days"></i><span>Tanggal Keluar</span></div>
<div class="mt-2 text-slate-700">{{ $studentExit->exit_date?->format('d-m-Y') ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-align-left"></i><span>Catatan</span></div>
<div class="mt-2 leading-6 text-slate-700">{{ $studentExit->exit_reason ?: '-' }}</div>
</div>
</div>
<div class="mt-6 flex flex-wrap gap-3">
<a href="{{ $fileUrl }}" target="_blank" rel="noopener noreferrer" class="rounded-2xl bg-sky-600 px-4 py-3 text-sm font-semibold text-white transition hover:bg-sky-700">Buka File Asli</a>
<a href="{{ route('student-exits.index') }}" class="rounded-2xl border border-slate-200 px-4 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">Kembali</a>
<a href="{{ $fileUrl }}" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 rounded-2xl bg-sky-600 px-4 py-3 text-sm font-semibold text-white transition hover:bg-sky-700">
<i class="fa-solid fa-up-right-from-square"></i>
<span>Buka File Asli</span>
</a>
<a href="{{ route('student-exits.show', $studentExit) }}" class="inline-flex items-center gap-2 rounded-2xl border border-slate-200 px-4 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">
<i class="fa-solid fa-arrow-left"></i>
<span>Kembali ke Detail</span>
</a>
</div>
</x-master-card>
<div class="xl:col-span-2">
<x-master-card title="Tampilan Surat Keluar" subtitle="Preview langsung untuk gambar dan PDF.">
@if ($isImage)
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-slate-50 p-4">
<img src="{{ $fileUrl }}" alt="{{ $studentExit->exit_letter_file_name }}" class="mx-auto max-h-[70vh] rounded-xl object-contain shadow-sm">
<x-master-card title="Tampilan Surat Keluar" subtitle="Preview langsung untuk gambar dan PDF.">
<div class="mb-4 flex items-center justify-between gap-3 rounded-2xl border border-slate-200 bg-gradient-to-r from-white via-slate-50 to-rose-50/30 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Mode Preview</div>
<div class="mt-1 text-sm font-semibold text-slate-800">
@if ($isImage)
Gambar langsung di browser
@elseif ($isPdf)
PDF embed di browser
@else
Preview tidak didukung
@endif
</div>
@elseif ($isPdf)
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-slate-50">
<iframe src="{{ $fileUrl }}" class="h-[75vh] w-full rounded-2xl" title="Preview Surat Keluar"></iframe>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-rose-600 ring-1 ring-rose-100 backdrop-blur-sm">
<i class="fa-solid {{ $isImage ? 'fa-image' : ($isPdf ? 'fa-file-pdf' : 'fa-circle-info') }} text-lg"></i>
</div>
</div>
@if ($isImage)
<div class="overflow-hidden rounded-3xl border border-slate-200 bg-slate-50 p-4 shadow-sm ring-1 ring-white/70">
<img src="{{ $fileUrl }}" alt="{{ $studentExit->exit_letter_file_name }}" class="mx-auto max-h-[72vh] rounded-2xl object-contain shadow-sm">
</div>
@elseif ($isPdf)
<div class="overflow-hidden rounded-3xl border border-slate-200 bg-slate-50 shadow-sm ring-1 ring-white/70">
<iframe src="{{ $fileUrl }}" class="h-[78vh] w-full rounded-3xl" title="Preview Surat Keluar"></iframe>
</div>
@else
<div class="rounded-3xl border border-dashed border-slate-300 bg-slate-50 p-10 text-center shadow-sm ring-1 ring-white/70">
<div class="mx-auto flex h-14 w-14 items-center justify-center rounded-2xl bg-white text-slate-500 shadow-sm ring-1 ring-slate-200">
<i class="fa-solid fa-file-circle-question text-xl"></i>
</div>
@else
<div class="rounded-2xl border border-dashed border-slate-300 bg-slate-50 p-10 text-center">
<div class="text-lg font-semibold text-slate-800">Preview tidak tersedia</div>
<p class="mt-2 text-sm text-slate-500">Format file ini belum didukung untuk preview langsung di browser. Silakan gunakan tombol buka file asli.</p>
</div>
@endif
</x-master-card>
</div>
<div class="mt-4 text-lg font-semibold text-slate-800">Preview tidak tersedia</div>
<p class="mt-2 text-sm text-slate-500">Format file ini belum didukung untuk preview langsung di browser. Gunakan tombol buka file asli untuk melihat dokumen.</p>
</div>
@endif
</x-master-card>
</div>
</x-master-page>
+116 -35
View File
@@ -1,44 +1,109 @@
<x-master-page title="Detail Arsip Siswa Keluar" description="Lihat ringkasan arsip siswa keluar dan kelola surat keluar tanpa masuk ke modul siswa aktif.">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-rose-50/40 p-6 shadow-sm shadow-slate-200/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-slate-500">Status Arsip</div>
<div class="mt-3 text-lg font-bold text-slate-900">Siswa Keluar</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-rose-600 ring-1 ring-rose-100 backdrop-blur-sm">
<i class="fa-solid fa-box-archive text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-sky-200 bg-gradient-to-br from-white via-sky-50/50 to-sky-100/60 p-6 shadow-sm shadow-sky-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-sky-700">Tanggal Keluar</div>
<div class="mt-3 text-lg font-bold text-sky-800">{{ $studentExit->exit_date?->format('d M Y') ?: 'Belum diisi' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-sky-600 ring-1 ring-sky-100 backdrop-blur-sm">
<i class="fa-solid fa-calendar-right text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border {{ $studentExit->exit_letter_file_path ? 'border-emerald-200 bg-gradient-to-br from-white via-emerald-50/50 to-emerald-100/60' : 'border-amber-200 bg-gradient-to-br from-white via-amber-50/50 to-amber-100/60' }} p-6 shadow-sm {{ $studentExit->exit_letter_file_path ? 'shadow-emerald-100/50' : 'shadow-amber-100/50' }} ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium {{ $studentExit->exit_letter_file_path ? 'text-emerald-700' : 'text-amber-700' }}">Surat Keluar</div>
<div class="mt-3 text-lg font-bold {{ $studentExit->exit_letter_file_path ? 'text-emerald-800' : 'text-amber-800' }}">{{ $studentExit->exit_letter_file_path ? 'Sudah tersedia' : 'Belum diupload' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 {{ $studentExit->exit_letter_file_path ? 'text-emerald-600 ring-1 ring-emerald-100' : 'text-amber-600 ring-1 ring-amber-100' }} backdrop-blur-sm">
<i class="fa-solid {{ $studentExit->exit_letter_file_path ? 'fa-file-circle-check' : 'fa-file-circle-xmark' }} text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-fuchsia-200 bg-gradient-to-br from-white via-fuchsia-50/50 to-fuchsia-100/60 p-6 shadow-sm shadow-fuchsia-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-fuchsia-700">Kelas Terakhir</div>
<div class="mt-3 text-lg font-bold text-fuchsia-800">{{ $studentExit->classRoom?->name ?: '-' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-fuchsia-600 ring-1 ring-fuchsia-100 backdrop-blur-sm">
<i class="fa-solid fa-school text-lg"></i>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 gap-6 xl:grid-cols-3">
<div class="xl:col-span-2 space-y-6">
<x-master-card title="Identitas Siswa" subtitle="Snapshot data terakhir saat siswa dipindahkan ke arsip keluar.">
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Nama Lengkap</div>
<div class="mt-1 text-sm font-semibold text-slate-900">{{ $studentExit->full_name }}</div>
<div class="mb-4 flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex h-11 w-11 items-center justify-center rounded-2xl bg-rose-100 text-rose-600">
<i class="fa-solid fa-id-card text-lg"></i>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Status Arsip</div>
<div class="mt-1"><span class="inline-flex rounded-full bg-rose-100 px-3 py-1 text-xs font-semibold text-rose-700">Siswa Keluar</span></div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Snapshot Identitas</div>
<div class="mt-1 text-sm font-semibold text-slate-800">Identitas dasar siswa saat masuk arsip keluar</div>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">NIS</div>
<div class="mt-1 text-sm text-slate-700">{{ $studentExit->student_number ?: '-' }}</div>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-user"></i><span>Nama Lengkap</span></div>
<div class="mt-2 text-sm font-semibold text-slate-900">{{ $studentExit->full_name }}</div>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">NISN</div>
<div class="mt-1 text-sm text-slate-700">{{ $studentExit->nisn ?: '-' }}</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-badge-check"></i><span>Status Arsip</span></div>
<div class="mt-2"><span class="inline-flex items-center gap-2 rounded-full bg-rose-100 px-3 py-1 text-xs font-semibold text-rose-700"><i class="fa-solid fa-right-from-bracket"></i><span>Siswa Keluar</span></span></div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-hashtag"></i><span>NIS</span></div>
<div class="mt-2 text-sm text-slate-700">{{ $studentExit->student_number ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-fingerprint"></i><span>NISN</span></div>
<div class="mt-2 text-sm text-slate-700">{{ $studentExit->nisn ?: '-' }}</div>
</div>
</div>
</x-master-card>
<x-master-card title="Akademik Terakhir" subtitle="Data akademik terakhir sebelum status siswa menjadi keluar.">
<div class="grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-4">
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Tahun Angkatan</div>
<div class="mt-1 text-sm text-slate-700">{{ $studentExit->academicYear?->name ?: '-' }}</div>
<div class="mb-4 flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex h-11 w-11 items-center justify-center rounded-2xl bg-amber-100 text-amber-600">
<i class="fa-solid fa-graduation-cap text-lg"></i>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Program Keahlian</div>
<div class="mt-1 text-sm text-slate-700">{{ $studentExit->department?->name ?: '-' }}</div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Posisi Akademik Terakhir</div>
<div class="mt-1 text-sm font-semibold text-slate-800">Rombel dan relasi akademik sebelum siswa keluar dari sekolah</div>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Kelas Terakhir</div>
<div class="mt-1 text-sm text-slate-700">{{ $studentExit->classRoom?->name ?: '-' }}</div>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-calendar-days"></i><span>Tahun Angkatan</span></div>
<div class="mt-2 text-sm text-slate-700">{{ $studentExit->academicYear?->name ?: '-' }}</div>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Tanggal Masuk</div>
<div class="mt-1 text-sm text-slate-700">{{ $studentExit->entry_date?->format('d-m-Y') ?: '-' }}</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-sitemap"></i><span>Program Keahlian</span></div>
<div class="mt-2 text-sm text-slate-700">{{ $studentExit->department?->name ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-school"></i><span>Kelas Terakhir</span></div>
<div class="mt-2 text-sm text-slate-700">{{ $studentExit->classRoom?->name ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-door-open"></i><span>Tanggal Masuk</span></div>
<div class="mt-2 text-sm text-slate-700">{{ $studentExit->entry_date?->format('d-m-Y') ?: '-' }}</div>
</div>
</div>
</x-master-card>
@@ -48,20 +113,33 @@
<x-master-card title="Dokumen Surat Keluar" subtitle="Upload atau ganti surat keluar langsung dari modul arsip.">
<div class="space-y-4">
@if ($studentExit->exit_letter_file_path)
<div class="rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-4">
<div class="text-sm font-semibold text-emerald-700">Surat keluar sudah tersedia</div>
<div class="mt-1 text-xs text-emerald-600">{{ $studentExit->exit_letter_file_name }}</div>
<div class="mt-3">
<a href="{{ route('student-exits.preview-letter', $studentExit) }}" class="inline-flex items-center rounded-xl border border-sky-200 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-50">Preview Surat</a>
<div class="rounded-2xl border border-emerald-200 bg-gradient-to-r from-emerald-50 to-white px-4 py-4 shadow-sm shadow-emerald-100/40">
<div class="flex items-start gap-3">
<div class="flex h-10 w-10 items-center justify-center rounded-2xl bg-white text-emerald-600 shadow-sm ring-1 ring-emerald-100">
<i class="fa-solid fa-file-circle-check"></i>
</div>
<div>
<div class="text-sm font-semibold text-emerald-700">Surat keluar sudah tersedia</div>
<div class="mt-1 text-xs text-emerald-600">{{ $studentExit->exit_letter_file_name }}</div>
<div class="mt-3">
<a href="{{ route('student-exits.preview-letter', $studentExit) }}" class="inline-flex items-center gap-2 rounded-xl border border-sky-200 bg-sky-50 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-100">
<i class="fa-solid fa-eye"></i>
<span>Preview Surat</span>
</a>
</div>
</div>
</div>
</div>
@else
<div class="rounded-2xl border border-amber-200 bg-amber-50 px-4 py-4 text-sm font-medium text-amber-700">
Surat keluar belum diupload.
<div class="rounded-2xl border border-amber-200 bg-gradient-to-r from-amber-50 to-white px-4 py-4 text-sm font-medium text-amber-700 shadow-sm shadow-amber-100/40">
<div class="flex items-center gap-3">
<i class="fa-solid fa-triangle-exclamation"></i>
<span>Surat keluar belum diupload.</span>
</div>
</div>
@endif
<form method="POST" action="{{ route('student-exits.update', $studentExit) }}" enctype="multipart/form-data" class="space-y-4">
<form method="POST" action="{{ route('student-exits.update', $studentExit) }}" enctype="multipart/form-data" class="space-y-4 rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-rose-50/25 p-4 shadow-sm ring-1 ring-white/70">
@csrf
@method('PATCH')
@@ -70,13 +148,16 @@
<div>
<label for="exit_letter" class="mb-2 block text-sm font-medium text-slate-700">Upload / Ganti Surat Keluar</label>
<input id="exit_letter" name="exit_letter" type="file" accept=".pdf,.jpg,.jpeg,.png" class="block w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-700 file:mr-4 file:rounded-xl file:border-0 file:bg-slate-100 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-slate-700 hover:file:bg-slate-200">
<div class="flex min-h-[3rem] items-center rounded-2xl border border-slate-200 bg-white px-3 py-2 transition focus-within:border-indigo-300 focus-within:ring-4 focus-within:ring-indigo-100">
<input id="exit_letter" name="exit_letter" type="file" accept=".pdf,.jpg,.jpeg,.png" class="block w-full text-sm text-slate-700 file:mr-3 file:rounded-xl file:border-0 file:bg-slate-100 file:px-4 file:py-2.5 file:text-sm file:font-semibold file:text-slate-700 hover:file:bg-slate-200">
</div>
<p class="mt-2 text-xs text-slate-500">Format: PDF, JPG, JPEG, PNG. Maksimal 2 MB.</p>
<x-input-error :messages="$errors->get('exit_letter')" class="mt-2" />
</div>
<button type="submit" class="inline-flex items-center rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
Simpan Arsip Keluar
<button type="submit" class="inline-flex items-center gap-2 rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
<i class="fa-solid fa-floppy-disk"></i>
<span>Simpan Arsip Keluar</span>
</button>
</form>
</div>
@@ -1,5 +1,6 @@
<x-master-page title="Kenaikan Kelas / Promosi Siswa" description="Proses semi otomatis untuk memindahkan siswa aktif ke kelas/tahun ajaran berikutnya.">
<x-master-card title="Filter Promosi" subtitle="Pilih tahun ajaran, lalu fokuskan proses per kelas agar pengecualian siswa lebih mudah dikelola.">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-lime-50/30 p-4 shadow-sm ring-1 ring-white/70">
<form method="GET" action="{{ route('student-promotions.index') }}" class="grid grid-cols-1 gap-4 lg:grid-cols-4 lg:items-end">
<div>
<label for="from_academic_year_id" class="mb-2 block text-sm font-medium text-slate-700">Tahun Ajaran Asal</label>
@@ -38,11 +39,13 @@
</div>
<div>
<button type="submit" class="inline-flex w-full items-center justify-center rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
Muat Rekomendasi
<button type="submit" class="inline-flex w-full items-center justify-center gap-2 rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
<i class="fa-solid fa-wand-magic-sparkles"></i>
<span>Muat Rekomendasi</span>
</button>
</div>
</form>
</div>
</x-master-card>
<x-master-card title="Kenaikan Kelas per Rombel" subtitle="Mayoritas siswa biasanya mengikuti hasil kelasnya, lalu admin tinggal mengubah siswa pengecualian.">
@@ -63,32 +66,33 @@
@foreach ($classSummaries as $summary)
<a
href="{{ route('student-promotions.index', ['from_academic_year_id' => $filters['from_academic_year_id'], 'to_academic_year_id' => $filters['to_academic_year_id'], 'class_id' => $summary->id]) }}"
class="group rounded-3xl border border-slate-200 bg-white p-5 transition hover:border-indigo-200 hover:bg-indigo-50/40"
class="group rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-lime-50/25 p-5 shadow-sm ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:border-indigo-200 hover:bg-indigo-50/40 hover:shadow-lg hover:shadow-slate-200/60"
>
<div class="flex flex-wrap items-start justify-between gap-4">
<div>
<div class="text-lg font-semibold text-slate-800">{{ $summary->name }}</div>
<div class="mt-1 text-sm text-slate-500">{{ $summary->department_name ?: '-' }} • Level {{ $summary->level }}</div>
</div>
<div class="rounded-2xl bg-slate-100 px-3 py-2 text-right">
<div class="rounded-2xl bg-white/80 px-3 py-2 text-right shadow-sm ring-1 ring-white/70 backdrop-blur-sm">
<div class="text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500">Siswa Aktif</div>
<div class="mt-1 text-2xl font-bold text-slate-800">{{ $summary->active_students_count }}</div>
</div>
</div>
<div class="mt-4 grid grid-cols-1 gap-3 md:grid-cols-2">
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3">
<div class="rounded-2xl border border-slate-200 bg-white/80 px-4 py-3 shadow-sm ring-1 ring-white/70 backdrop-blur-sm">
<div class="text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500">Rekomendasi Default</div>
<div class="mt-1 font-semibold text-slate-800">{{ $summary->recommendation_label }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3">
<div class="rounded-2xl border border-slate-200 bg-white/80 px-4 py-3 shadow-sm ring-1 ring-white/70 backdrop-blur-sm">
<div class="text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500">Target Umum</div>
<div class="mt-1 font-semibold text-slate-800">{{ $summary->recommendation_target_name ?: 'Perlu cek setelah kelas dipilih' }}</div>
</div>
</div>
<div class="mt-4 inline-flex items-center text-sm font-semibold text-indigo-700 transition group-hover:translate-x-0.5">
Proses kelas ini →
<div class="mt-4 inline-flex items-center gap-2 text-sm font-semibold text-indigo-700 transition group-hover:translate-x-0.5">
<i class="fa-solid fa-arrow-right"></i>
<span>Proses kelas ini</span>
</div>
</a>
@endforeach
@@ -116,44 +120,52 @@
</div>
@endif
<div class="mb-6 rounded-3xl border border-slate-200 bg-slate-50 px-5 py-4">
<div class="mb-6 rounded-3xl border border-slate-200 bg-gradient-to-r from-white via-slate-50 to-lime-50/30 px-5 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex flex-wrap items-start justify-between gap-4">
<div>
<div class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Kelas Sedang Diproses</div>
<div class="mt-2 text-2xl font-bold text-slate-800">{{ $selectedClass->name }}</div>
<div class="mt-1 text-sm text-slate-500">{{ $selectedClass->department?->name ?: '-' }} • {{ $selectedClass->active_students_count }} siswa aktif</div>
</div>
<a href="{{ route('student-promotions.index', ['from_academic_year_id' => $filters['from_academic_year_id'], 'to_academic_year_id' => $filters['to_academic_year_id']]) }}" class="inline-flex items-center rounded-2xl border border-slate-200 bg-white px-4 py-2 text-sm font-semibold text-slate-700 transition hover:bg-slate-100">
Ganti Kelas
<a href="{{ route('student-promotions.index', ['from_academic_year_id' => $filters['from_academic_year_id'], 'to_academic_year_id' => $filters['to_academic_year_id']]) }}" class="inline-flex items-center gap-2 rounded-2xl border border-slate-200 bg-white px-4 py-2 text-sm font-semibold text-slate-700 transition hover:bg-slate-100">
<i class="fa-solid fa-arrow-left"></i>
<span>Ganti Kelas</span>
</a>
</div>
</div>
<div class="mb-6 grid grid-cols-1 gap-4 md:grid-cols-4">
<div class="rounded-3xl border border-emerald-200 bg-emerald-50 px-5 py-4" data-promotion-counter-card="promote">
<div class="rounded-3xl border border-emerald-200 bg-gradient-to-br from-white via-emerald-50/50 to-emerald-100/60 px-5 py-4 shadow-sm ring-1 ring-white/70" data-promotion-counter-card="promote">
<div class="text-xs font-semibold uppercase tracking-[0.18em] text-emerald-600">Naik Kelas</div>
<div class="mt-2 text-3xl font-bold text-emerald-700" data-promotion-counter="promote">{{ $promotionCount }}</div>
<div class="mt-1 text-sm text-emerald-600">Siswa yang akan naik ke level berikutnya.</div>
</div>
<div class="rounded-3xl border border-amber-200 bg-amber-50 px-5 py-4" data-promotion-counter-card="repeat">
<div class="rounded-3xl border border-amber-200 bg-gradient-to-br from-white via-amber-50/50 to-amber-100/60 px-5 py-4 shadow-sm ring-1 ring-white/70" data-promotion-counter-card="repeat">
<div class="text-xs font-semibold uppercase tracking-[0.18em] text-amber-600">Tinggal Kelas</div>
<div class="mt-2 text-3xl font-bold text-amber-700" data-promotion-counter="repeat">{{ $repeatCount }}</div>
<div class="mt-1 text-sm text-amber-600">Siswa yang akan tetap di level yang sama.</div>
</div>
<div class="rounded-3xl border border-sky-200 bg-sky-50 px-5 py-4" data-promotion-counter-card="graduate">
<div class="rounded-3xl border border-sky-200 bg-gradient-to-br from-white via-sky-50/50 to-sky-100/60 px-5 py-4 shadow-sm ring-1 ring-white/70" data-promotion-counter-card="graduate">
<div class="text-xs font-semibold uppercase tracking-[0.18em] text-sky-600">Lulus</div>
<div class="mt-2 text-3xl font-bold text-sky-700" data-promotion-counter="graduate">{{ $graduateCount }}</div>
<div class="mt-1 text-sm text-sky-600">Siswa kelas akhir yang akan diluluskan.</div>
</div>
<div class="rounded-3xl border border-slate-200 bg-slate-50 px-5 py-4" data-promotion-counter-card="skip">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-slate-100/80 px-5 py-4 shadow-sm ring-1 ring-white/70" data-promotion-counter-card="skip">
<div class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Lewati</div>
<div class="mt-2 text-3xl font-bold text-slate-700" data-promotion-counter="skip">{{ $skipCount }}</div>
<div class="mt-1 text-sm text-slate-500">Siswa yang tidak ikut diproses pada batch ini.</div>
</div>
</div>
<div class="mb-6 rounded-3xl border border-indigo-200 bg-indigo-50 px-5 py-4 text-sm text-indigo-700" data-promotion-live-summary>
Ringkasan live: <span class="font-semibold">{{ $promotionCount }}</span> naik, <span class="font-semibold">{{ $repeatCount }}</span> tinggal kelas, <span class="font-semibold">{{ $graduateCount }}</span> lulus, dan <span class="font-semibold">{{ $skipCount }}</span> dilewati.
<div class="mb-6 rounded-3xl border border-indigo-200 bg-gradient-to-r from-indigo-50 to-white px-5 py-4 text-sm text-indigo-700 shadow-sm ring-1 ring-white/70" data-promotion-live-summary>
<div class="flex items-center gap-3">
<div class="inline-flex h-10 w-10 items-center justify-center rounded-2xl bg-white text-indigo-600 shadow-sm">
<i class="fa-solid fa-chart-line"></i>
</div>
<div>
Ringkasan live: <span class="font-semibold">{{ $promotionCount }}</span> naik, <span class="font-semibold">{{ $repeatCount }}</span> tinggal kelas, <span class="font-semibold">{{ $graduateCount }}</span> lulus, dan <span class="font-semibold">{{ $skipCount }}</span> dilewati.
</div>
</div>
</div>
<form method="POST" action="{{ route('student-promotions.store') }}" class="space-y-6" data-student-promotion-form>
@@ -162,12 +174,12 @@
<input type="hidden" name="to_academic_year_id" value="{{ $filters['to_academic_year_id'] }}">
<input type="hidden" name="class_id" value="{{ $filters['class_id'] }}">
<div class="flex flex-wrap items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-5 py-4" data-promotion-toolbar data-class-level="{{ strtoupper((string) $selectedClass->level) }}">
<div class="flex flex-wrap items-center gap-3 rounded-3xl border border-slate-200 bg-gradient-to-r from-white via-slate-50 to-indigo-50/25 px-5 py-4 shadow-sm ring-1 ring-white/70" data-promotion-toolbar data-class-level="{{ strtoupper((string) $selectedClass->level) }}">
<div class="text-sm font-medium text-slate-700">Aksi cepat untuk kelas ini:</div>
<button type="button" class="rounded-xl border border-emerald-200 px-4 py-2 text-xs font-semibold text-emerald-700 transition hover:bg-emerald-50" data-promotion-bulk-action="promote">Set semua → Naik Kelas</button>
<button type="button" class="rounded-xl border border-sky-200 px-4 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-50" data-promotion-bulk-action="graduate">Set semua → Lulus</button>
<button type="button" class="rounded-xl border border-amber-200 px-4 py-2 text-xs font-semibold text-amber-700 transition hover:bg-amber-50" data-promotion-bulk-action="repeat">Set semua → Tinggal Kelas</button>
<button type="button" class="rounded-xl border border-slate-200 px-4 py-2 text-xs font-semibold text-slate-700 transition hover:bg-slate-100" data-promotion-bulk-action="skip">Set semua → Lewati</button>
<button type="button" class="inline-flex items-center gap-2 rounded-xl border border-emerald-200 px-4 py-2 text-xs font-semibold text-emerald-700 transition hover:bg-emerald-50" data-promotion-bulk-action="promote"><i class="fa-solid fa-arrow-trend-up"></i><span>Set semua → Naik Kelas</span></button>
<button type="button" class="inline-flex items-center gap-2 rounded-xl border border-sky-200 px-4 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-50" data-promotion-bulk-action="graduate"><i class="fa-solid fa-graduation-cap"></i><span>Set semua → Lulus</span></button>
<button type="button" class="inline-flex items-center gap-2 rounded-xl border border-amber-200 px-4 py-2 text-xs font-semibold text-amber-700 transition hover:bg-amber-50" data-promotion-bulk-action="repeat"><i class="fa-solid fa-rotate-left"></i><span>Set semua → Tinggal Kelas</span></button>
<button type="button" class="inline-flex items-center gap-2 rounded-xl border border-slate-200 px-4 py-2 text-xs font-semibold text-slate-700 transition hover:bg-slate-100" data-promotion-bulk-action="skip"><i class="fa-solid fa-forward"></i><span>Set semua → Lewati</span></button>
</div>
@if (strtoupper((string) $selectedClass->level) === 'XII')
@@ -179,7 +191,7 @@
<div class="hidden rounded-3xl border px-5 py-4 text-sm" data-promotion-capacity-summary></div>
<div class="hidden rounded-3xl border border-indigo-200 bg-indigo-50 px-5 py-4 text-sm text-indigo-700" data-promotion-final-preview></div>
<div class="overflow-x-auto rounded-3xl border border-slate-200">
<div class="overflow-x-auto rounded-3xl border border-slate-200 shadow-sm ring-1 ring-white/70">
<table class="min-w-full divide-y divide-slate-200">
<thead class="bg-slate-50">
<tr>
@@ -235,11 +247,18 @@
default => 'bg-slate-100 text-slate-700',
};
@endphp
<tr class="align-top hover:bg-slate-50/60 {{ $rowErrors->isNotEmpty() ? 'bg-rose-50/60' : '' }}">
<tr class="align-top transition hover:bg-slate-50/60 {{ $rowErrors->isNotEmpty() ? 'bg-rose-50/60' : '' }}">
<td class="px-4 py-4">
<input type="hidden" name="promotions[{{ $loop->index }}][student_id]" value="{{ $student->id }}">
<div class="font-semibold text-slate-800">{{ $student->full_name }}</div>
<div class="mt-1 text-xs text-slate-500">NIS: {{ $student->student_number ?: '-' }}</div>
<div class="flex items-start gap-3">
<div class="flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl bg-lime-100 text-sm font-bold text-lime-700">
{{ strtoupper(mb_substr($student->full_name, 0, 1)) }}
</div>
<div>
<div class="font-semibold text-slate-800">{{ $student->full_name }}</div>
<div class="mt-1 text-xs text-slate-500">NIS: {{ $student->student_number ?: '-' }}</div>
</div>
</div>
@if ($rowErrors->isNotEmpty())
<div class="mt-3 rounded-2xl border border-rose-200 bg-rose-50 px-3 py-2 text-xs text-rose-700">
<div class="font-semibold">Perlu diperbaiki</div>
@@ -257,15 +276,16 @@
<div class="text-xs text-slate-500">{{ $student->department?->name ?: '-' }}</div>
</td>
<td class="px-4 py-4">
<div class="inline-flex rounded-full px-3 py-1 text-xs font-semibold {{ $badgeClass }}">
{{ $recommendation['label'] }}
<div class="inline-flex items-center gap-2 rounded-full px-3 py-1 text-xs font-semibold {{ $badgeClass }}">
<i class="fa-solid {{ $recommendation['action'] === 'promote' ? 'fa-arrow-trend-up' : ($recommendation['action'] === 'repeat' ? 'fa-rotate-left' : ($recommendation['action'] === 'graduate' ? 'fa-graduation-cap' : 'fa-forward')) }}"></i>
<span>{{ $recommendation['label'] }}</span>
</div>
@if ($recommendation['target_class_name'])
<div class="mt-2 text-xs text-slate-500">Target: {{ $recommendation['target_class_name'] }}</div>
@endif
</td>
<td class="px-4 py-4">
<select name="promotions[{{ $loop->index }}][action]" class="promotion-action-select w-44 rounded-2xl border {{ $errors->has($actionField) ? 'border-rose-300 bg-rose-50' : 'border-slate-200 bg-white' }} px-4 py-3 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-2 focus:ring-indigo-100" data-default-action="{{ $recommendation['action'] }}">
<select name="promotions[{{ $loop->index }}][action]" class="promotion-action-select w-44 rounded-2xl border {{ $errors->has($actionField) ? 'border-rose-300 bg-rose-50' : 'border-slate-200 bg-white shadow-sm ring-1 ring-white/70' }} px-4 py-3 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-2 focus:ring-indigo-100" data-default-action="{{ $recommendation['action'] }}">
<option value="promote" @selected($oldAction === 'promote')>Naik Kelas</option>
<option value="repeat" @selected($oldAction === 'repeat')>Tinggal Kelas</option>
<option value="graduate" @selected($oldAction === 'graduate')>Lulus</option>
@@ -281,7 +301,7 @@
<select
name="promotions[{{ $loop->index }}][target_class_id]"
class="promotion-target-select {{ $oldAction === 'graduate' ? 'hidden' : '' }} mt-0 w-60 rounded-2xl border {{ $errors->has($targetClassField) ? 'border-rose-300 bg-rose-50' : 'border-slate-200 bg-white' }} px-4 py-3 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-2 focus:ring-indigo-100"
class="promotion-target-select {{ $oldAction === 'graduate' ? 'hidden' : '' }} mt-0 w-60 rounded-2xl border {{ $errors->has($targetClassField) ? 'border-rose-300 bg-rose-50' : 'border-slate-200 bg-white shadow-sm ring-1 ring-white/70' }} px-4 py-3 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-2 focus:ring-indigo-100"
data-options-promote='@json($promoteClassOptions)'
data-options-repeat='@json($repeatClassOptions)'
data-selected-promote="{{ $selectedPromote }}"
@@ -307,7 +327,7 @@
@endif
</td>
<td class="px-4 py-4">
<textarea name="promotions[{{ $loop->index }}][notes]" rows="3" class="w-64 rounded-2xl border {{ $errors->has($notesField) ? 'border-rose-300 bg-rose-50' : 'border-slate-200 bg-white' }} px-4 py-3 text-sm text-slate-700 placeholder:text-slate-400 focus:border-indigo-300 focus:outline-none focus:ring-2 focus:ring-indigo-100" placeholder="Contoh: Tinggal kelas karena dokumen akademik belum lengkap...">{{ old($notesField) }}</textarea>
<textarea name="promotions[{{ $loop->index }}][notes]" rows="3" class="w-64 rounded-2xl border {{ $errors->has($notesField) ? 'border-rose-300 bg-rose-50' : 'border-slate-200 bg-white shadow-sm ring-1 ring-white/70' }} px-4 py-3 text-sm text-slate-700 placeholder:text-slate-400 focus:border-indigo-300 focus:outline-none focus:ring-2 focus:ring-indigo-100" placeholder="Contoh: Tinggal kelas karena dokumen akademik belum lengkap...">{{ old($notesField) }}</textarea>
</td>
</tr>
@endforeach
@@ -315,13 +335,14 @@
</table>
</div>
<div class="flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-5 py-4">
<div class="flex flex-wrap items-center justify-between gap-3 rounded-3xl border border-slate-200 bg-gradient-to-r from-white via-slate-50 to-lime-50/25 px-5 py-4 shadow-sm ring-1 ring-white/70">
<div class="space-y-1 text-sm text-slate-500">
<div>Total siswa siap diproses dari kelas ini: <span class="font-semibold text-slate-800">{{ $students->count() }}</span></div>
<div class="text-xs text-slate-400">Tips: pola kerja idealnya set semua sesuai rekomendasi kelas, lalu ubah hanya siswa yang menjadi pengecualian.</div>
</div>
<button type="submit" class="inline-flex items-center rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700" data-promotion-submit-button>
Proses Promosi Kelas Ini
<button type="submit" class="inline-flex items-center gap-2 rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700" data-promotion-submit-button>
<i class="fa-solid fa-paper-plane"></i>
<span>Proses Promosi Kelas Ini</span>
</button>
</div>
</form>
@@ -25,14 +25,15 @@
}
.print-button {
background: #0f172a;
background: linear-gradient(135deg, #0f172a, #1e293b);
color: #ffffff;
border: none;
border-radius: 8px;
border-radius: 10px;
padding: 10px 16px;
font-size: 13px;
font-weight: 700;
cursor: pointer;
box-shadow: 0 10px 24px -14px rgba(15, 23, 42, 0.75);
}
.letterhead {
@@ -82,9 +83,11 @@
.meta-box {
border: 1px solid #374151;
padding: 12px 14px;
padding: 14px 16px;
margin-bottom: 16px;
font-size: 13px;
background: linear-gradient(135deg, #ffffff, #f8fafc);
border-radius: 14px;
}
.access-panel {
@@ -92,6 +95,9 @@
width: 100%;
border: 1px solid #374151;
margin-bottom: 16px;
border-radius: 18px;
overflow: hidden;
background: linear-gradient(135deg, #ffffff, #f8fafc);
}
.access-panel-row {
@@ -125,6 +131,7 @@
letter-spacing: 0.14em;
margin-bottom: 10px;
background: #f9fafb;
border-radius: 999px;
}
.portal-title {
@@ -144,6 +151,7 @@
border: 1px solid #d1d5db;
background: #ffffff;
padding: 10px 12px;
border-radius: 12px;
}
.portal-link-label {
@@ -171,6 +179,7 @@
background: #ffffff;
border: 1px solid #d1d5db;
padding: 10px;
border-radius: 14px;
}
.qr-title {
@@ -236,6 +245,7 @@
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
background: #f8fafc;
}
td.center {
@@ -253,6 +263,10 @@
.notes {
margin-top: 18px;
font-size: 12px;
border: 1px solid #d1d5db;
border-radius: 14px;
padding: 14px 16px;
background: #fcfcfd;
}
.notes-title {
@@ -1,30 +1,65 @@
<x-master-page title="Self Servis Siswa" description="Pantau progres pelengkapan data mandiri siswa dan kelola kode akses portal self servis.">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-5">
<div class="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm shadow-slate-200/50">
<div class="text-sm font-medium text-slate-500">Total Siswa</div>
<div class="mt-3 text-3xl font-bold text-slate-900">{{ $summary['total'] }}</div>
<div class="group rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/40 p-6 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-200/70">
<div class="flex items-start justify-between">
<div>
<div class="text-sm font-medium text-slate-500">Total Siswa</div>
<div class="mt-3 text-3xl font-bold text-slate-900">{{ $summary['total'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-indigo-600 ring-1 ring-indigo-100 backdrop-blur-sm">
<i class="fa-solid fa-users text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm shadow-slate-200/50">
<div class="text-sm font-medium text-slate-500">Belum Mulai</div>
<div class="mt-3 text-3xl font-bold text-slate-900">{{ $summary['not_started'] }}</div>
<div class="group rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-slate-100/70 p-6 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-200/70">
<div class="flex items-start justify-between">
<div>
<div class="text-sm font-medium text-slate-500">Belum Mulai</div>
<div class="mt-3 text-3xl font-bold text-slate-900">{{ $summary['not_started'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-slate-600 ring-1 ring-slate-200 backdrop-blur-sm">
<i class="fa-solid fa-hourglass-start text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-amber-200 bg-amber-50 p-6 shadow-sm shadow-amber-100/50">
<div class="text-sm font-medium text-amber-700">Draft</div>
<div class="mt-3 text-3xl font-bold text-amber-800">{{ $summary['draft'] }}</div>
<div class="group rounded-3xl border border-amber-200 bg-gradient-to-br from-white via-amber-50/50 to-amber-100/60 p-6 shadow-sm shadow-amber-100/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-amber-200/60">
<div class="flex items-start justify-between">
<div>
<div class="text-sm font-medium text-amber-700">Draft</div>
<div class="mt-3 text-3xl font-bold text-amber-800">{{ $summary['draft'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-amber-600 ring-1 ring-amber-100 backdrop-blur-sm">
<i class="fa-solid fa-pen-to-square text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-emerald-200 bg-emerald-50 p-6 shadow-sm shadow-emerald-100/50">
<div class="text-sm font-medium text-emerald-700">Sudah Dikirim</div>
<div class="mt-3 text-3xl font-bold text-emerald-800">{{ $summary['submitted'] }}</div>
<div class="group rounded-3xl border border-emerald-200 bg-gradient-to-br from-white via-emerald-50/50 to-emerald-100/60 p-6 shadow-sm shadow-emerald-100/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-emerald-200/60">
<div class="flex items-start justify-between">
<div>
<div class="text-sm font-medium text-emerald-700">Sudah Dikirim</div>
<div class="mt-3 text-3xl font-bold text-emerald-800">{{ $summary['submitted'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-emerald-600 ring-1 ring-emerald-100 backdrop-blur-sm">
<i class="fa-solid fa-paper-plane text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border {{ $summary['without_code'] > 0 ? 'border-rose-200 bg-rose-50' : 'border-sky-200 bg-sky-50' }} p-6 shadow-sm {{ $summary['without_code'] > 0 ? 'shadow-rose-100/50' : 'shadow-sky-100/50' }}">
<div class="text-sm font-medium {{ $summary['without_code'] > 0 ? 'text-rose-700' : 'text-sky-700' }}">Belum Punya Kode</div>
<div class="mt-3 text-3xl font-bold {{ $summary['without_code'] > 0 ? 'text-rose-800' : 'text-sky-800' }}">{{ $summary['without_code'] }}</div>
<div class="group rounded-3xl border {{ $summary['without_code'] > 0 ? 'border-rose-200 bg-gradient-to-br from-white via-rose-50/50 to-rose-100/60' : 'border-sky-200 bg-gradient-to-br from-white via-sky-50/50 to-sky-100/60' }} p-6 shadow-sm {{ $summary['without_code'] > 0 ? 'shadow-rose-100/50' : 'shadow-sky-100/50' }} ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg {{ $summary['without_code'] > 0 ? 'hover:shadow-rose-200/60' : 'hover:shadow-sky-200/60' }}">
<div class="flex items-start justify-between">
<div>
<div class="text-sm font-medium {{ $summary['without_code'] > 0 ? 'text-rose-700' : 'text-sky-700' }}">Belum Punya Kode</div>
<div class="mt-3 text-3xl font-bold {{ $summary['without_code'] > 0 ? 'text-rose-800' : 'text-sky-800' }}">{{ $summary['without_code'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 {{ $summary['without_code'] > 0 ? 'text-rose-600 ring-1 ring-rose-100' : 'text-sky-600 ring-1 ring-sky-100' }} backdrop-blur-sm">
<i class="fa-solid {{ $summary['without_code'] > 0 ? 'fa-key' : 'fa-shield-check' }} text-lg"></i>
</div>
</div>
</div>
</div>
<x-master-card title="Manajemen Kode Akses" subtitle="Kelola pembuatan kode akses siswa dan siapkan daftar kode per kelas untuk dibagikan ke wali kelas, siswa, atau orang tua.">
<div class="space-y-4">
<div class="rounded-2xl border {{ $summary['without_code'] > 0 ? 'border-amber-200 bg-amber-50' : 'border-emerald-200 bg-emerald-50' }} px-4 py-3 shadow-sm {{ $summary['without_code'] > 0 ? 'shadow-amber-100/40' : 'shadow-emerald-100/40' }}">
<div class="rounded-3xl border {{ $summary['without_code'] > 0 ? 'border-amber-200 bg-gradient-to-r from-amber-50 to-white' : 'border-emerald-200 bg-gradient-to-r from-emerald-50 to-white' }} px-4 py-4 shadow-sm {{ $summary['without_code'] > 0 ? 'shadow-amber-100/40' : 'shadow-emerald-100/40' }}">
<div class="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
<div>
<div class="text-sm font-semibold {{ $summary['without_code'] > 0 ? 'text-amber-800' : 'text-emerald-800' }}">
@@ -36,14 +71,15 @@
: 'Kode akses sudah lengkap. Anda bisa reset bila sekolah ingin mengganti seluruh batch kode.' }}
</p>
</div>
<div class="inline-flex items-center rounded-full {{ $summary['without_code'] > 0 ? 'bg-white text-amber-800' : 'bg-white text-emerald-800' }} px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.16em] shadow-sm">
<div class="inline-flex items-center gap-2 rounded-full {{ $summary['without_code'] > 0 ? 'bg-white text-amber-800' : 'bg-white text-emerald-800' }} px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.16em] shadow-sm">
<i class="fa-solid {{ $summary['without_code'] > 0 ? 'fa-key' : 'fa-circle-check' }}"></i>
{{ $summary['with_code'] }} siswa sudah punya kode
</div>
</div>
</div>
<div class="grid grid-cols-1 gap-4 xl:grid-cols-[1.15fr_0.85fr]">
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-4">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/30 p-4 shadow-sm ring-1 ring-white/70">
<div class="mb-3">
<h4 class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Aksi Massal Kode Akses</h4>
<p class="mt-1 text-sm text-slate-500">Bootstrap awal atau ganti seluruh kode akses.</p>
@@ -68,7 +104,7 @@
</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-4">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-slate-100/70 p-4 shadow-sm ring-1 ring-white/70">
<div class="mb-3">
<h4 class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Export Kode per Kelas</h4>
<p class="mt-1 text-sm text-slate-500">Siapkan lembar siap cetak / PDF per kelas.</p>
@@ -87,21 +123,44 @@
</x-master-card>
<x-master-card title="Filter Self Servis Siswa" subtitle="Cari siswa, filter per tahun angkatan, kelas, atau status self servis.">
<form method="GET" action="{{ route('student-self-service.index') }}" class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<x-form-input name="search" label="Pencarian" :value="$search" placeholder="Nama / NIS / NISN" />
<x-form-select name="academic_year_id" label="Tahun Angkatan" :options="$academicYears->all()" :value="$filters['academic_year_id']" />
<x-form-select name="class_id" label="Kelas" :options="$classes->all()" :value="$filters['class_id']" />
<x-form-select name="status" label="Status Self Servis" :options="$statuses" :value="$filters['status']" />
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/30 p-4 shadow-sm ring-1 ring-white/70">
<form method="GET" action="{{ route('student-self-service.index') }}" class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<x-form-input name="search" label="Pencarian" :value="$search" placeholder="Nama / NIS / NISN" />
<x-form-select name="academic_year_id" label="Tahun Angkatan" :options="$academicYears->all()" :value="$filters['academic_year_id']" />
<x-form-select name="class_id" label="Kelas" :options="$classes->all()" :value="$filters['class_id']" />
<x-form-select name="status" label="Status Self Servis" :options="$statuses" :value="$filters['status']" />
<div class="flex items-end gap-3 md:col-span-2 xl:col-span-4">
<button type="submit" class="rounded-2xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-slate-800">Cari</button>
<a href="{{ route('student-self-service.index') }}" class="rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">Reset</a>
<a href="{{ route('public.student-self-service.index') }}" target="_blank" class="rounded-2xl border border-indigo-200 bg-indigo-50 px-5 py-3 text-sm font-semibold text-indigo-700 transition hover:bg-indigo-100">Buka Portal Publik</a>
</div>
</form>
<div class="flex flex-wrap items-end gap-3 md:col-span-2 xl:col-span-4">
<button type="submit" class="inline-flex items-center gap-2 rounded-2xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-slate-800">
<i class="fa-solid fa-magnifying-glass"></i>
<span>Cari</span>
</button>
<a href="{{ route('student-self-service.index') }}" class="inline-flex items-center gap-2 rounded-2xl border border-slate-200 bg-white px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">
<i class="fa-solid fa-rotate-left"></i>
<span>Reset</span>
</a>
<a href="{{ route('public.student-self-service.index') }}" target="_blank" class="inline-flex items-center gap-2 rounded-2xl border border-indigo-200 bg-indigo-50 px-5 py-3 text-sm font-semibold text-indigo-700 transition hover:bg-indigo-100">
<i class="fa-solid fa-up-right-from-square"></i>
<span>Buka Portal Publik</span>
</a>
</div>
</form>
</div>
</x-master-card>
<div class="overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-sm shadow-slate-200/50">
<div x-data="{ copiedStudentId: null }" class="overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-sm shadow-slate-200/50 ring-1 ring-white/70">
<div class="border-b border-slate-200 bg-gradient-to-r from-white via-slate-50 to-indigo-50/30 px-6 py-5">
<div class="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
<div>
<div class="text-lg font-semibold text-slate-800">Daftar Akses Self Servis</div>
<div class="mt-1 text-sm text-slate-500">Pantau status progres siswa, kelola kode akses, dan bagikan kode yang siap dipakai.</div>
</div>
<div class="inline-flex items-center gap-2 rounded-full bg-slate-900 px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.16em] text-white shadow-sm">
<i class="fa-solid fa-id-card-clip"></i>
{{ $students->total() }} data terfilter
</div>
</div>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-slate-200">
<thead class="bg-slate-50">
@@ -123,15 +182,22 @@
'submitted' => 'bg-emerald-100 text-emerald-700',
];
@endphp
<tr>
<tr class="transition hover:bg-slate-50/80">
<td class="px-6 py-4 text-sm text-slate-600">
<div class="font-semibold text-slate-800">{{ $student->full_name }}</div>
<div class="mt-1 text-xs text-slate-500">NIS: {{ $student->student_number ?: '-' }} · NISN: {{ $student->nisn ?: '-' }}</div>
<div class="flex items-start gap-3">
<div class="flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl bg-fuchsia-100 text-sm font-bold text-fuchsia-700">
{{ strtoupper(mb_substr($student->full_name, 0, 1)) }}
</div>
<div>
<div class="font-semibold text-slate-800">{{ $student->full_name }}</div>
<div class="mt-1 text-xs text-slate-500">NIS: {{ $student->student_number ?: '-' }} · NISN: {{ $student->nisn ?: '-' }}</div>
</div>
</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div>{{ $student->academicYear?->name ?: '-' }}</div>
<div class="font-medium text-slate-700">{{ $student->academicYear?->name ?: '-' }}</div>
<div>{{ $student->department?->name ?: '-' }}</div>
<div>{{ $student->classRoom?->name ?: '-' }}</div>
<div class="text-xs text-slate-500">{{ $student->classRoom?->name ?: '-' }}</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<span class="rounded-full px-3 py-1 text-xs font-semibold {{ $statusMap[$student->self_service_status] ?? 'bg-slate-100 text-slate-600' }}">
@@ -139,24 +205,34 @@
</span>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div class="font-mono text-sm tracking-[0.18em] text-slate-800">{{ $student->self_service_access_code ?: '-' }}</div>
<div class="inline-flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-3 py-2 font-mono text-sm tracking-[0.18em] text-slate-800">
<i class="fa-solid fa-key text-xs text-slate-400"></i>
<span>{{ $student->self_service_access_code ?: '-' }}</span>
</div>
<div class="mt-1 text-xs text-slate-500">Digenerate: {{ $student->self_service_code_generated_at?->format('d-m-Y H:i') ?: '-' }}</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div>Terakhir akses: {{ $student->self_service_last_access_at?->format('d-m-Y H:i') ?: '-' }}</div>
<div class="mt-1">Submitted: {{ $student->self_service_submitted_at?->format('d-m-Y H:i') ?: '-' }}</div>
<div class="flex items-center gap-2"><i class="fa-solid fa-clock text-xs text-slate-400"></i><span>Terakhir akses: {{ $student->self_service_last_access_at?->format('d-m-Y H:i') ?: '-' }}</span></div>
<div class="mt-1 flex items-center gap-2"><i class="fa-solid fa-paper-plane text-xs text-slate-400"></i><span>Submitted: {{ $student->self_service_submitted_at?->format('d-m-Y H:i') ?: '-' }}</span></div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div class="flex flex-wrap items-center gap-2">
@if ($student->self_service_access_code)
<button type="button" class="rounded-xl border border-sky-200 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-50" onclick="navigator.clipboard.writeText('{{ $student->self_service_access_code }}')">
Copy Kode
<button
type="button"
class="inline-flex items-center gap-2 rounded-xl border border-sky-200 bg-sky-50 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-100"
@click="navigator.clipboard.writeText('{{ $student->self_service_access_code }}'); copiedStudentId = {{ $student->id }}; setTimeout(() => copiedStudentId = null, 1800)"
>
<i class="fa-solid fa-copy"></i>
<span x-show="copiedStudentId !== {{ $student->id }}">Copy Kode</span>
<span x-show="copiedStudentId === {{ $student->id }}" x-cloak>Tersalin</span>
</button>
@endif
<form method="POST" action="{{ route('student-self-service.regenerate-student-code', $student) }}" class="delete-form" data-entity="kode akses {{ $student->full_name }}">
@csrf
<button type="submit" class="rounded-xl border border-amber-200 px-3 py-2 text-xs font-semibold text-amber-700 transition hover:bg-amber-50">
{{ $student->self_service_access_code ? 'Reset Kode' : 'Generate Kode' }}
<button type="submit" class="inline-flex items-center gap-2 rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-xs font-semibold text-amber-700 transition hover:bg-amber-100">
<i class="fa-solid {{ $student->self_service_access_code ? 'fa-rotate' : 'fa-wand-magic-sparkles' }}"></i>
<span>{{ $student->self_service_access_code ? 'Reset Kode' : 'Generate Kode' }}</span>
</button>
</form>
</div>
+170 -29
View File
@@ -1,19 +1,129 @@
<x-master-page title="Data Siswa Mutasi" description="Data siswa yang dipindahkan ke tabel arsip siswa mutasi saat statusnya diubah menjadi Pindah ke sekolah lain.">
<x-master-card title="Filter Data Siswa Mutasi" subtitle="Cari data siswa mutasi berdasarkan identitas dan akademik terakhir.">
<form method="GET" action="{{ route('student-transfers.index') }}" class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<x-form-input name="search" label="Pencarian" :value="$search" placeholder="Nama / NIS / NISN" />
<x-form-select name="academic_year_id" label="Tahun Angkatan" :options="$academicYears->all()" :value="$filters['academic_year_id']" />
<x-form-select name="department_id" label="Program Keahlian" :options="$departments->all()" :value="$filters['department_id']" />
<x-form-select name="class_id" label="Kelas Terakhir" :options="$classes->all()" :value="$filters['class_id']" />
<div class="flex items-end gap-3 md:col-span-2 xl:col-span-4">
<button type="submit" class="rounded-2xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-slate-800">Cari</button>
<a href="{{ route('student-transfers.index') }}" class="rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">Reset</a>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-5">
<div class="group rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/40 p-6 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-200/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-slate-500">Total Arsip Mutasi</div>
<div class="mt-3 text-3xl font-bold text-slate-900">{{ $summary['total'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-indigo-600 ring-1 ring-indigo-100 backdrop-blur-sm">
<i class="fa-solid fa-right-left text-lg"></i>
</div>
</div>
</form>
</x-master-card>
</div>
<div class="group rounded-3xl border border-emerald-200 bg-gradient-to-br from-white via-emerald-50/50 to-emerald-100/60 p-6 shadow-sm shadow-emerald-100/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-emerald-200/60">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-emerald-700">Sudah Ada Surat</div>
<div class="mt-3 text-3xl font-bold text-emerald-800">{{ $summary['with_letter'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-emerald-600 ring-1 ring-emerald-100 backdrop-blur-sm">
<i class="fa-solid fa-file-circle-check text-lg"></i>
</div>
</div>
</div>
<div class="group rounded-3xl border {{ $summary['without_letter'] > 0 ? 'border-amber-200 bg-gradient-to-br from-white via-amber-50/50 to-amber-100/60' : 'border-sky-200 bg-gradient-to-br from-white via-sky-50/50 to-sky-100/60' }} p-6 shadow-sm {{ $summary['without_letter'] > 0 ? 'shadow-amber-100/50' : 'shadow-sky-100/50' }} ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg {{ $summary['without_letter'] > 0 ? 'hover:shadow-amber-200/60' : 'hover:shadow-sky-200/60' }}">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium {{ $summary['without_letter'] > 0 ? 'text-amber-700' : 'text-sky-700' }}">Belum Ada Surat</div>
<div class="mt-3 text-3xl font-bold {{ $summary['without_letter'] > 0 ? 'text-amber-800' : 'text-sky-800' }}">{{ $summary['without_letter'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 {{ $summary['without_letter'] > 0 ? 'text-amber-600 ring-1 ring-amber-100' : 'text-sky-600 ring-1 ring-sky-100' }} backdrop-blur-sm">
<i class="fa-solid {{ $summary['without_letter'] > 0 ? 'fa-file-circle-xmark' : 'fa-folder-open' }} text-lg"></i>
</div>
</div>
</div>
<div class="group rounded-3xl border border-fuchsia-200 bg-gradient-to-br from-white via-fuchsia-50/50 to-fuchsia-100/60 p-6 shadow-sm shadow-fuchsia-100/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-fuchsia-200/60">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-fuchsia-700">Mutasi Tahun Ini</div>
<div class="mt-3 text-3xl font-bold text-fuchsia-800">{{ $summary['this_year'] }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-fuchsia-600 ring-1 ring-fuchsia-100 backdrop-blur-sm">
<i class="fa-solid fa-calendar-check text-lg"></i>
</div>
</div>
</div>
<div class="group rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-slate-100/70 p-6 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-200/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-slate-500">Hasil Filter</div>
<div class="mt-3 text-3xl font-bold text-slate-900">{{ $summary['filtered'] }}</div>
<div class="mt-2 text-xs text-slate-500">{{ $summary['latest_transfer_date'] ? 'Update terakhir ' . \Illuminate\Support\Carbon::parse($summary['latest_transfer_date'])->format('d M Y') : 'Belum ada tanggal mutasi' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-slate-600 ring-1 ring-slate-200 backdrop-blur-sm">
<i class="fa-solid fa-filter text-lg"></i>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 gap-6 xl:grid-cols-[1.15fr_0.85fr]">
<x-master-card title="Filter Data Siswa Mutasi" subtitle="Cari data siswa mutasi berdasarkan identitas dan akademik terakhir.">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/30 p-4 shadow-sm ring-1 ring-white/70">
<form method="GET" action="{{ route('student-transfers.index') }}" class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<x-form-input name="search" label="Pencarian" :value="$search" placeholder="Nama / NIS / NISN" />
<x-form-select name="academic_year_id" label="Tahun Angkatan" :options="$academicYears->all()" :value="$filters['academic_year_id']" />
<x-form-select name="department_id" label="Program Keahlian" :options="$departments->all()" :value="$filters['department_id']" />
<x-form-select name="class_id" label="Kelas Terakhir" :options="$classes->all()" :value="$filters['class_id']" />
<div class="flex flex-wrap items-end gap-3 md:col-span-2 xl:col-span-4">
<button type="submit" class="inline-flex items-center gap-2 rounded-2xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-slate-800">
<i class="fa-solid fa-magnifying-glass"></i>
<span>Cari</span>
</button>
<a href="{{ route('student-transfers.index') }}" class="inline-flex items-center gap-2 rounded-2xl border border-slate-200 bg-white px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">
<i class="fa-solid fa-rotate-left"></i>
<span>Reset</span>
</a>
</div>
</form>
</div>
</x-master-card>
<x-master-card title="Aktivitas Terbaru" subtitle="Tiga arsip mutasi terbaru yang masuk ke sistem.">
@if ($recentTransfers->isEmpty())
<div class="rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-4 py-6 text-sm text-slate-500">
Belum ada arsip mutasi terbaru untuk ditampilkan.
</div>
@else
<div class="space-y-3">
@foreach ($recentTransfers as $recentTransfer)
<div class="rounded-2xl border border-slate-200 bg-slate-50/80 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-semibold text-slate-800">{{ $recentTransfer->full_name }}</div>
<div class="mt-1 text-xs text-slate-500">{{ $recentTransfer->department?->name ?: '-' }} · {{ $recentTransfer->classRoom?->name ?: '-' }}</div>
</div>
<span class="rounded-full bg-white px-3 py-1 text-[11px] font-semibold text-slate-600 ring-1 ring-slate-200">
{{ $recentTransfer->transfer_date?->format('d M Y') ?: 'Tanggal belum diisi' }}
</span>
</div>
</div>
@endforeach
</div>
@endif
</x-master-card>
</div>
<div class="overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-sm shadow-slate-200/50 ring-1 ring-white/70">
<div class="border-b border-slate-200 bg-gradient-to-r from-white via-slate-50 to-indigo-50/30 px-6 py-5">
<div class="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
<div>
<div class="text-lg font-semibold text-slate-800">Daftar Siswa Mutasi</div>
<div class="mt-1 text-sm text-slate-500">Arsip ini terisi otomatis saat status siswa diubah menjadi pindah sekolah.</div>
</div>
<div class="inline-flex items-center gap-2 rounded-full bg-slate-900 px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.16em] text-white shadow-sm">
<i class="fa-solid fa-box-archive"></i>
{{ $studentTransfers->total() }} data terfilter
</div>
</div>
</div>
<x-master-card title="Daftar Siswa Mutasi" subtitle="Arsip ini terisi otomatis saat status siswa diubah menjadi Pindah.">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-slate-200">
<thead class="bg-slate-50">
@@ -28,44 +138,75 @@
</thead>
<tbody class="divide-y divide-slate-100 bg-white">
@forelse ($studentTransfers as $studentTransfer)
<tr>
<tr class="transition hover:bg-slate-50/80">
<td class="px-6 py-4 text-sm text-slate-700">
<div class="font-semibold text-slate-800">{{ $studentTransfer->full_name }}</div>
<div class="mt-1 text-xs text-slate-500">NIS: {{ $studentTransfer->student_number ?: '-' }} · NISN: {{ $studentTransfer->nisn ?: '-' }}</div>
<div class="flex items-start gap-3">
<div class="flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl bg-indigo-100 text-sm font-bold text-indigo-700">
{{ strtoupper(mb_substr($studentTransfer->full_name, 0, 1)) }}
</div>
<div>
<div class="font-semibold text-slate-800">{{ $studentTransfer->full_name }}</div>
<div class="mt-1 text-xs text-slate-500">NIS: {{ $studentTransfer->student_number ?: '-' }} · NISN: {{ $studentTransfer->nisn ?: '-' }}</div>
</div>
</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div>{{ $studentTransfer->academicYear?->name ?: '-' }}</div>
<div class="font-medium text-slate-700">{{ $studentTransfer->academicYear?->name ?: '-' }}</div>
<div>{{ $studentTransfer->department?->name ?: '-' }}</div>
<div>{{ $studentTransfer->classRoom?->name ?: '-' }}</div>
<div class="text-xs text-slate-500">{{ $studentTransfer->classRoom?->name ?: '-' }}</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div class="inline-flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-3 py-2">
<i class="fa-solid fa-calendar-days text-xs text-slate-400"></i>
<span>{{ $studentTransfer->transfer_date?->format('d-m-Y') ?: '-' }}</span>
</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">{{ $studentTransfer->transfer_date?->format('d-m-Y') ?: '-' }}</td>
<td class="px-6 py-4 text-sm text-slate-600">
@if ($studentTransfer->transfer_letter_file_path)
<span class="inline-flex rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700">Sudah diupload</span>
<span class="inline-flex items-center gap-2 rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700">
<i class="fa-solid fa-circle-check"></i>
<span>Sudah diupload</span>
</span>
<div class="mt-2 text-xs text-slate-500">{{ $studentTransfer->transfer_letter_file_name }}</div>
<div class="mt-3">
<a href="{{ route('student-transfers.preview-letter', $studentTransfer) }}" class="inline-flex items-center rounded-xl border border-sky-200 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-50">Preview Surat</a>
<a href="{{ route('student-transfers.preview-letter', $studentTransfer) }}" class="inline-flex items-center gap-2 rounded-xl border border-sky-200 bg-sky-50 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-100">
<i class="fa-solid fa-eye"></i>
<span>Preview Surat</span>
</a>
</div>
@else
<span class="inline-flex rounded-full bg-amber-100 px-3 py-1 text-xs font-semibold text-amber-700">Surat Pindah Sekolah belum di upload</span>
<span class="inline-flex items-center gap-2 rounded-full bg-amber-100 px-3 py-1 text-xs font-semibold text-amber-700">
<i class="fa-solid fa-triangle-exclamation"></i>
<span>Belum upload surat</span>
</span>
@endif
</td>
<td class="px-6 py-4 text-sm text-slate-600">{{ $studentTransfer->transfer_reason ?: '-' }}</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div class="line-clamp-3">{{ $studentTransfer->transfer_reason ?: '-' }}</div>
</td>
<td class="px-6 py-4 text-sm">
<a href="{{ route('student-transfers.show', $studentTransfer) }}" class="rounded-xl border border-sky-200 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-50">Detail Arsip</a>
<a href="{{ route('student-transfers.show', $studentTransfer) }}" class="inline-flex items-center gap-2 rounded-xl border border-sky-200 bg-sky-50 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-100">
<i class="fa-solid fa-arrow-up-right-from-square"></i>
<span>Detail Arsip</span>
</a>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="px-6 py-10 text-center text-sm text-slate-500">Belum ada data siswa mutasi.</td>
<td colspan="6" class="px-6 py-12 text-center text-sm text-slate-500">
<div class="mx-auto max-w-md">
<div class="text-base font-semibold text-slate-700">Belum ada data siswa mutasi.</div>
<div class="mt-2 text-sm text-slate-500">Arsip mutasi akan muncul otomatis saat status siswa diubah menjadi pindah sekolah.</div>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
<div class="mt-5">
{{ $studentTransfers->links() }}
</div>
</x-master-card>
<div class="mt-6 flex justify-end">
{{ $studentTransfers->onEachSide(1)->links() }}
</div>
</x-master-page>
@@ -1,38 +1,141 @@
<x-master-page title="Preview Surat Pindah Sekolah" description="Lihat surat pindah sekolah langsung di browser bila format mendukung.">
<div class="grid grid-cols-1 gap-6 xl:grid-cols-3">
<x-master-card title="Informasi Surat Pindah" subtitle="Metadata surat pindah sekolah.">
<div class="space-y-4 text-sm text-slate-600">
<div><span class="font-semibold text-slate-800">Nama Siswa:</span> {{ $studentTransfer->full_name }}</div>
<div><span class="font-semibold text-slate-800">Nama File:</span> {{ $studentTransfer->transfer_letter_file_name ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Tipe MIME:</span> {{ $mimeType ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Ukuran:</span> {{ $studentTransfer->transfer_letter_file_size ? number_format($studentTransfer->transfer_letter_file_size / 1024, 1) . ' KB' : '-' }}</div>
<div><span class="font-semibold text-slate-800">Tanggal Pindah:</span> {{ $studentTransfer->transfer_date?->format('d-m-Y') ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Catatan:</span> {{ $studentTransfer->transfer_reason ?: '-' }}</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/40 p-6 shadow-sm shadow-slate-200/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-slate-500">Nama Siswa</div>
<div class="mt-3 text-lg font-bold text-slate-900">{{ $studentTransfer->full_name }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-indigo-600 ring-1 ring-indigo-100 backdrop-blur-sm">
<i class="fa-solid fa-user text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-sky-200 bg-gradient-to-br from-white via-sky-50/50 to-sky-100/60 p-6 shadow-sm shadow-sky-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-sky-700">Format File</div>
<div class="mt-3 text-lg font-bold text-sky-800">{{ strtoupper(pathinfo($studentTransfer->transfer_letter_file_name ?: '', PATHINFO_EXTENSION) ?: 'FILE') }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-sky-600 ring-1 ring-sky-100 backdrop-blur-sm">
<i class="fa-solid fa-file-lines text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-emerald-200 bg-gradient-to-br from-white via-emerald-50/50 to-emerald-100/60 p-6 shadow-sm shadow-emerald-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-emerald-700">Ukuran File</div>
<div class="mt-3 text-lg font-bold text-emerald-800">{{ $studentTransfer->transfer_letter_file_size ? number_format($studentTransfer->transfer_letter_file_size / 1024, 1) . ' KB' : '-' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-emerald-600 ring-1 ring-emerald-100 backdrop-blur-sm">
<i class="fa-solid fa-weight-scale text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-fuchsia-200 bg-gradient-to-br from-white via-fuchsia-50/50 to-fuchsia-100/60 p-6 shadow-sm shadow-fuchsia-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-fuchsia-700">Tanggal Pindah</div>
<div class="mt-3 text-lg font-bold text-fuchsia-800">{{ $studentTransfer->transfer_date?->format('d M Y') ?: 'Belum diisi' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-fuchsia-600 ring-1 ring-fuchsia-100 backdrop-blur-sm">
<i class="fa-solid fa-calendar-right text-lg"></i>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 gap-6 xl:grid-cols-[380px_minmax(0,1fr)]">
<x-master-card title="Informasi Surat Pindah" subtitle="Metadata file dan ringkasan arsip mutasi.">
<div class="mb-4 flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex h-11 w-11 items-center justify-center rounded-2xl bg-indigo-100 text-indigo-600">
<i class="fa-solid fa-folder-open text-lg"></i>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Metadata Dokumen</div>
<div class="mt-1 text-sm font-semibold text-slate-800">Informasi cepat sebelum file dibuka atau diunduh</div>
</div>
</div>
<div class="space-y-3 text-sm text-slate-600">
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-user"></i><span>Nama Siswa</span></div>
<div class="mt-2 font-semibold text-slate-800">{{ $studentTransfer->full_name }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-file-signature"></i><span>Nama File</span></div>
<div class="mt-2 break-words font-semibold text-slate-800">{{ $studentTransfer->transfer_letter_file_name ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-code"></i><span>Tipe MIME</span></div>
<div class="mt-2 text-slate-700">{{ $mimeType ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-weight-scale"></i><span>Ukuran File</span></div>
<div class="mt-2 text-slate-700">{{ $studentTransfer->transfer_letter_file_size ? number_format($studentTransfer->transfer_letter_file_size / 1024, 1) . ' KB' : '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-calendar-days"></i><span>Tanggal Pindah</span></div>
<div class="mt-2 text-slate-700">{{ $studentTransfer->transfer_date?->format('d-m-Y') ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-align-left"></i><span>Catatan</span></div>
<div class="mt-2 leading-6 text-slate-700">{{ $studentTransfer->transfer_reason ?: '-' }}</div>
</div>
</div>
<div class="mt-6 flex flex-wrap gap-3">
<a href="{{ $fileUrl }}" target="_blank" rel="noopener noreferrer" class="rounded-2xl bg-sky-600 px-4 py-3 text-sm font-semibold text-white transition hover:bg-sky-700">Buka File Asli</a>
<a href="{{ route('student-transfers.index') }}" class="rounded-2xl border border-slate-200 px-4 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">Kembali</a>
<a href="{{ $fileUrl }}" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-2 rounded-2xl bg-sky-600 px-4 py-3 text-sm font-semibold text-white transition hover:bg-sky-700">
<i class="fa-solid fa-up-right-from-square"></i>
<span>Buka File Asli</span>
</a>
<a href="{{ route('student-transfers.show', $studentTransfer) }}" class="inline-flex items-center gap-2 rounded-2xl border border-slate-200 px-4 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">
<i class="fa-solid fa-arrow-left"></i>
<span>Kembali ke Detail</span>
</a>
</div>
</x-master-card>
<div class="xl:col-span-2">
<x-master-card title="Tampilan Surat Pindah" subtitle="Preview langsung untuk gambar dan PDF.">
@if ($isImage)
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-slate-50 p-4">
<img src="{{ $fileUrl }}" alt="{{ $studentTransfer->transfer_letter_file_name }}" class="mx-auto max-h-[70vh] rounded-xl object-contain shadow-sm">
<x-master-card title="Tampilan Surat Pindah" subtitle="Preview langsung untuk gambar dan PDF.">
<div class="mb-4 flex items-center justify-between gap-3 rounded-2xl border border-slate-200 bg-gradient-to-r from-white via-slate-50 to-indigo-50/30 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Mode Preview</div>
<div class="mt-1 text-sm font-semibold text-slate-800">
@if ($isImage)
Gambar langsung di browser
@elseif ($isPdf)
PDF embed di browser
@else
Preview tidak didukung
@endif
</div>
@elseif ($isPdf)
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-slate-50">
<iframe src="{{ $fileUrl }}" class="h-[75vh] w-full rounded-2xl" title="Preview Surat Pindah"></iframe>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-indigo-600 ring-1 ring-indigo-100 backdrop-blur-sm">
<i class="fa-solid {{ $isImage ? 'fa-image' : ($isPdf ? 'fa-file-pdf' : 'fa-circle-info') }} text-lg"></i>
</div>
</div>
@if ($isImage)
<div class="overflow-hidden rounded-3xl border border-slate-200 bg-slate-50 p-4 shadow-sm ring-1 ring-white/70">
<img src="{{ $fileUrl }}" alt="{{ $studentTransfer->transfer_letter_file_name }}" class="mx-auto max-h-[72vh] rounded-2xl object-contain shadow-sm">
</div>
@elseif ($isPdf)
<div class="overflow-hidden rounded-3xl border border-slate-200 bg-slate-50 shadow-sm ring-1 ring-white/70">
<iframe src="{{ $fileUrl }}" class="h-[78vh] w-full rounded-3xl" title="Preview Surat Pindah"></iframe>
</div>
@else
<div class="rounded-3xl border border-dashed border-slate-300 bg-slate-50 p-10 text-center shadow-sm ring-1 ring-white/70">
<div class="mx-auto flex h-14 w-14 items-center justify-center rounded-2xl bg-white text-slate-500 shadow-sm ring-1 ring-slate-200">
<i class="fa-solid fa-file-circle-question text-xl"></i>
</div>
@else
<div class="rounded-2xl border border-dashed border-slate-300 bg-slate-50 p-10 text-center">
<div class="text-lg font-semibold text-slate-800">Preview tidak tersedia</div>
<p class="mt-2 text-sm text-slate-500">Format file ini belum didukung untuk preview langsung di browser. Silakan gunakan tombol buka file asli.</p>
</div>
@endif
</x-master-card>
</div>
<div class="mt-4 text-lg font-semibold text-slate-800">Preview tidak tersedia</div>
<p class="mt-2 text-sm text-slate-500">Format file ini belum didukung untuk preview langsung di browser. Gunakan tombol buka file asli untuk melihat dokumen.</p>
</div>
@endif
</x-master-card>
</div>
</x-master-page>
+116 -35
View File
@@ -1,44 +1,109 @@
<x-master-page title="Detail Arsip Siswa Mutasi" description="Lihat ringkasan arsip siswa mutasi dan kelola surat pindah tanpa masuk ke modul siswa aktif.">
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/40 p-6 shadow-sm shadow-slate-200/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-slate-500">Status Arsip</div>
<div class="mt-3 text-lg font-bold text-slate-900">Siswa Mutasi</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-indigo-600 ring-1 ring-indigo-100 backdrop-blur-sm">
<i class="fa-solid fa-box-archive text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-sky-200 bg-gradient-to-br from-white via-sky-50/50 to-sky-100/60 p-6 shadow-sm shadow-sky-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-sky-700">Tanggal Pindah</div>
<div class="mt-3 text-lg font-bold text-sky-800">{{ $studentTransfer->transfer_date?->format('d M Y') ?: 'Belum diisi' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-sky-600 ring-1 ring-sky-100 backdrop-blur-sm">
<i class="fa-solid fa-calendar-right text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border {{ $studentTransfer->transfer_letter_file_path ? 'border-emerald-200 bg-gradient-to-br from-white via-emerald-50/50 to-emerald-100/60' : 'border-amber-200 bg-gradient-to-br from-white via-amber-50/50 to-amber-100/60' }} p-6 shadow-sm {{ $studentTransfer->transfer_letter_file_path ? 'shadow-emerald-100/50' : 'shadow-amber-100/50' }} ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium {{ $studentTransfer->transfer_letter_file_path ? 'text-emerald-700' : 'text-amber-700' }}">Surat Pindah</div>
<div class="mt-3 text-lg font-bold {{ $studentTransfer->transfer_letter_file_path ? 'text-emerald-800' : 'text-amber-800' }}">{{ $studentTransfer->transfer_letter_file_path ? 'Sudah tersedia' : 'Belum diupload' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 {{ $studentTransfer->transfer_letter_file_path ? 'text-emerald-600 ring-1 ring-emerald-100' : 'text-amber-600 ring-1 ring-amber-100' }} backdrop-blur-sm">
<i class="fa-solid {{ $studentTransfer->transfer_letter_file_path ? 'fa-file-circle-check' : 'fa-file-circle-xmark' }} text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-fuchsia-200 bg-gradient-to-br from-white via-fuchsia-50/50 to-fuchsia-100/60 p-6 shadow-sm shadow-fuchsia-100/50 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-fuchsia-700">Kelas Terakhir</div>
<div class="mt-3 text-lg font-bold text-fuchsia-800">{{ $studentTransfer->classRoom?->name ?: '-' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-fuchsia-600 ring-1 ring-fuchsia-100 backdrop-blur-sm">
<i class="fa-solid fa-school text-lg"></i>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 gap-6 xl:grid-cols-3">
<div class="xl:col-span-2 space-y-6">
<x-master-card title="Identitas Siswa" subtitle="Snapshot data terakhir saat siswa dipindahkan ke arsip mutasi.">
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Nama Lengkap</div>
<div class="mt-1 text-sm font-semibold text-slate-900">{{ $studentTransfer->full_name }}</div>
<div class="mb-4 flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex h-11 w-11 items-center justify-center rounded-2xl bg-indigo-100 text-indigo-600">
<i class="fa-solid fa-id-card text-lg"></i>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Status Arsip</div>
<div class="mt-1"><span class="inline-flex rounded-full bg-amber-100 px-3 py-1 text-xs font-semibold text-amber-700">Siswa Mutasi</span></div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Snapshot Identitas</div>
<div class="mt-1 text-sm font-semibold text-slate-800">Identitas dasar siswa saat masuk arsip mutasi</div>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">NIS</div>
<div class="mt-1 text-sm text-slate-700">{{ $studentTransfer->student_number ?: '-' }}</div>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-user"></i><span>Nama Lengkap</span></div>
<div class="mt-2 text-sm font-semibold text-slate-900">{{ $studentTransfer->full_name }}</div>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">NISN</div>
<div class="mt-1 text-sm text-slate-700">{{ $studentTransfer->nisn ?: '-' }}</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-badge-check"></i><span>Status Arsip</span></div>
<div class="mt-2"><span class="inline-flex items-center gap-2 rounded-full bg-amber-100 px-3 py-1 text-xs font-semibold text-amber-700"><i class="fa-solid fa-right-left"></i><span>Siswa Mutasi</span></span></div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-hashtag"></i><span>NIS</span></div>
<div class="mt-2 text-sm text-slate-700">{{ $studentTransfer->student_number ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-fingerprint"></i><span>NISN</span></div>
<div class="mt-2 text-sm text-slate-700">{{ $studentTransfer->nisn ?: '-' }}</div>
</div>
</div>
</x-master-card>
<x-master-card title="Akademik Terakhir" subtitle="Data akademik terakhir sebelum status siswa menjadi mutasi.">
<div class="grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-4">
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Tahun Angkatan</div>
<div class="mt-1 text-sm text-slate-700">{{ $studentTransfer->academicYear?->name ?: '-' }}</div>
<div class="mb-4 flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex h-11 w-11 items-center justify-center rounded-2xl bg-amber-100 text-amber-600">
<i class="fa-solid fa-graduation-cap text-lg"></i>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Program Keahlian</div>
<div class="mt-1 text-sm text-slate-700">{{ $studentTransfer->department?->name ?: '-' }}</div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Posisi Akademik Terakhir</div>
<div class="mt-1 text-sm font-semibold text-slate-800">Rombel dan relasi akademik sebelum siswa pindah sekolah</div>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Kelas Terakhir</div>
<div class="mt-1 text-sm text-slate-700">{{ $studentTransfer->classRoom?->name ?: '-' }}</div>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-calendar-days"></i><span>Tahun Angkatan</span></div>
<div class="mt-2 text-sm text-slate-700">{{ $studentTransfer->academicYear?->name ?: '-' }}</div>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Tanggal Masuk</div>
<div class="mt-1 text-sm text-slate-700">{{ $studentTransfer->entry_date?->format('d-m-Y') ?: '-' }}</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-sitemap"></i><span>Program Keahlian</span></div>
<div class="mt-2 text-sm text-slate-700">{{ $studentTransfer->department?->name ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-school"></i><span>Kelas Terakhir</span></div>
<div class="mt-2 text-sm text-slate-700">{{ $studentTransfer->classRoom?->name ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-door-open"></i><span>Tanggal Masuk</span></div>
<div class="mt-2 text-sm text-slate-700">{{ $studentTransfer->entry_date?->format('d-m-Y') ?: '-' }}</div>
</div>
</div>
</x-master-card>
@@ -48,20 +113,33 @@
<x-master-card title="Dokumen Surat Pindah" subtitle="Upload atau ganti surat pindah langsung dari modul arsip.">
<div class="space-y-4">
@if ($studentTransfer->transfer_letter_file_path)
<div class="rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-4">
<div class="text-sm font-semibold text-emerald-700">Surat pindah sudah tersedia</div>
<div class="mt-1 text-xs text-emerald-600">{{ $studentTransfer->transfer_letter_file_name }}</div>
<div class="mt-3">
<a href="{{ route('student-transfers.preview-letter', $studentTransfer) }}" class="inline-flex items-center rounded-xl border border-sky-200 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-50">Preview Surat</a>
<div class="rounded-2xl border border-emerald-200 bg-gradient-to-r from-emerald-50 to-white px-4 py-4 shadow-sm shadow-emerald-100/40">
<div class="flex items-start gap-3">
<div class="flex h-10 w-10 items-center justify-center rounded-2xl bg-white text-emerald-600 shadow-sm ring-1 ring-emerald-100">
<i class="fa-solid fa-file-circle-check"></i>
</div>
<div>
<div class="text-sm font-semibold text-emerald-700">Surat pindah sudah tersedia</div>
<div class="mt-1 text-xs text-emerald-600">{{ $studentTransfer->transfer_letter_file_name }}</div>
<div class="mt-3">
<a href="{{ route('student-transfers.preview-letter', $studentTransfer) }}" class="inline-flex items-center gap-2 rounded-xl border border-sky-200 bg-sky-50 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-100">
<i class="fa-solid fa-eye"></i>
<span>Preview Surat</span>
</a>
</div>
</div>
</div>
</div>
@else
<div class="rounded-2xl border border-amber-200 bg-amber-50 px-4 py-4 text-sm font-medium text-amber-700">
Surat pindah belum diupload.
<div class="rounded-2xl border border-amber-200 bg-gradient-to-r from-amber-50 to-white px-4 py-4 text-sm font-medium text-amber-700 shadow-sm shadow-amber-100/40">
<div class="flex items-center gap-3">
<i class="fa-solid fa-triangle-exclamation"></i>
<span>Surat pindah belum diupload.</span>
</div>
</div>
@endif
<form method="POST" action="{{ route('student-transfers.update', $studentTransfer) }}" enctype="multipart/form-data" class="space-y-4">
<form method="POST" action="{{ route('student-transfers.update', $studentTransfer) }}" enctype="multipart/form-data" class="space-y-4 rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/25 p-4 shadow-sm ring-1 ring-white/70">
@csrf
@method('PATCH')
@@ -70,13 +148,16 @@
<div>
<label for="transfer_letter" class="mb-2 block text-sm font-medium text-slate-700">Upload / Ganti Surat Pindah</label>
<input id="transfer_letter" name="transfer_letter" type="file" accept=".pdf,.jpg,.jpeg,.png" class="block w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-700 file:mr-4 file:rounded-xl file:border-0 file:bg-slate-100 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-slate-700 hover:file:bg-slate-200">
<div class="flex min-h-[3rem] items-center rounded-2xl border border-slate-200 bg-white px-3 py-2 transition focus-within:border-indigo-300 focus-within:ring-4 focus-within:ring-indigo-100">
<input id="transfer_letter" name="transfer_letter" type="file" accept=".pdf,.jpg,.jpeg,.png" class="block w-full text-sm text-slate-700 file:mr-3 file:rounded-xl file:border-0 file:bg-slate-100 file:px-4 file:py-2.5 file:text-sm file:font-semibold file:text-slate-700 hover:file:bg-slate-200">
</div>
<p class="mt-2 text-xs text-slate-500">Format: PDF, JPG, JPEG, PNG. Maksimal 2 MB.</p>
<x-input-error :messages="$errors->get('transfer_letter')" class="mt-2" />
</div>
<button type="submit" class="inline-flex items-center rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
Simpan Arsip Mutasi
<button type="submit" class="inline-flex items-center gap-2 rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
<i class="fa-solid fa-floppy-disk"></i>
<span>Simpan Arsip Mutasi</span>
</button>
</form>
</div>
+181 -100
View File
@@ -95,12 +95,32 @@
if (!exists) this.selectedClass = '';
},
async init() {
this.hydrateSelectionFromClass();
this.syncDepartmentSelection();
this.syncClassSelection();
if (this.selectedProvince) await this.loadRegencies(false);
if (this.selectedRegency) await this.loadDistricts(false);
if (this.selectedDistrict) await this.loadVillages(false);
},
hydrateSelectionFromClass() {
if (!this.selectedClass) {
return;
}
const selectedClassOption = this.classOptions.find(item => String(item.id) === String(this.selectedClass));
if (!selectedClassOption) {
return;
}
if (!this.selectedAcademicYear) {
this.selectedAcademicYear = String(selectedClassOption.academic_year_id ?? '');
}
if (!this.selectedDepartment) {
this.selectedDepartment = String(selectedClassOption.department_id ?? '');
}
},
async loadRegencies(reset = true) {
if (reset) {
this.selectedRegency = '';
@@ -142,111 +162,172 @@
}
</script>
<div class="grid grid-cols-1 gap-6 xl:grid-cols-2">
<x-master-card title="Identitas Siswa" subtitle="Data utama identitas dan akademik siswa.">
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
<x-form-input name="student_number" label="NIS" :value="$student->student_number ?? ''" placeholder="Nomor induk siswa" />
<x-form-input name="nisn" label="NISN" :value="$student->nisn ?? ''" placeholder="Nomor induk siswa nasional" />
<div class="md:col-span-2">
<x-form-input name="full_name" label="Nama Lengkap" :value="$student->full_name ?? ''" placeholder="Nama lengkap siswa" required />
<div class="mb-6 grid grid-cols-1 gap-4 xl:grid-cols-[1.15fr_0.85fr]">
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-200/50 ring-1 ring-white/70">
<div class="flex items-start gap-4">
<div class="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-indigo-100 text-indigo-600 shadow-sm shadow-indigo-100/60">
<i class="fa-solid fa-id-card text-lg"></i>
</div>
<x-form-select name="gender" label="Jenis Kelamin" :options="$genders" :value="$student->gender ?? null" required />
<x-form-select name="status" label="Status Siswa" :options="$statuses" :value="$student->status ?? 'active'" x-ref="statusField" x-on:change="statusValue = $event.target.value" required />
<x-form-input name="place_of_birth" label="Tempat Lahir" :value="$student->place_of_birth ?? ''" placeholder="Kota kelahiran" />
<x-form-input name="date_of_birth" label="Tanggal Lahir" type="date" :value="isset($student?->date_of_birth) ? $student->date_of_birth?->format('Y-m-d') : ''" />
<x-form-select name="religion" label="Agama" :options="$religions" :value="$student->religion ?? null" />
<x-form-input name="nationality" label="Kewarganegaraan" :value="$student->nationality ?? 'Indonesia'" placeholder="Contoh: Indonesia" />
<x-form-input name="family_card_number" label="No. Kartu Keluarga" :value="$student->family_card_number ?? ''" placeholder="Nomor KK" />
<x-form-input name="entry_date" label="Tanggal Masuk" type="date" :value="isset($student?->entry_date) ? $student->entry_date?->format('Y-m-d') : ''" />
<x-form-input name="child_order" label="Anak Ke" type="number" :value="$student->child_order ?? ''" placeholder="Contoh: 1" />
<x-form-input name="number_of_siblings" label="Jumlah Saudara" type="number" :value="$student->number_of_siblings ?? ''" placeholder="Contoh: 2" />
</div>
</x-master-card>
<x-master-card title="Akademik & Kontak" subtitle="Relasi akademik, alamat, dan informasi kontak siswa.">
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
<x-form-select name="academic_year_id" label="Tahun Angkatan" :options="$academicYears->all()" :value="$student->academic_year_id ?? null" x-model="selectedAcademicYear" x-on:change="syncDepartmentSelection(); syncClassSelection()" required />
<div>
<label for="department_id" class="mb-2 block text-sm font-medium text-slate-700">Program Keahlian</label>
<select id="department_id" name="department_id" x-model="selectedDepartment" x-on:change="syncClassSelection()" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100 disabled:cursor-not-allowed disabled:bg-slate-100 disabled:text-slate-400" :disabled="!selectedAcademicYear || filteredDepartments.length === 0" required>
<option value="" x-text="departmentPlaceholder"></option>
<template x-for="department in filteredDepartments" :key="department.id">
<option :value="department.id" x-text="department.name"></option>
</template>
</select>
<p class="mt-2 text-xs text-slate-500" x-text="!selectedAcademicYear ? 'Pilih Tahun Angkatan terlebih dahulu untuk membuka daftar Program Keahlian.' : (filteredDepartments.length === 0 ? 'Belum ada kelas yang menghubungkan Tahun Angkatan ini dengan Program Keahlian mana pun.' : 'Program Keahlian difilter otomatis berdasarkan Tahun Angkatan yang dipilih.')"></p>
<x-input-error :messages="$errors->get('department_id')" class="mt-2" />
</div>
<div>
<label for="class_id" class="mb-2 block text-sm font-medium text-slate-700">Kelas</label>
<select id="class_id" name="class_id" x-model="selectedClass" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100 disabled:cursor-not-allowed disabled:bg-slate-100 disabled:text-slate-400" :disabled="!selectedAcademicYear || !selectedDepartment || filteredClasses.length === 0" required>
<option value="" x-text="classPlaceholder"></option>
<template x-for="classOption in filteredClasses" :key="classOption.id">
<option :value="classOption.id" x-text="classOption.name"></option>
</template>
</select>
<p class="mt-2 text-xs text-slate-500" x-text="!selectedAcademicYear ? 'Pilih Tahun Angkatan terlebih dahulu.' : (!selectedDepartment ? 'Pilih Program Keahlian terlebih dahulu.' : (filteredClasses.length === 0 ? 'Belum ada kelas aktif untuk kombinasi Tahun Angkatan dan Program Keahlian ini.' : 'Hanya kelas yang cocok dengan kombinasi pilihan yang ditampilkan.'))"></p>
<x-input-error :messages="$errors->get('class_id')" class="mt-2" />
</div>
<x-form-input name="phone_number" label="No. HP" :value="$student->phone_number ?? ''" placeholder="Nomor HP siswa" />
<x-form-input name="email" label="Email" type="email" :value="$student->email ?? ''" placeholder="Alamat email" />
<div>
<label for="province_id" class="mb-2 block text-sm font-medium text-slate-700">Provinsi</label>
<select id="province_id" name="province_id" x-model="selectedProvince" @change="loadRegencies()" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100">
<option value="">-- Pilih Provinsi --</option>
@foreach($provinces as $id => $name)
<option value="{{ $id }}">{{ $name }}</option>
@endforeach
</select>
</div>
<div>
<label for="regency_id" class="mb-2 block text-sm font-medium text-slate-700">Kota/Kabupaten</label>
<select id="regency_id" name="regency_id" x-model="selectedRegency" @change="loadDistricts()" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100">
<option value="" x-text="loadingRegencies ? 'Memuat...' : '-- Pilih Kota/Kabupaten --'"></option>
<template x-for="regency in regencies" :key="regency.id">
<option :value="regency.id" x-text="regency.name"></option>
</template>
</select>
</div>
<div>
<label for="district_id" class="mb-2 block text-sm font-medium text-slate-700">Kecamatan</label>
<select id="district_id" name="district_id" x-model="selectedDistrict" @change="loadVillages()" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100">
<option value="" x-text="loadingDistricts ? 'Memuat...' : '-- Pilih Kecamatan --'"></option>
<template x-for="district in districts" :key="district.id">
<option :value="district.id" x-text="district.name"></option>
</template>
</select>
</div>
<div>
<label for="village_id" class="mb-2 block text-sm font-medium text-slate-700">Desa/Kelurahan</label>
<select id="village_id" name="village_id" x-model="selectedVillage" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100">
<option value="" x-text="loadingVillages ? 'Memuat...' : '-- Pilih Desa/Kelurahan --'"></option>
<template x-for="village in villages" :key="village.id">
<option :value="village.id" x-text="village.name"></option>
</template>
</select>
</div>
<x-form-input name="postal_code" label="Kode Pos" :value="$student->postal_code ?? ''" placeholder="Kode pos" />
<div class="md:col-span-2">
<x-form-textarea name="address" label="Alamat" :value="$student->address ?? ''" placeholder="Alamat lengkap siswa" rows="4" />
</div>
<div class="md:col-span-2">
<x-form-textarea name="notes" label="Catatan" :value="$student->notes ?? ''" placeholder="Catatan tambahan terkait siswa" rows="4" />
<div class="text-sm font-semibold text-slate-800">Panduan Pengisian</div>
<div class="mt-2 text-sm leading-6 text-slate-600">Isi identitas inti terlebih dahulu, lalu lengkapi relasi akademik dan alamat agar validasi dependensi kelas berjalan mulus.</div>
</div>
</div>
</x-master-card>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-1">
<div class="rounded-3xl border border-emerald-200 bg-gradient-to-br from-white via-emerald-50/60 to-emerald-100/70 p-5 shadow-sm shadow-emerald-100/60 ring-1 ring-white/70">
<div class="text-sm font-medium text-emerald-700">Mode Form</div>
<div class="mt-3 text-3xl font-bold text-emerald-800">{{ $formModeLabel ?? 'Create' }}</div>
<div class="mt-1 text-xs text-slate-500">input data siswa</div>
</div>
<div class="rounded-3xl border border-sky-200 bg-gradient-to-br from-white via-sky-50/60 to-sky-100/70 p-5 shadow-sm shadow-sky-100/60 ring-1 ring-white/70">
<div class="text-sm font-medium text-sky-700">Struktur Form</div>
<div class="mt-3 text-lg font-bold text-sky-800">3 Area Utama</div>
<div class="mt-1 text-xs text-slate-500">Identitas, akademik, dan alamat/kontak</div>
</div>
</div>
</div>
<div class="mt-6 flex flex-wrap items-center gap-3">
<button type="submit" class="rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">{{ $submitLabel }}</button>
<a href="{{ route('students.index') }}" class="rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">Batal</a>
<div class="grid grid-cols-1 gap-6 xl:grid-cols-[1.05fr_0.95fr]">
<div class="space-y-6">
<x-master-card title="Identitas Siswa" subtitle="Data inti siswa yang wajib rapi sejak awal.">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-emerald-50/30 p-5 shadow-sm ring-1 ring-white/70">
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
<x-form-input name="student_number" label="NIS" :value="$student->student_number ?? ''" placeholder="Nomor induk siswa" />
<x-form-input name="nisn" label="NISN" :value="$student->nisn ?? ''" placeholder="Nomor induk siswa nasional" />
<x-form-input name="nik" label="NIK" :value="$student->nik ?? ''" placeholder="Nomor Induk Kependudukan" />
<div class="md:col-span-2">
<x-form-input name="full_name" label="Nama Lengkap" :value="$student->full_name ?? ''" placeholder="Nama lengkap siswa" required />
</div>
<x-form-select name="gender" label="Jenis Kelamin" :options="$genders" :value="$student->gender ?? null" required />
<x-form-select name="status" label="Status Siswa" :options="$statuses" :value="$student->status ?? 'active'" x-ref="statusField" x-on:change="statusValue = $event.target.value" required />
<x-form-input name="place_of_birth" label="Tempat Lahir" :value="$student->place_of_birth ?? ''" placeholder="Kota kelahiran" />
<x-form-input name="date_of_birth" label="Tanggal Lahir" type="date" :value="isset($student?->date_of_birth) ? $student->date_of_birth?->format('Y-m-d') : ''" />
<x-form-select name="religion" label="Agama" :options="$religions" :value="$student->religion ?? null" />
<x-form-input name="nationality" label="Kewarganegaraan" :value="$student->nationality ?? 'Indonesia'" placeholder="Contoh: Indonesia" />
<x-form-input name="family_card_number" label="No. Kartu Keluarga" :value="$student->family_card_number ?? ''" placeholder="Nomor KK" />
<x-form-input name="entry_date" label="Tanggal Masuk" type="date" :value="isset($student?->entry_date) ? $student->entry_date?->format('Y-m-d') : ''" />
<x-form-input name="child_order" label="Anak Ke" type="number" :value="$student->child_order ?? ''" placeholder="Contoh: 1" />
<x-form-input name="number_of_siblings" label="Jumlah Saudara" type="number" :value="$student->number_of_siblings ?? ''" placeholder="Contoh: 2" />
</div>
</div>
</x-master-card>
<x-master-card title="Akademik Siswa" subtitle="Pastikan relasi angkatan, jurusan, dan kelas konsisten.">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-sky-50/30 p-5 shadow-sm ring-1 ring-white/70">
<div class="mb-4 rounded-2xl border border-sky-100 bg-sky-50 px-4 py-3 text-xs leading-6 text-sky-700">
Program Keahlian dan Kelas akan menyesuaikan otomatis berdasarkan Tahun Angkatan yang dipilih.
</div>
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
<x-form-select name="academic_year_id" label="Tahun Angkatan" :options="$academicYears->all()" :value="$student->academic_year_id ?? null" x-model="selectedAcademicYear" x-on:change="syncDepartmentSelection(); syncClassSelection()" required />
<div>
<label for="department_id" class="mb-2 block text-sm font-medium text-slate-700">Program Keahlian</label>
<select id="department_id" name="department_id" x-model="selectedDepartment" x-on:change="syncClassSelection()" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100 disabled:cursor-not-allowed disabled:bg-slate-100 disabled:text-slate-400" :disabled="!selectedAcademicYear || filteredDepartments.length === 0" required>
<option value="" x-text="departmentPlaceholder"></option>
@foreach ($departments as $departmentId => $departmentName)
<option
value="{{ $departmentId }}"
x-show="filteredDepartments.some(item => String(item.id) === '{{ (string) $departmentId }}')"
:selected="String(selectedDepartment) === '{{ (string) $departmentId }}'"
>{{ $departmentName }}</option>
@endforeach
</select>
<p class="mt-2 text-xs text-slate-500" x-text="!selectedAcademicYear ? 'Pilih Tahun Angkatan terlebih dahulu untuk membuka daftar Program Keahlian.' : (filteredDepartments.length === 0 ? 'Belum ada kelas yang menghubungkan Tahun Angkatan ini dengan Program Keahlian mana pun.' : 'Program Keahlian difilter otomatis berdasarkan Tahun Angkatan yang dipilih.')"></p>
<x-input-error :messages="$errors->get('department_id')" class="mt-2" />
</div>
<div class="md:col-span-2">
<label for="class_id" class="mb-2 block text-sm font-medium text-slate-700">Kelas</label>
<select id="class_id" name="class_id" x-model="selectedClass" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100 disabled:cursor-not-allowed disabled:bg-slate-100 disabled:text-slate-400" :disabled="!selectedAcademicYear || !selectedDepartment || filteredClasses.length === 0" required>
<option value="" x-text="classPlaceholder"></option>
@foreach ($classOptions as $classOption)
<option
value="{{ $classOption['id'] }}"
x-show="filteredClasses.some(item => String(item.id) === '{{ (string) $classOption['id'] }}')"
:selected="String(selectedClass) === '{{ (string) $classOption['id'] }}'"
>{{ $classOption['name'] }}</option>
@endforeach
</select>
<p class="mt-2 text-xs text-slate-500" x-text="!selectedAcademicYear ? 'Pilih Tahun Angkatan terlebih dahulu.' : (!selectedDepartment ? 'Pilih Program Keahlian terlebih dahulu.' : (filteredClasses.length === 0 ? 'Belum ada kelas aktif untuk kombinasi Tahun Angkatan dan Program Keahlian ini.' : 'Hanya kelas yang cocok dengan kombinasi pilihan yang ditampilkan.'))"></p>
<x-input-error :messages="$errors->get('class_id')" class="mt-2" />
</div>
</div>
</div>
</x-master-card>
</div>
<div class="space-y-6">
<x-master-card title="Kontak & Domisili" subtitle="Simpan informasi yang paling sering dipakai saat follow-up siswa.">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-amber-50/30 p-5 shadow-sm ring-1 ring-white/70">
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
<x-form-input name="phone_number" label="No. HP" :value="$student->phone_number ?? ''" placeholder="Nomor HP siswa" />
<x-form-input name="email" label="Email" type="email" :value="$student->email ?? ''" placeholder="Alamat email" />
<div>
<label for="province_id" class="mb-2 block text-sm font-medium text-slate-700">Provinsi</label>
<select id="province_id" name="province_id" x-model="selectedProvince" @change="loadRegencies()" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100">
<option value="">-- Pilih Provinsi --</option>
@foreach($provinces as $id => $name)
<option value="{{ $id }}">{{ $name }}</option>
@endforeach
</select>
</div>
<div>
<label for="regency_id" class="mb-2 block text-sm font-medium text-slate-700">Kota/Kabupaten</label>
<select id="regency_id" name="regency_id" x-model="selectedRegency" @change="loadDistricts()" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100">
<option value="" x-text="loadingRegencies ? 'Memuat...' : '-- Pilih Kota/Kabupaten --'"></option>
<template x-for="regency in regencies" :key="regency.id">
<option :value="regency.id" x-text="regency.name"></option>
</template>
</select>
</div>
<div>
<label for="district_id" class="mb-2 block text-sm font-medium text-slate-700">Kecamatan</label>
<select id="district_id" name="district_id" x-model="selectedDistrict" @change="loadVillages()" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100">
<option value="" x-text="loadingDistricts ? 'Memuat...' : '-- Pilih Kecamatan --'"></option>
<template x-for="district in districts" :key="district.id">
<option :value="district.id" x-text="district.name"></option>
</template>
</select>
</div>
<div>
<label for="village_id" class="mb-2 block text-sm font-medium text-slate-700">Desa/Kelurahan</label>
<select id="village_id" name="village_id" x-model="selectedVillage" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100">
<option value="" x-text="loadingVillages ? 'Memuat...' : '-- Pilih Desa/Kelurahan --'"></option>
<template x-for="village in villages" :key="village.id">
<option :value="village.id" x-text="village.name"></option>
</template>
</select>
</div>
<x-form-input name="postal_code" label="Kode Pos" :value="$student->postal_code ?? ''" placeholder="Kode pos" />
<div class="md:col-span-2">
<x-form-textarea name="address" label="Alamat" :value="$student->address ?? ''" placeholder="Alamat lengkap siswa" rows="4" />
</div>
<div class="md:col-span-2">
<x-form-textarea name="notes" label="Catatan" :value="$student->notes ?? ''" placeholder="Catatan tambahan terkait siswa" rows="4" />
</div>
</div>
</div>
</x-master-card>
</div>
</div>
<div class="mt-6 flex flex-col gap-3 rounded-3xl border border-slate-200 bg-white px-5 py-4 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 lg:flex-row lg:items-center lg:justify-between">
<div class="text-sm text-slate-600">
<div class="font-semibold text-slate-800">Siap simpan data siswa?</div>
<div class="mt-1 text-xs text-slate-500">Pastikan identitas inti dan relasi akademik sudah sesuai sebelum menyimpan perubahan.</div>
</div>
<div class="flex flex-wrap items-center gap-3">
<a href="{{ route('students.index') }}" class="rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">Batal</a>
<button type="submit" class="inline-flex items-center gap-2 rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
<i class="fa-solid fa-floppy-disk"></i>
<span>{{ $submitLabel }}</span>
</button>
</div>
</div>
</div>
+21 -1
View File
@@ -1,6 +1,26 @@
<x-master-page title="Tambah Siswa" description="Tambahkan data siswa baru ke dalam sistem.">
<div class="mb-6 rounded-3xl border border-emerald-200 bg-gradient-to-br from-emerald-50 via-white to-sky-50 px-6 py-6 shadow-sm shadow-emerald-100/60 ring-1 ring-white/70">
<div class="flex flex-col gap-5 lg:flex-row lg:items-start lg:justify-between">
<div>
<div class="inline-flex items-center gap-2 rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold uppercase tracking-[0.18em] text-emerald-700">
<i class="fa-solid fa-user-plus"></i>
Form Siswa
</div>
<h2 class="mt-4 text-2xl font-bold text-slate-900">Tambah Siswa Baru</h2>
<p class="mt-2 max-w-2xl text-sm leading-6 text-slate-600">
Lengkapi identitas, relasi akademik, alamat, dan kontak siswa dengan struktur form yang lebih rapi agar input lebih cepat dan minim salah pilih.
</p>
</div>
<div class="rounded-3xl border border-white/80 bg-white/80 px-5 py-4 text-right shadow-sm backdrop-blur">
<div class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Mode Form</div>
<div class="mt-2 text-2xl font-bold text-emerald-700">Create</div>
<div class="mt-1 text-xs text-slate-500">data siswa baru</div>
</div>
</div>
</div>
<form action="{{ route('students.store') }}" method="POST" enctype="multipart/form-data">
@csrf
@include('students._form', ['submitLabel' => 'Simpan'])
@include('students._form', ['submitLabel' => 'Simpan', 'formModeLabel' => 'Create'])
</form>
</x-master-page>
+21 -1
View File
@@ -1,7 +1,27 @@
<x-master-page title="Edit Siswa" description="Perbarui data siswa yang sudah tersimpan.">
<div class="mb-6 rounded-3xl border border-amber-200 bg-gradient-to-br from-amber-50 via-white to-sky-50 px-6 py-6 shadow-sm shadow-amber-100/60 ring-1 ring-white/70">
<div class="flex flex-col gap-5 lg:flex-row lg:items-start lg:justify-between">
<div>
<div class="inline-flex items-center gap-2 rounded-full bg-amber-100 px-3 py-1 text-xs font-semibold uppercase tracking-[0.18em] text-amber-700">
<i class="fa-solid fa-user-pen"></i>
Form Siswa
</div>
<h2 class="mt-4 text-2xl font-bold text-slate-900">Edit Data Siswa</h2>
<p class="mt-2 max-w-2xl text-sm leading-6 text-slate-600">
Perbarui data identitas, akademik, alamat, dan kontak siswa dengan struktur form yang konsisten agar perubahan lebih aman dan mudah ditinjau.
</p>
</div>
<div class="rounded-3xl border border-white/80 bg-white/80 px-5 py-4 text-right shadow-sm backdrop-blur">
<div class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Mode Form</div>
<div class="mt-2 text-2xl font-bold text-amber-700">Edit</div>
<div class="mt-1 text-xs text-slate-500">update data siswa</div>
</div>
</div>
</div>
<form action="{{ route('students.update', $student) }}" method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
@include('students._form', ['submitLabel' => 'Update'])
@include('students._form', ['submitLabel' => 'Update', 'formModeLabel' => 'Edit'])
</form>
</x-master-page>
+31 -9
View File
@@ -1,7 +1,7 @@
<x-master-page title="Import Data Siswa" description="Import data siswa esensial dari file Excel/CSV. Gunakan template agar format kolom sesuai.">
<x-master-page title="Import Data Siswa" description="Import data siswa lengkap dari file Excel/CSV. Gunakan template agar format kolom sesuai.">
<div class="grid grid-cols-1 gap-6 xl:grid-cols-3">
<div class="xl:col-span-2 space-y-6">
<x-master-card title="Upload File Import" subtitle="Kolom yang diimport hanya data esensial siswa. Data lain bisa dilengkapi belakangan dari form detail siswa.">
<x-master-card title="Upload File Import" subtitle="Semua field inti siswa bisa diimport dari Excel/CSV selama kolomnya mengikuti template.">
<form method="POST" action="{{ route('students.import.preview') }}" enctype="multipart/form-data" class="space-y-6">
@csrf
@@ -18,6 +18,9 @@
<a href="{{ route('students.import.template') }}" class="inline-flex items-center rounded-2xl border border-emerald-200 bg-emerald-50 px-5 py-3 text-sm font-semibold text-emerald-700 transition hover:bg-emerald-100">
Download Template
</a>
<a href="{{ route('students.import.example') }}" class="inline-flex items-center rounded-2xl border border-sky-200 bg-sky-50 px-5 py-3 text-sm font-semibold text-sky-700 transition hover:bg-sky-100">
Download Contoh File
</a>
<a href="{{ route('students.index') }}" class="inline-flex items-center rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">
Kembali ke Data Siswa
</a>
@@ -126,18 +129,37 @@
</div>
<div class="space-y-6">
<x-master-card title="Kolom Esensial" subtitle="Kolom inti yang harus ada di file import.">
<x-master-card title="Kolom Import" subtitle="Field inti siswa yang didukung saat import Excel/CSV.">
<ul class="space-y-2 text-sm text-slate-600">
<li><strong>student_number</strong> — NIS</li>
<li><strong>nisn</strong> — NISN</li>
<li><strong>nik</strong> — NIK</li>
<li><strong>full_name</strong> — nama lengkap</li>
<li><strong>gender</strong> — L / P</li>
<li><strong>academic_year_id</strong> — ID tahun ajaran</li>
<li><strong>department_id</strong> — ID program keahlian</li>
<li><strong>class_id</strong> — ID kelas</li>
<li><strong>entry_date</strong> — format YYYY-MM-DD</li>
<li><strong>status</strong> — default paling aman: active</li>
<li><strong>phone_number</strong> — opsional</li>
<li><strong>place_of_birth</strong> — tempat lahir</li>
<li><strong>date_of_birth</strong> — tanggal lahir</li>
<li><strong>religion</strong> — agama</li>
<li><strong>nationality</strong> — kewarganegaraan</li>
<li><strong>family_card_number</strong> — no. kartu keluarga</li>
<li><strong>child_order</strong> — anak ke</li>
<li><strong>number_of_siblings</strong> — jumlah saudara</li>
<li><strong>phone_number</strong> — nomor HP</li>
<li><strong>email</strong> — email</li>
<li><strong>address</strong> — alamat</li>
<li><strong>province_id / provinsi</strong> — provinsi</li>
<li><strong>regency_id / kabupaten_kota</strong> — kabupaten/kota</li>
<li><strong>district_id / kecamatan</strong> — kecamatan</li>
<li><strong>village_id / kelurahan_desa</strong> — kelurahan/desa</li>
<li><strong>postal_code</strong> — kode pos</li>
<li><strong>academic_year_id</strong> — ID atau nama tahun ajaran</li>
<li><strong>department_id</strong> — ID atau nama program keahlian</li>
<li><strong>class_id</strong> — ID atau nama kelas</li>
<li><strong>entry_date</strong> — tanggal masuk</li>
<li><strong>status</strong> — status siswa</li>
<li><strong>notes</strong> — catatan</li>
<li><strong>parent_father_*</strong> — data ayah</li>
<li><strong>parent_mother_*</strong> — data ibu</li>
<li><strong>parent_guardian_*</strong> — data wali</li>
</ul>
</x-master-card>
+178 -92
View File
@@ -24,47 +24,94 @@
};
@endphp
<div class="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div></div>
<div class="flex flex-wrap items-center gap-3">
@if (($viewMode ?? 'all') === 'all')
@if ($isAdmin)
<a href="{{ route('students.import.form') }}" class="inline-flex items-center justify-center rounded-2xl border border-emerald-200 bg-emerald-50 px-5 py-3 text-sm font-semibold text-emerald-700 transition hover:bg-emerald-100">
Import Data Siswa
</a>
@endif
<a href="{{ route('students.create') }}" class="inline-flex items-center justify-center rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
Tambah Siswa
</a>
@endif
<div class="grid grid-cols-1 gap-4 xl:grid-cols-[1.15fr_0.85fr]">
<div class="rounded-3xl border border-emerald-200 bg-gradient-to-br from-emerald-50 via-white to-sky-50 px-6 py-6 shadow-sm shadow-emerald-100/60 ring-1 ring-white/70">
<div class="flex flex-col gap-5 lg:flex-row lg:items-start lg:justify-between">
<div>
<div class="inline-flex items-center gap-2 rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold uppercase tracking-[0.18em] text-emerald-700">
<i class="fa-solid fa-users"></i>
Modul Siswa
</div>
<h2 class="mt-4 text-2xl font-bold text-slate-900">{{ $pageTitle ?? 'Data Siswa' }}</h2>
<p class="mt-2 max-w-2xl text-sm leading-6 text-slate-600">
{{ $pageDescription ?? 'Kelola data siswa lengkap berdasarkan master akademik yang tersedia.' }}
</p>
</div>
<div class="rounded-3xl border border-white/80 bg-white/80 px-5 py-4 text-right shadow-sm backdrop-blur">
<div class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Data Terfilter</div>
<div class="mt-2 text-4xl font-bold text-emerald-700">{{ $students->total() }}</div>
<div class="mt-1 text-xs text-slate-500">siswa tampil</div>
</div>
</div>
</div>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-1">
<div class="rounded-3xl border border-sky-200 bg-gradient-to-br from-white via-sky-50/60 to-sky-100/70 p-5 shadow-sm shadow-sky-100/60 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-sky-700">Halaman Aktif</div>
<div class="mt-3 text-3xl font-bold text-sky-800">{{ $students->currentPage() }}</div>
<div class="mt-1 text-xs text-slate-500">dari {{ $students->lastPage() }} halaman</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-sky-600 ring-1 ring-sky-100">
<i class="fa-solid fa-table-list text-lg"></i>
</div>
</div>
</div>
<div class="rounded-3xl border border-amber-200 bg-gradient-to-br from-white via-amber-50/60 to-amber-100/70 p-5 shadow-sm shadow-amber-100/60 ring-1 ring-white/70">
<div class="flex items-start justify-between gap-3">
<div>
<div class="text-sm font-medium text-amber-700">Mode Tampilan</div>
<div class="mt-3 text-lg font-bold text-amber-800">{{ ($viewMode ?? 'all') === 'all' ? 'Siswa Aktif' : 'Arsip Status' }}</div>
<div class="mt-1 text-xs text-slate-500">{{ ($viewMode ?? 'all') === 'all' ? 'Default fokus data aktif' : 'Sedang melihat data status khusus' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-amber-600 ring-1 ring-amber-100">
<i class="fa-solid fa-filter-circle-dollar text-lg"></i>
</div>
</div>
</div>
</div>
</div>
<x-master-card title="Filter Data Siswa" subtitle="Cari berdasarkan nama, NIS, NISN, kontak, atau filter akademik dasar.">
<form method="GET" action="{{ $indexRoute }}" class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-5">
<x-form-input name="search" label="Pencarian" :value="$search" placeholder="Nama / NIS / NISN / HP" />
<x-form-select name="academic_year_id" label="Tahun Angkatan" :options="$academicYears->all()" :value="$filters['academic_year_id']" />
<x-form-select name="department_id" label="Program Keahlian" :options="$departments->all()" :value="$filters['department_id']" />
<x-form-select name="class_id" label="Kelas" :options="$classes->all()" :value="$filters['class_id']" />
@if (($viewMode ?? 'all') === 'all')
<x-form-select name="status" label="Status" :options="$statuses" :value="$filters['status']" />
@endif
<x-master-card title="Filter Data Siswa" subtitle="Cari berdasarkan nama, NIS, NISN, NIK, kontak, atau filter akademik dasar.">
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-emerald-50/30 p-4 shadow-sm ring-1 ring-white/70">
<form method="GET" action="{{ $indexRoute }}" class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-5">
<x-form-input name="search" label="Pencarian" :value="$search" placeholder="Nama / NIS / NISN / NIK / HP" />
<x-form-select name="academic_year_id" label="Tahun Angkatan" :options="$academicYears->all()" :value="$filters['academic_year_id']" />
<x-form-select name="department_id" label="Program Keahlian" :options="$departments->all()" :value="$filters['department_id']" />
<x-form-select name="class_id" label="Kelas" :options="$classes->all()" :value="$filters['class_id']" />
@if (($viewMode ?? 'all') === 'all')
<x-form-select name="status" label="Status" :options="$statuses" :value="$filters['status']" />
@endif
<input type="hidden" name="sort" value="{{ $sort }}">
<input type="hidden" name="direction" value="{{ $direction }}">
<input type="hidden" name="sort" value="{{ $sort }}">
<input type="hidden" name="direction" value="{{ $direction }}">
<div class="flex items-end gap-3 md:col-span-2 xl:col-span-5">
<button type="submit" class="rounded-2xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-slate-800">Cari</button>
<a href="{{ $indexRoute }}" class="rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">Reset</a>
</div>
</form>
<div class="flex flex-wrap items-end gap-3 md:col-span-2 xl:col-span-5">
<button type="submit" class="inline-flex items-center gap-2 rounded-2xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-slate-800"><i class="fa-solid fa-magnifying-glass"></i><span>Cari</span></button>
<a href="{{ $indexRoute }}" class="inline-flex items-center gap-2 rounded-2xl border border-slate-200 bg-white px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50"><i class="fa-solid fa-rotate-left"></i><span>Reset</span></a>
@if (($viewMode ?? 'all') === 'all')
@if ($isAdmin)
<a href="{{ route('students.import.form') }}" class="inline-flex items-center justify-center gap-2 rounded-2xl border border-emerald-200 bg-emerald-50 px-5 py-3 text-sm font-semibold text-emerald-700 transition hover:bg-emerald-100">
<i class="fa-solid fa-file-import"></i>
<span>Import Data Siswa</span>
</a>
@endif
<a href="{{ route('students.create') }}" class="inline-flex items-center justify-center gap-2 rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
<i class="fa-solid fa-plus"></i>
<span>Tambah Siswa</span>
</a>
@endif
</div>
</form>
</div>
</x-master-card>
<form method="POST" action="{{ route('students.bulk-destroy') }}" class="space-y-4" id="bulk-delete-students-form">
@csrf
@method('DELETE')
<div class="flex flex-col gap-3 rounded-2xl border border-slate-200 bg-white px-5 py-4 shadow-sm lg:flex-row lg:items-center lg:justify-between">
<div class="flex flex-col gap-3 rounded-3xl border border-slate-200 bg-white px-5 py-4 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 lg:flex-row lg:items-center lg:justify-between">
<div class="text-sm text-slate-600">
<div><span class="font-semibold text-slate-800">Total data:</span> {{ $students->total() }} siswa</div>
<div class="text-xs text-slate-400">Halaman {{ $students->currentPage() }} dari {{ $students->lastPage() }}</div>
@@ -86,83 +133,114 @@
{{ $students->onEachSide(1)->links() }}
</div>
<div class="overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-sm shadow-slate-200/50">
<div class="overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-sm shadow-slate-200/50 ring-1 ring-white/70">
<div class="border-b border-slate-200 bg-gradient-to-r from-white via-slate-50 to-emerald-50/30 px-6 py-5">
<div class="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div>
<div class="text-lg font-semibold text-slate-800">Daftar Data Siswa</div>
<div class="mt-1 text-sm text-slate-500">Pantau identitas siswa, posisi akademik, kontak, kelengkapan dokumen, dan status secara ringkas.</div>
</div>
<div class="flex flex-wrap items-center gap-2">
<div class="inline-flex items-center gap-2 rounded-full bg-slate-900 px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.16em] text-white shadow-sm">
<i class="fa-solid fa-users"></i>
{{ $students->total() }} data terfilter
</div>
@if ($search || collect($filters)->filter()->isNotEmpty())
<div class="inline-flex items-center gap-2 rounded-full bg-emerald-100 px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.16em] text-emerald-700 shadow-sm">
<i class="fa-solid fa-filter"></i>
Filter aktif
</div>
@endif
</div>
</div>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-slate-200">
<thead class="bg-slate-50">
<tr>
@if ($isAdmin && ($viewMode ?? 'all') === 'all')
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Pilih</th>
<th class="px-5 py-3.5 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Pilih</th>
@endif
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">
<th class="px-5 py-3.5 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">
<a href="{{ $sortUrl('full_name') }}" class="inline-flex items-center gap-2 hover:text-slate-700">Siswa <span>{{ $sortIcon('full_name') }}</span></a>
</th>
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">
<th class="px-5 py-3.5 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">
<a href="{{ $sortUrl('academic_year') }}" class="inline-flex items-center gap-2 hover:text-slate-700">Akademik <span>{{ $sortIcon('academic_year') }}</span></a>
</th>
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">
<th class="px-5 py-3.5 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">
<a href="{{ $sortUrl('phone_number') }}" class="inline-flex items-center gap-2 hover:text-slate-700">Kontak <span>{{ $sortIcon('phone_number') }}</span></a>
</th>
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Kelengkapan Dokumen</th>
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">
<th class="px-5 py-3.5 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Kelengkapan Dokumen</th>
<th class="px-5 py-3.5 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">
<a href="{{ $sortUrl('status') }}" class="inline-flex items-center gap-2 hover:text-slate-700">Status <span>{{ $sortIcon('status') }}</span></a>
</th>
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Aksi</th>
<th class="px-5 py-3.5 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Aksi</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 bg-white">
@forelse ($students as $student)
<tr>
<tr class="transition hover:bg-emerald-50/30">
@if ($isAdmin && ($viewMode ?? 'all') === 'all')
<td class="px-6 py-4 align-top">
<td class="px-5 py-3.5 align-top">
<input type="checkbox" name="student_ids[]" value="{{ $student->id }}" class="student-row-checkbox rounded border-slate-300 text-indigo-600 focus:ring-indigo-500">
</td>
@endif
<td class="px-6 py-4">
<button
type="button"
class="student-hover-trigger cursor-default font-semibold text-slate-800 underline decoration-dotted underline-offset-4"
data-photo-url="{{ $student->hover_photo_url }}"
data-student-name="{{ $student->full_name }}"
data-student-nis="{{ $student->student_number ?: '-' }}"
data-student-class="{{ $student->classRoom?->name ?: '-' }}"
data-student-department="{{ $student->department?->name ?: '-' }}"
>
{{ $student->full_name }}
</button>
<div class="mt-1 text-xs text-slate-500">NIS: {{ $student->student_number ?: '-' }} · NISN: {{ $student->nisn ?: '-' }}</div>
@if ($student->achievement_badge_count > 0)
@php
$achievementStars = match ($student->achievement_badge_level) {
'Sekolah' => 1,
'Kecamatan' => 2,
'Kabupaten/Kota' => 3,
'Provinsi' => 4,
'Nasional' => 5,
'Internasional' => 6,
default => 1,
};
@endphp
<div class="mt-2 flex items-center gap-1" title="{{ $student->achievement_badge_title }}">
@for ($star = 0; $star < $achievementStars; $star++)
<svg class="h-4 w-4.5" viewBox="0 0 24 24" fill="#facc15" style="color:#facc15">
<path d="M12 3 9.5 8H4l4.5 3.3L6.8 17 12 13.8 17.2 17l-1.7-5.7L20 8h-5.5L12 3Z" />
</svg>
@endfor
<td class="px-5 py-3.5">
<div class="flex items-start gap-3">
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl bg-emerald-100 text-sm font-bold text-emerald-700 shadow-sm shadow-emerald-100/60">
{{ strtoupper(mb_substr($student->full_name, 0, 1)) }}
</div>
@endif
<div class="min-w-0">
<button
type="button"
class="student-hover-trigger cursor-default font-semibold text-slate-800 underline decoration-dotted underline-offset-4"
data-photo-url="{{ $student->hover_photo_url }}"
data-student-name="{{ $student->full_name }}"
data-student-nis="{{ $student->student_number ?: '-' }}"
data-student-class="{{ $student->classRoom?->name ?: '-' }}"
data-student-department="{{ $student->department?->name ?: '-' }}"
>
{{ $student->full_name }}
</button>
<div class="mt-1 text-xs text-slate-500">NIS: {{ $student->student_number ?: '-' }} · NISN: {{ $student->nisn ?: '-' }}</div>
@if ($student->achievement_badge_count > 0)
@php
$achievementStars = match ($student->achievement_badge_level) {
'Sekolah' => 1,
'Kecamatan' => 2,
'Kabupaten/Kota' => 3,
'Provinsi' => 4,
'Nasional' => 5,
'Internasional' => 6,
default => 1,
};
@endphp
<div class="mt-1.5 inline-flex items-center gap-1 rounded-full bg-amber-50 px-2.5 py-1 text-[11px] font-semibold text-amber-700 ring-1 ring-amber-100" title="{{ $student->achievement_badge_title }}">
@for ($star = 0; $star < $achievementStars; $star++)
<svg class="h-3.5 w-4" viewBox="0 0 24 24" fill="#facc15" style="color:#facc15">
<path d="M12 3 9.5 8H4l4.5 3.3L6.8 17 12 13.8 17.2 17l-1.7-5.7L20 8h-5.5L12 3Z" />
</svg>
@endfor
<span>{{ $student->achievement_badge_level }}</span>
</div>
@endif
</div>
</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div>{{ $student->academicYear?->name ?: '-' }}</div>
<div>{{ $student->department?->name ?: '-' }}</div>
<div>{{ $student->classRoom?->name ?: '-' }}</div>
<td class="px-5 py-3.5 text-sm text-slate-600">
<div class="font-medium text-slate-700">{{ $student->academicYear?->name ?: '-' }}</div>
<div class="mt-1">{{ $student->department?->name ?: '-' }}</div>
<div class="text-xs text-slate-500">Kelas: {{ $student->classRoom?->name ?: '-' }}</div>
</td>
<td class="px-6 py-4 text-sm text-slate-600">
<div>{{ $student->phone_number ?: '-' }}</div>
<div>{{ $student->email ?: '-' }}</div>
<td class="px-5 py-3.5 text-sm text-slate-600">
<div class="inline-flex items-center gap-2 rounded-2xl border border-slate-200 bg-slate-50 px-3 py-2">
<i class="fa-solid fa-phone text-xs text-slate-400"></i>
<span>{{ $student->phone_number ?: '-' }}</span>
</div>
<div class="mt-2 text-xs text-slate-500 break-all">{{ $student->email ?: '-' }}</div>
</td>
<td class="px-6 py-4">
<div class="min-w-[180px]">
<td class="px-5 py-3.5">
<div class="min-w-[210px] rounded-2xl border border-slate-200 bg-slate-50/80 px-3.5 py-3.5">
<div class="mb-2 flex items-center justify-between text-xs font-medium text-slate-500">
<span>Upload: {{ $student->required_documents_present }}/{{ $student->required_documents_total }}</span>
<span>{{ $student->document_completion_percentage }}%</span>
@@ -170,19 +248,19 @@
<div class="h-2.5 overflow-hidden rounded-full bg-slate-200">
<div class="h-full rounded-full {{ $student->required_documents_missing === 0 ? 'bg-emerald-500' : 'bg-amber-500' }}" style="width: {{ $student->document_completion_percentage }}%"></div>
</div>
<div class="mt-2 flex items-center justify-between text-xs font-medium text-slate-500">
<div class="mt-2.5 flex items-center justify-between text-xs font-medium text-slate-500">
<span>Terverifikasi: {{ $student->required_documents_verified }}/{{ $student->required_documents_total }}</span>
<span>{{ $student->document_verification_percentage }}%</span>
</div>
<div class="h-2.5 overflow-hidden rounded-full bg-slate-200">
<div class="h-full rounded-full {{ $student->required_documents_unverified === 0 && $student->required_documents_missing === 0 ? 'bg-sky-500' : 'bg-slate-500' }}" style="width: {{ $student->document_verification_percentage }}%"></div>
</div>
<div class="mt-2 text-xs {{ $student->required_documents_missing > 0 ? 'text-amber-600' : ($student->required_documents_unverified > 0 ? 'text-sky-600' : 'text-emerald-600') }}">
<div class="mt-2.5 inline-flex rounded-full px-3 py-1 text-[11px] font-semibold {{ $student->required_documents_missing > 0 ? 'bg-amber-100 text-amber-700' : ($student->required_documents_unverified > 0 ? 'bg-sky-100 text-sky-700' : 'bg-emerald-100 text-emerald-700') }}">
{{ $student->required_documents_missing > 0 ? 'Kurang ' . $student->required_documents_missing . ' dokumen wajib' : ($student->required_documents_unverified > 0 ? $student->required_documents_unverified . ' dokumen menunggu validasi' : 'Lengkap & tervalidasi') }}
</div>
</div>
</td>
<td class="px-6 py-4">
<td class="px-5 py-3.5">
@php
$statusMap = [
'active' => 'bg-emerald-100 text-emerald-700',
@@ -192,21 +270,21 @@
'inactive' => 'bg-slate-100 text-slate-600',
];
@endphp
<span class="rounded-full px-3 py-1 text-xs font-semibold {{ $statusMap[$student->status] ?? 'bg-slate-100 text-slate-600' }}">
<span class="inline-flex rounded-full px-3 py-1 text-xs font-semibold {{ $statusMap[$student->status] ?? 'bg-slate-100 text-slate-600' }}">
{{ $studentStatusLabels[$student->status] ?? $student->status }}
</span>
</td>
<td class="px-6 py-4">
<td class="px-5 py-3.5">
<div class="flex flex-wrap items-center gap-2">
<a href="{{ route('students.show', $student) }}" class="rounded-xl border border-sky-200 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-50">Detail</a>
<a href="{{ route('students.show', $student) }}" class="inline-flex items-center gap-2 rounded-xl border border-sky-200 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-50"><i class="fa-solid fa-eye text-[11px]"></i><span>Detail</span></a>
@if (($viewMode ?? 'all') === 'all')
<a href="{{ route('students.edit', $student) }}" class="rounded-xl border border-slate-200 px-3 py-2 text-xs font-semibold text-slate-700 transition hover:bg-slate-50">Edit</a>
<a href="{{ route('students.edit', $student) }}" class="inline-flex items-center gap-2 rounded-xl border border-slate-200 px-3 py-2 text-xs font-semibold text-slate-700 transition hover:bg-slate-50"><i class="fa-solid fa-pen-to-square text-[11px]"></i><span>Edit</span></a>
@endif
@if ($isAdmin && ($viewMode ?? 'all') === 'all')
<form action="{{ route('students.destroy', $student) }}" method="POST" class="delete-form" data-entity="data siswa {{ $student->full_name }}">
@csrf
@method('DELETE')
<button type="submit" class="rounded-xl border border-rose-200 px-3 py-2 text-xs font-semibold text-rose-600 transition hover:bg-rose-50">Hapus</button>
<button type="submit" class="inline-flex items-center gap-2 rounded-xl border border-rose-200 px-3 py-2 text-xs font-semibold text-rose-600 transition hover:bg-rose-50"><i class="fa-solid fa-trash text-[11px]"></i><span>Hapus</span></button>
</form>
@endif
</div>
@@ -214,7 +292,15 @@
</tr>
@empty
<tr>
<td colspan="{{ ($isAdmin && ($viewMode ?? 'all') === 'all') ? 7 : 6 }}" class="px-6 py-10 text-center text-sm text-slate-500">Belum ada data siswa.</td>
<td colspan="{{ ($isAdmin && ($viewMode ?? 'all') === 'all') ? 7 : 6 }}" class="px-6 py-14 text-center">
<div class="mx-auto flex max-w-md flex-col items-center text-center">
<div class="flex h-14 w-14 items-center justify-center rounded-3xl bg-slate-100 text-slate-400 shadow-sm ring-1 ring-slate-200">
<i class="fa-solid fa-users-slash text-xl"></i>
</div>
<div class="mt-4 text-base font-semibold text-slate-700">Belum ada data siswa</div>
<div class="mt-2 text-sm text-slate-500">Coba ubah filter pencarian atau tambahkan data siswa baru agar tabel mulai terisi.</div>
</div>
</td>
</tr>
@endforelse
</tbody>
@@ -222,11 +308,11 @@
</div>
</div>
<div class="flex flex-col gap-3 rounded-2xl border border-slate-200 bg-white px-5 py-4 shadow-sm lg:flex-row lg:items-center lg:justify-between">
<div class="flex flex-col gap-3 rounded-3xl border border-slate-200 bg-white px-5 py-4 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 lg:flex-row lg:items-center lg:justify-between">
<div class="text-sm text-slate-600">
Menampilkan <span class="font-semibold text-slate-800">{{ $students->firstItem() ?? 0 }}</span> sampai <span class="font-semibold text-slate-800">{{ $students->lastItem() ?? 0 }}</span> dari <span class="font-semibold text-slate-800">{{ $students->total() }}</span> siswa.
</div>
<div>
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-3 py-2 shadow-sm ring-1 ring-white/70">
{{ $students->onEachSide(1)->links() }}
</div>
</div>
+392 -89
View File
@@ -60,40 +60,77 @@
<div class="grid grid-cols-1 gap-6 xl:grid-cols-3">
<x-master-card title="Identitas Utama" subtitle="Informasi inti siswa.">
<div class="space-y-4 text-sm text-slate-600">
<div><span class="font-semibold text-slate-800">Nama:</span> {{ $student->full_name }}</div>
<div><span class="font-semibold text-slate-800">NIS:</span> {{ $student->student_number ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">NISN:</span> {{ $student->nisn ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Jenis Kelamin:</span> {{ $student->gender === 'L' ? 'Laki-laki' : 'Perempuan' }}</div>
<div><span class="font-semibold text-slate-800">TTL:</span> {{ $student->place_of_birth ?: '-' }}, {{ $student->date_of_birth?->format('d-m-Y') ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Agama:</span> {{ $student->religion ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Kewarganegaraan:</span> {{ $student->nationality ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Status:</span> {{ ucfirst($student->status) }}</div>
<div class="mb-4 flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex h-11 w-11 items-center justify-center rounded-2xl bg-indigo-100 text-indigo-600">
<i class="fa-solid fa-id-card text-lg"></i>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Kartu Identitas Siswa</div>
<div class="mt-1 text-sm font-semibold text-slate-800">Data dasar yang paling sering dipakai admin</div>
</div>
</div>
<div class="space-y-3 text-sm text-slate-600">
<div class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70"><i class="fa-solid fa-user mt-0.5 text-slate-400"></i><div><span class="font-semibold text-slate-800">Nama:</span> {{ $student->full_name }}</div></div>
<div class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70"><i class="fa-solid fa-hashtag mt-0.5 text-slate-400"></i><div><span class="font-semibold text-slate-800">NIS:</span> {{ $student->student_number ?: '-' }}</div></div>
<div class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70"><i class="fa-solid fa-fingerprint mt-0.5 text-slate-400"></i><div><span class="font-semibold text-slate-800">NISN:</span> {{ $student->nisn ?: '-' }}</div></div>
<div class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70"><i class="fa-solid fa-id-card-clip mt-0.5 text-slate-400"></i><div><span class="font-semibold text-slate-800">NIK:</span> {{ $student->nik ?: '-' }}</div></div>
<div class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70"><i class="fa-solid fa-venus-mars mt-0.5 text-slate-400"></i><div><span class="font-semibold text-slate-800">Jenis Kelamin:</span> {{ $student->gender === 'L' ? 'Laki-laki' : 'Perempuan' }}</div></div>
<div class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70"><i class="fa-solid fa-cake-candles mt-0.5 text-slate-400"></i><div><span class="font-semibold text-slate-800">TTL:</span> {{ $student->place_of_birth ?: '-' }}, {{ $student->date_of_birth?->format('d-m-Y') ?: '-' }}</div></div>
<div class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70"><i class="fa-solid fa-mosque mt-0.5 text-slate-400"></i><div><span class="font-semibold text-slate-800">Agama:</span> {{ $student->religion ?: '-' }}</div></div>
<div class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70"><i class="fa-solid fa-flag mt-0.5 text-slate-400"></i><div><span class="font-semibold text-slate-800">Kewarganegaraan:</span> {{ $student->nationality ?: '-' }}</div></div>
<div class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70"><i class="fa-solid fa-badge-check mt-0.5 text-slate-400"></i><div><span class="font-semibold text-slate-800">Status:</span> {{ ucfirst($student->status) }}</div></div>
</div>
</x-master-card>
<x-master-card title="Akademik" subtitle="Relasi akademik siswa.">
<div class="space-y-4 text-sm text-slate-600">
<div><span class="font-semibold text-slate-800">Tahun Angkatan:</span> {{ $student->academicYear?->name ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Program Keahlian:</span> {{ $student->department?->name ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Kelas:</span> {{ $student->classRoom?->name ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Tanggal Masuk:</span> {{ $student->entry_date?->format('d-m-Y') ?: '-' }}</div>
<div class="mb-4 flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex h-11 w-11 items-center justify-center rounded-2xl bg-amber-100 text-amber-600">
<i class="fa-solid fa-graduation-cap text-lg"></i>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Snapshot Akademik</div>
<div class="mt-1 text-sm font-semibold text-slate-800">Posisi siswa pada struktur akademik saat ini</div>
</div>
</div>
<div class="space-y-3 text-sm text-slate-600">
<div class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70"><i class="fa-solid fa-calendar-days mt-0.5 text-slate-400"></i><div><span class="font-semibold text-slate-800">Tahun Angkatan:</span> {{ $student->academicYear?->name ?: '-' }}</div></div>
<div class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70"><i class="fa-solid fa-sitemap mt-0.5 text-slate-400"></i><div><span class="font-semibold text-slate-800">Program Keahlian:</span> {{ $student->department?->name ?: '-' }}</div></div>
<div class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70"><i class="fa-solid fa-school mt-0.5 text-slate-400"></i><div><span class="font-semibold text-slate-800">Kelas:</span> {{ $student->classRoom?->name ?: '-' }}</div></div>
<div class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70"><i class="fa-solid fa-door-open mt-0.5 text-slate-400"></i><div><span class="font-semibold text-slate-800">Tanggal Masuk:</span> {{ $student->entry_date?->format('d-m-Y') ?: '-' }}</div></div>
</div>
</x-master-card>
<x-master-card title="Kontak & Alamat" subtitle="Informasi komunikasi siswa.">
<div class="space-y-4 text-sm text-slate-600">
<div><span class="font-semibold text-slate-800">No. HP:</span> {{ $student->phone_number ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Email:</span> {{ $student->email ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Alamat:</span> {{ $student->address ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Wilayah:</span> {{ collect([$student->village, $student->district, $student->city, $student->province])->filter()->join(', ') ?: '-' }}</div>
<div><span class="font-semibold text-slate-800">Kode Pos:</span> {{ $student->postal_code ?: '-' }}</div>
<div class="mb-4 flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex h-11 w-11 items-center justify-center rounded-2xl bg-emerald-100 text-emerald-600">
<i class="fa-solid fa-address-book text-lg"></i>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Kontak dan Domisili</div>
<div class="mt-1 text-sm font-semibold text-slate-800">Saluran komunikasi dan alamat tempat tinggal siswa</div>
</div>
</div>
<div class="space-y-3 text-sm text-slate-600">
<div class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70"><i class="fa-solid fa-phone mt-0.5 text-slate-400"></i><div><span class="font-semibold text-slate-800">No. HP:</span> {{ $student->phone_number ?: '-' }}</div></div>
<div class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70"><i class="fa-solid fa-envelope mt-0.5 text-slate-400"></i><div><span class="font-semibold text-slate-800">Email:</span> {{ $student->email ?: '-' }}</div></div>
<div class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70"><i class="fa-solid fa-location-dot mt-0.5 text-slate-400"></i><div><span class="font-semibold text-slate-800">Alamat:</span> {{ $student->address ?: '-' }}</div></div>
<div class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70"><i class="fa-solid fa-map-location-dot mt-0.5 text-slate-400"></i><div><span class="font-semibold text-slate-800">Wilayah:</span> {{ collect([$student->village, $student->district, $student->city, $student->province])->filter()->join(', ') ?: '-' }}</div></div>
<div class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 shadow-sm ring-1 ring-white/70"><i class="fa-solid fa-mailbox mt-0.5 text-slate-400"></i><div><span class="font-semibold text-slate-800">Kode Pos:</span> {{ $student->postal_code ?: '-' }}</div></div>
</div>
</x-master-card>
</div>
<div class="mt-6 grid grid-cols-1 gap-6 xl:grid-cols-2">
<x-master-card title="Histori Akademik Siswa" subtitle="Riwayat penempatan siswa per tahun angkatan, jurusan, dan kelas.">
<div class="mb-4 flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex h-11 w-11 items-center justify-center rounded-2xl bg-sky-100 text-sky-600">
<i class="fa-solid fa-timeline text-lg"></i>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Timeline Akademik</div>
<div class="mt-1 text-sm font-semibold text-slate-800">Riwayat perpindahan dan status akademik siswa dari waktu ke waktu</div>
</div>
</div>
@if ($student->academicHistories->isEmpty())
<div class="text-sm text-slate-500">Belum ada histori akademik untuk siswa ini.</div>
@else
@@ -131,46 +168,167 @@
</x-master-card>
<x-master-card title="Riwayat Prestasi Siswa" subtitle="Catatan prestasi akademik maupun non-akademik siswa.">
<div class="mb-4">
<a href="{{ route('student-achievements.create', ['student_id' => $student->id]) }}" class="inline-flex items-center rounded-2xl bg-amber-500 px-4 py-2.5 text-sm font-semibold text-white shadow-lg shadow-amber-200 transition hover:bg-amber-600">
Tambah Prestasi
</a>
<div class="mb-5 grid grid-cols-1 gap-4 xl:grid-cols-[1.2fr_0.8fr]">
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4">
<div class="rounded-2xl border border-amber-200 bg-gradient-to-br from-white via-amber-50 to-amber-100/70 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center justify-between gap-3">
<div>
<div class="text-[11px] font-semibold uppercase tracking-[0.16em] text-amber-600">Total Prestasi</div>
<div class="mt-2 text-2xl font-bold text-amber-800">{{ $student->achievements->count() }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-amber-600 ring-1 ring-amber-100">
<i class="fa-solid fa-trophy"></i>
</div>
</div>
</div>
<div class="rounded-2xl border border-indigo-200 bg-gradient-to-br from-white via-indigo-50 to-indigo-100/70 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center justify-between gap-3">
<div>
<div class="text-[11px] font-semibold uppercase tracking-[0.16em] text-indigo-600">Prestasi Terakhir</div>
<div class="mt-2 text-sm font-bold text-indigo-800">{{ $student->achievements->first()?->achievement_year ?: 'Belum ada' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-indigo-600 ring-1 ring-indigo-100">
<i class="fa-solid fa-award"></i>
</div>
</div>
</div>
<div class="rounded-2xl border border-emerald-200 bg-gradient-to-br from-white via-emerald-50 to-emerald-100/70 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center justify-between gap-3">
<div>
<div class="text-[11px] font-semibold uppercase tracking-[0.16em] text-emerald-600">Tingkat Tertinggi</div>
<div class="mt-2 text-sm font-bold text-emerald-800">{{ $student->achievements->pluck('level')->filter()->first() ?: 'Belum diisi' }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-emerald-600 ring-1 ring-emerald-100">
<i class="fa-solid fa-medal"></i>
</div>
</div>
</div>
<div class="rounded-2xl border border-sky-200 bg-gradient-to-br from-white via-sky-50 to-sky-100/70 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center justify-between gap-3">
<div>
<div class="text-[11px] font-semibold uppercase tracking-[0.16em] text-sky-600">Ada Piagam</div>
<div class="mt-2 text-2xl font-bold text-sky-800">{{ $student->achievements->whereNotNull('certificate_file')->count() }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-sky-600 ring-1 ring-sky-100">
<i class="fa-solid fa-file-circle-check"></i>
</div>
</div>
</div>
</div>
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/25 p-5 shadow-sm ring-1 ring-white/70">
<div class="flex items-start gap-3">
<div class="flex h-11 w-11 items-center justify-center rounded-2xl bg-indigo-100 text-indigo-600">
<i class="fa-solid fa-chart-column"></i>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Ringkasan Prestasi</div>
<div class="mt-1 text-sm text-slate-600">Pantau cepat performa siswa, kelengkapan piagam, dan arah prestasi tanpa pindah ke modul utama.</div>
</div>
</div>
<div class="mt-4 flex flex-wrap gap-2 text-xs font-semibold">
<span class="rounded-full bg-amber-50 px-3 py-1.5 text-amber-700">{{ $student->achievements->whereNotNull('activity_photo')->count() }} prestasi ada foto</span>
<span class="rounded-full bg-indigo-50 px-3 py-1.5 text-indigo-700">{{ $student->achievements->pluck('category')->filter()->unique()->count() }} kategori tercatat</span>
</div>
<div class="mt-4">
<a href="{{ route('student-achievements.create', ['student_id' => $student->id]) }}" class="inline-flex items-center justify-center gap-2 rounded-2xl bg-amber-500 px-4 py-2.5 text-sm font-semibold text-white shadow-lg shadow-amber-200 transition hover:bg-amber-600">
<i class="fa-solid fa-plus"></i>
<span>Tambah Prestasi</span>
</a>
</div>
</div>
</div>
@if ($student->achievements->isEmpty())
<div class="text-sm text-slate-500">Belum ada data prestasi untuk siswa ini.</div>
<div class="rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-5 py-6 text-sm text-slate-500">
Belum ada data prestasi untuk siswa ini.
</div>
@else
<div class="space-y-4">
@foreach ($student->achievements as $achievement)
<div class="rounded-3xl border border-slate-200 bg-slate-50 p-5 text-sm text-slate-600 shadow-sm shadow-slate-200/40">
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
<div>
@php
$certificateUrl = $achievement->certificate_file ? asset('storage/' . $achievement->certificate_file) : null;
$certificateExtension = $achievement->certificate_file ? strtolower(pathinfo($achievement->certificate_file, PATHINFO_EXTENSION)) : null;
$certificateIsImage = $certificateExtension && in_array($certificateExtension, ['jpg', 'jpeg', 'png', 'webp']);
@endphp
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-amber-50/20 p-5 text-sm text-slate-600 shadow-sm shadow-slate-200/40 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-200/60">
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div class="flex-1">
<div class="flex flex-wrap items-center gap-2">
<div class="font-semibold text-slate-800">{{ $achievement->achievement_name }}</div>
<div class="text-base font-semibold text-slate-800">{{ $achievement->achievement_name }}</div>
@if ($achievement->level)
<span class="rounded-full bg-amber-100 px-3 py-1 text-xs font-semibold text-amber-700">{{ $achievement->level }}</span>
<span class="rounded-full bg-amber-100 px-3 py-1 text-xs font-semibold text-amber-700">
<i class="fa-solid fa-layer-group mr-1"></i>{{ $achievement->level }}
</span>
@endif
@if ($achievement->rank)
<span class="rounded-full bg-indigo-100 px-3 py-1 text-xs font-semibold text-indigo-700">{{ $achievement->rank }}</span>
<span class="rounded-full bg-indigo-100 px-3 py-1 text-xs font-semibold text-indigo-700">
<i class="fa-solid fa-ranking-star mr-1"></i>{{ $achievement->rank }}
</span>
@endif
</div>
<div class="mt-2">Kategori: {{ $achievement->category ?: '-' }}</div>
<div>Penyelenggara: {{ $achievement->organizer ?: '-' }}</div>
<div>Tahun/Tanggal: {{ $achievement->achievement_year ?: '-' }} · {{ $achievement->achievement_date?->format('d-m-Y') ?: '-' }}</div>
<div class="mt-4 grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3">
<div class="rounded-2xl border border-slate-200 bg-white/80 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500">
<i class="fa-solid fa-tag"></i>
<span>Kategori</span>
</div>
<div class="mt-2 font-semibold text-slate-800">{{ $achievement->category ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white/80 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500">
<i class="fa-solid fa-building-columns"></i>
<span>Penyelenggara</span>
</div>
<div class="mt-2 font-semibold text-slate-800">{{ $achievement->organizer ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white/80 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500">
<i class="fa-solid fa-calendar-days"></i>
<span>Waktu Prestasi</span>
</div>
<div class="mt-2 font-semibold text-slate-800">{{ $achievement->achievement_year ?: '-' }} · {{ $achievement->achievement_date?->format('d-m-Y') ?: '-' }}</div>
</div>
</div>
@if ($achievement->description)
<div class="mt-2 rounded-xl bg-white px-3 py-2 text-xs text-slate-500">
<div class="mt-4 rounded-2xl border border-slate-200 bg-white/80 px-4 py-3 text-xs leading-6 text-slate-500 shadow-sm ring-1 ring-white/70">
<div class="mb-1 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500">
<i class="fa-solid fa-align-left"></i>
<span>Keterangan</span>
</div>
{{ $achievement->description }}
</div>
@endif
</div>
<div class="flex flex-wrap gap-2">
<div class="flex w-full flex-col gap-2 lg:w-auto lg:min-w-[220px]">
@if ($achievement->certificate_file)
<a href="{{ asset('storage/' . $achievement->certificate_file) }}" target="_blank" class="rounded-xl border border-indigo-200 px-3 py-2 text-xs font-semibold text-indigo-700 transition hover:bg-indigo-50">Piagam</a>
@if ($certificateIsImage)
<button type="button" data-lightbox-trigger data-lightbox-src="{{ $certificateUrl }}" data-lightbox-title="Piagam — {{ $achievement->achievement_name }}" class="inline-flex items-center justify-center gap-2 rounded-xl border border-indigo-200 bg-indigo-50 px-3 py-2 text-xs font-semibold text-indigo-700 transition hover:bg-indigo-100">
<i class="fa-solid fa-file-image"></i>
<span>Lihat Piagam</span>
</button>
@else
<a href="{{ $certificateUrl }}" target="_blank" class="inline-flex items-center justify-center gap-2 rounded-xl border border-indigo-200 bg-indigo-50 px-3 py-2 text-xs font-semibold text-indigo-700 transition hover:bg-indigo-100">
<i class="fa-solid fa-file-pdf"></i>
<span>Buka Piagam</span>
</a>
@endif
@endif
@if ($achievement->activity_photo)
<button type="button" data-lightbox-trigger data-lightbox-src="{{ asset('storage/' . $achievement->activity_photo) }}" data-lightbox-title="Foto Kegiatan — {{ $achievement->achievement_name }}" class="rounded-xl border border-emerald-200 px-3 py-2 text-xs font-semibold text-emerald-700 transition hover:bg-emerald-50">Foto</button>
<button type="button" data-lightbox-trigger data-lightbox-src="{{ asset('storage/' . $achievement->activity_photo) }}" data-lightbox-title="Foto Kegiatan — {{ $achievement->achievement_name }}" class="inline-flex items-center justify-center gap-2 rounded-xl border border-emerald-200 bg-emerald-50 px-3 py-2 text-xs font-semibold text-emerald-700 transition hover:bg-emerald-100">
<i class="fa-solid fa-camera"></i>
<span>Lihat Foto</span>
</button>
@endif
<a href="{{ route('student-achievements.edit', $achievement) }}" class="rounded-xl border border-slate-200 px-3 py-2 text-xs font-semibold text-slate-700 transition hover:bg-slate-100">Edit</a>
<a href="{{ route('student-achievements.edit', $achievement) }}" class="inline-flex items-center justify-center gap-2 rounded-xl border border-slate-200 px-3 py-2 text-xs font-semibold text-slate-700 transition hover:bg-slate-100">
<i class="fa-solid fa-pen"></i>
<span>Edit Prestasi</span>
</a>
</div>
</div>
</div>
@@ -182,32 +340,105 @@
<div class="mt-6 grid grid-cols-1 gap-6 xl:grid-cols-2">
<x-master-card title="Data Orang Tua / Wali" subtitle="Kelola data ayah, ibu, atau wali siswa.">
<div class="mb-4">
<a href="{{ route('students.parents.create', $student) }}" class="inline-flex items-center rounded-2xl bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
Tambah Orang Tua / Wali
</a>
<div class="mb-5 grid grid-cols-1 gap-4 xl:grid-cols-[1.15fr_0.85fr]">
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3">
<div class="rounded-2xl border border-indigo-200 bg-gradient-to-br from-white via-indigo-50 to-indigo-100/70 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center justify-between gap-3">
<div>
<div class="text-[11px] font-semibold uppercase tracking-[0.16em] text-indigo-600">Total Data</div>
<div class="mt-2 text-2xl font-bold text-indigo-800">{{ $student->parents->count() }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-indigo-600 ring-1 ring-indigo-100">
<i class="fa-solid fa-people-roof"></i>
</div>
</div>
</div>
<div class="rounded-2xl border border-amber-200 bg-gradient-to-br from-white via-amber-50 to-amber-100/70 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center justify-between gap-3">
<div>
<div class="text-[11px] font-semibold uppercase tracking-[0.16em] text-amber-600">Ayah / Ibu</div>
<div class="mt-2 text-2xl font-bold text-amber-800">{{ $student->parents->whereIn('relation_type', ['father', 'mother'])->count() }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-amber-600 ring-1 ring-amber-100">
<i class="fa-solid fa-people-group"></i>
</div>
</div>
</div>
<div class="rounded-2xl border border-emerald-200 bg-gradient-to-br from-white via-emerald-50 to-emerald-100/70 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center justify-between gap-3">
<div>
<div class="text-[11px] font-semibold uppercase tracking-[0.16em] text-emerald-600">No. HP Tersedia</div>
<div class="mt-2 text-2xl font-bold text-emerald-800">{{ $student->parents->filter(fn ($parent) => filled($parent->phone_number))->count() }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-emerald-600 ring-1 ring-emerald-100">
<i class="fa-solid fa-phone-volume"></i>
</div>
</div>
</div>
</div>
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/25 p-5 shadow-sm ring-1 ring-white/70">
<div class="flex items-start gap-3">
<div class="flex h-11 w-11 items-center justify-center rounded-2xl bg-indigo-100 text-indigo-600">
<i class="fa-solid fa-id-card-clip"></i>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Ringkasan Kontak Keluarga</div>
<div class="mt-1 text-sm text-slate-600">Pastikan relasi keluarga inti dan kontak penting siswa sudah tersimpan lengkap.</div>
</div>
</div>
<div class="mt-4 flex flex-wrap gap-2 text-xs font-semibold">
<span class="rounded-full bg-amber-50 px-3 py-1.5 text-amber-700">{{ $student->parents->where('relation_type', 'guardian')->count() }} data wali</span>
<span class="rounded-full bg-sky-50 px-3 py-1.5 text-sky-700">{{ $student->parents->filter(fn ($parent) => filled($parent->occupation))->count() }} pekerjaan terisi</span>
</div>
<div class="mt-4">
<a href="{{ route('students.parents.create', $student) }}" class="inline-flex items-center gap-2 rounded-2xl bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
<i class="fa-solid fa-plus"></i>
<span>Tambah Orang Tua / Wali</span>
</a>
</div>
</div>
</div>
@if ($student->parents->isEmpty())
<div class="text-sm text-slate-500">Belum ada data orang tua/wali untuk siswa ini.</div>
<div class="rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-5 py-6 text-sm text-slate-500">Belum ada data orang tua/wali untuk siswa ini.</div>
@else
<div class="space-y-4">
@foreach ($student->parents as $parent)
<div class="rounded-2xl border border-slate-200 bg-slate-50 p-4 text-sm text-slate-600">
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
<div>
<div class="font-semibold text-slate-800">{{ ucfirst($parent->relation_type === 'father' ? 'Ayah' : ($parent->relation_type === 'mother' ? 'Ibu' : 'Wali')) }} - {{ $parent->name }}</div>
<div class="mt-1">Pekerjaan: {{ $parent->occupation ?: '-' }}</div>
<div>No. HP: {{ $parent->phone_number ?: '-' }}</div>
<div>Pendidikan: {{ $parent->education ?: '-' }}</div>
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/20 p-5 text-sm text-slate-600 shadow-sm shadow-slate-200/40 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-200/60">
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div class="flex-1">
<div class="flex flex-wrap items-center gap-2">
<div class="text-base font-semibold text-slate-800">{{ ucfirst($parent->relation_type === 'father' ? 'Ayah' : ($parent->relation_type === 'mother' ? 'Ibu' : 'Wali')) }} - {{ $parent->name }}</div>
<span class="rounded-full bg-indigo-100 px-3 py-1 text-xs font-semibold text-indigo-700">
<i class="fa-solid fa-user-tag mr-1"></i>{{ ucfirst($parent->relation_type === 'father' ? 'Ayah' : ($parent->relation_type === 'mother' ? 'Ibu' : 'Wali')) }}
</span>
</div>
<div class="mt-4 grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3">
<div class="rounded-2xl border border-slate-200 bg-white/80 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-briefcase"></i><span>Pekerjaan</span></div>
<div class="mt-2 font-semibold text-slate-800">{{ $parent->occupation ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white/80 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-phone"></i><span>No. HP</span></div>
<div class="mt-2 font-semibold text-slate-800">{{ $parent->phone_number ?: '-' }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white/80 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-graduation-cap"></i><span>Pendidikan</span></div>
<div class="mt-2 font-semibold text-slate-800">{{ $parent->education ?: '-' }}</div>
</div>
</div>
</div>
<div class="flex flex-wrap gap-2">
<a href="{{ route('students.parents.edit', [$student, $parent]) }}" class="rounded-xl border border-slate-200 px-3 py-2 text-xs font-semibold text-slate-700 transition hover:bg-slate-100">Edit</a>
<div class="flex w-full flex-col gap-2 lg:w-auto lg:min-w-[220px]">
<a href="{{ route('students.parents.edit', [$student, $parent]) }}" class="inline-flex items-center justify-center gap-2 rounded-xl border border-slate-200 px-3 py-2 text-xs font-semibold text-slate-700 transition hover:bg-slate-100"><i class="fa-solid fa-pen"></i><span>Edit</span></a>
@if ($isAdmin)
<form action="{{ route('students.parents.destroy', [$student, $parent]) }}" method="POST" class="delete-form" data-entity="data {{ $parent->name }}">
@csrf
@method('DELETE')
<button type="submit" class="rounded-xl border border-rose-200 px-3 py-2 text-xs font-semibold text-rose-600 transition hover:bg-rose-50">Hapus</button>
<button type="submit" class="inline-flex w-full items-center justify-center gap-2 rounded-xl border border-rose-200 px-3 py-2 text-xs font-semibold text-rose-600 transition hover:bg-rose-50"><i class="fa-solid fa-trash"></i><span>Hapus</span></button>
</form>
@endif
</div>
@@ -219,14 +450,73 @@
</x-master-card>
<x-master-card title="Dokumen Siswa" subtitle="Kelola file dan status verifikasi dokumen siswa.">
<div class="mb-4">
<a href="{{ route('students.documents.create', $student) }}" class="inline-flex items-center rounded-2xl bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
Tambah Dokumen
</a>
<div class="mb-5 grid grid-cols-1 gap-4 xl:grid-cols-[1.15fr_0.85fr]">
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4">
<div class="rounded-2xl border border-indigo-200 bg-gradient-to-br from-white via-indigo-50 to-indigo-100/70 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center justify-between gap-3">
<div>
<div class="text-[11px] font-semibold uppercase tracking-[0.16em] text-indigo-600">Total Dokumen</div>
<div class="mt-2 text-2xl font-bold text-indigo-800">{{ $student->documents->count() }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-indigo-600 ring-1 ring-indigo-100"><i class="fa-solid fa-folder-open"></i></div>
</div>
</div>
<div class="rounded-2xl border border-emerald-200 bg-gradient-to-br from-white via-emerald-50 to-emerald-100/70 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center justify-between gap-3">
<div>
<div class="text-[11px] font-semibold uppercase tracking-[0.16em] text-emerald-600">Valid</div>
<div class="mt-2 text-2xl font-bold text-emerald-800">{{ $student->documents->where('verification_status', 'valid')->count() }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-emerald-600 ring-1 ring-emerald-100"><i class="fa-solid fa-circle-check"></i></div>
</div>
</div>
<div class="rounded-2xl border border-amber-200 bg-gradient-to-br from-white via-amber-50 to-amber-100/70 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center justify-between gap-3">
<div>
<div class="text-[11px] font-semibold uppercase tracking-[0.16em] text-amber-600">Perlu Revisi</div>
<div class="mt-2 text-2xl font-bold text-amber-800">{{ $student->documents->where('verification_status', 'revision')->count() }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-amber-600 ring-1 ring-amber-100"><i class="fa-solid fa-rotate-left"></i></div>
</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-slate-100/70 px-4 py-4 shadow-sm ring-1 ring-white/70">
<div class="flex items-center justify-between gap-3">
<div>
<div class="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Pending</div>
<div class="mt-2 text-2xl font-bold text-slate-800">{{ $student->documents->where('verification_status', 'pending')->count() }}</div>
</div>
<div class="rounded-2xl bg-white/80 p-3 text-slate-600 ring-1 ring-slate-200"><i class="fa-solid fa-hourglass-half"></i></div>
</div>
</div>
</div>
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/25 p-5 shadow-sm ring-1 ring-white/70">
<div class="flex items-start gap-3">
<div class="flex h-11 w-11 items-center justify-center rounded-2xl bg-indigo-100 text-indigo-600">
<i class="fa-solid fa-file-shield"></i>
</div>
<div>
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Ringkasan Verifikasi</div>
<div class="mt-1 text-sm text-slate-600">Pantau kualitas dokumen siswa dan percepat tindakan verifikasi dari satu tempat.</div>
</div>
</div>
<div class="mt-4 flex flex-wrap gap-2 text-xs font-semibold">
<span class="rounded-full bg-rose-50 px-3 py-1.5 text-rose-700">{{ $student->documents->where('verification_status', 'invalid')->count() }} invalid</span>
<span class="rounded-full bg-sky-50 px-3 py-1.5 text-sky-700">{{ $student->documents->filter(fn ($document) => filled($document->verified_by))->count() }} sudah diverifikasi</span>
</div>
<div class="mt-4">
<a href="{{ route('students.documents.create', $student) }}" class="inline-flex items-center gap-2 rounded-2xl bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
<i class="fa-solid fa-plus"></i>
<span>Tambah Dokumen</span>
</a>
</div>
</div>
</div>
@if ($student->documents->isEmpty())
<div class="text-sm text-slate-500">Belum ada dokumen siswa yang diunggah.</div>
<div class="rounded-2xl border border-dashed border-slate-200 bg-slate-50 px-5 py-6 text-sm text-slate-500">Belum ada dokumen siswa yang diunggah.</div>
@else
<div class="space-y-4">
@foreach ($student->documents as $document)
@@ -238,63 +528,76 @@
'revision' => 'bg-amber-100 text-amber-700',
];
@endphp
<div class="rounded-3xl border border-slate-200 bg-slate-50 p-5 text-sm text-slate-600 shadow-sm shadow-slate-200/40">
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
<div>
<div class="font-semibold text-slate-800">{{ $document->documentType?->name ?: '-' }}</div>
<div class="mt-1">File: {{ $document->file_name }}</div>
<div>Format: {{ strtoupper($document->file_extension ?: '-') }}</div>
<div>Ukuran: {{ $document->file_size ? number_format($document->file_size / 1024, 1) . ' KB' : '-' }}</div>
@if ($document->verifiedBy)
<div>Diverifikasi oleh: {{ $document->verifiedBy->name }}</div>
@endif
<div class="mt-2 flex flex-wrap gap-2">
<span class="rounded-full px-3 py-1 text-xs font-semibold {{ $verificationMap[$document->verification_status] ?? 'bg-slate-100 text-slate-600' }}">
{{ ucfirst($document->verification_status) }}
</span>
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50/20 p-5 text-sm text-slate-600 shadow-sm shadow-slate-200/40 ring-1 ring-white/70 transition hover:-translate-y-0.5 hover:shadow-lg hover:shadow-slate-200/60">
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div class="flex-1">
<div class="flex flex-wrap items-center gap-2">
<div class="text-base font-semibold text-slate-800">{{ $document->documentType?->name ?: '-' }}</div>
<span class="rounded-full px-3 py-1 text-xs font-semibold {{ $verificationMap[$document->verification_status] ?? 'bg-slate-100 text-slate-600' }}">{{ ucfirst($document->verification_status) }}</span>
@if ($document->verified_at)
<span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-600">
Diverifikasi {{ $document->verified_at->format('d-m-Y H:i') }}
</span>
<span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-600">Diverifikasi {{ $document->verified_at->format('d-m-Y H:i') }}</span>
@endif
</div>
<div class="mt-4 grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3">
<div class="rounded-2xl border border-slate-200 bg-white/80 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-file-signature"></i><span>Nama File</span></div>
<div class="mt-2 break-words font-semibold text-slate-800">{{ $document->file_name }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white/80 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-file-code"></i><span>Format</span></div>
<div class="mt-2 font-semibold text-slate-800">{{ strtoupper($document->file_extension ?: '-') }}</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white/80 px-4 py-3 shadow-sm ring-1 ring-white/70">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-weight-scale"></i><span>Ukuran</span></div>
<div class="mt-2 font-semibold text-slate-800">{{ $document->file_size ? number_format($document->file_size / 1024, 1) . ' KB' : '-' }}</div>
</div>
@if ($document->verifiedBy)
<div class="rounded-2xl border border-slate-200 bg-white/80 px-4 py-3 shadow-sm ring-1 ring-white/70 md:col-span-2 xl:col-span-3">
<div class="flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-user-check"></i><span>Diverifikasi Oleh</span></div>
<div class="mt-2 font-semibold text-slate-800">{{ $document->verifiedBy->name }}</div>
</div>
@endif
</div>
@if ($document->verification_note)
<div class="mt-2 rounded-xl bg-white px-3 py-2 text-xs text-slate-500">
Catatan: {{ $document->verification_note }}
<div class="mt-4 rounded-2xl border border-slate-200 bg-white/80 px-4 py-3 text-xs leading-6 text-slate-500 shadow-sm ring-1 ring-white/70">
<div class="mb-1 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.14em] text-slate-500"><i class="fa-solid fa-align-left"></i><span>Catatan Verifikasi</span></div>
{{ $document->verification_note }}
</div>
@endif
</div>
<div class="flex flex-wrap gap-2">
<a href="{{ route('students.documents.preview', $document) }}" class="rounded-xl border border-indigo-200 px-3 py-2 text-xs font-semibold text-indigo-700 transition hover:bg-indigo-50">Lihat</a>
<a href="{{ route('students.documents.download', $document) }}" class="rounded-xl border border-sky-200 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-50">Download</a>
<a href="{{ route('students.documents.edit', [$student, $document]) }}" class="rounded-xl border border-slate-200 px-3 py-2 text-xs font-semibold text-slate-700 transition hover:bg-slate-100">Edit</a>
<div class="flex w-full flex-col gap-2 lg:w-auto lg:min-w-[220px]">
<a href="{{ route('students.documents.preview', $document) }}" class="inline-flex items-center justify-center gap-2 rounded-xl border border-indigo-200 bg-indigo-50 px-3 py-2 text-xs font-semibold text-indigo-700 transition hover:bg-indigo-100"><i class="fa-solid fa-eye"></i><span>Lihat</span></a>
<a href="{{ route('students.documents.download', $document) }}" class="inline-flex items-center justify-center gap-2 rounded-xl border border-sky-200 bg-sky-50 px-3 py-2 text-xs font-semibold text-sky-700 transition hover:bg-sky-100"><i class="fa-solid fa-download"></i><span>Download</span></a>
<a href="{{ route('students.documents.edit', [$student, $document]) }}" class="inline-flex items-center justify-center gap-2 rounded-xl border border-slate-200 px-3 py-2 text-xs font-semibold text-slate-700 transition hover:bg-slate-100"><i class="fa-solid fa-pen"></i><span>Edit</span></a>
@if ($isAdmin)
<form action="{{ route('students.documents.quick-verify', [$student, $document]) }}" method="POST" class="inline-flex gap-2">
<form action="{{ route('students.documents.quick-verify', [$student, $document]) }}" method="POST">
@csrf
<input type="hidden" name="verification_status" value="valid">
<input type="hidden" name="verification_note" value="Dokumen dinyatakan valid.">
<button type="submit" @disabled($document->verification_status === 'valid') class="rounded-xl border border-emerald-200 px-3 py-2 text-xs font-semibold text-emerald-700 transition hover:bg-emerald-50 disabled:cursor-not-allowed disabled:bg-emerald-100 disabled:text-emerald-500">Valid</button>
<button type="submit" @disabled($document->verification_status === 'valid') class="inline-flex w-full items-center justify-center gap-2 rounded-xl border border-emerald-200 px-3 py-2 text-xs font-semibold text-emerald-700 transition hover:bg-emerald-50 disabled:cursor-not-allowed disabled:bg-emerald-100 disabled:text-emerald-500"><i class="fa-solid fa-circle-check"></i><span>Valid</span></button>
</form>
<form action="{{ route('students.documents.quick-verify', [$student, $document]) }}" method="POST" class="inline-flex gap-2">
<form action="{{ route('students.documents.quick-verify', [$student, $document]) }}" method="POST">
@csrf
<input type="hidden" name="verification_status" value="revision">
<input type="hidden" name="verification_note" value="Dokumen perlu revisi.">
<button type="submit" @disabled($document->verification_status === 'revision') class="rounded-xl border border-amber-200 px-3 py-2 text-xs font-semibold text-amber-700 transition hover:bg-amber-50 disabled:cursor-not-allowed disabled:bg-amber-100 disabled:text-amber-500">Revisi</button>
<button type="submit" @disabled($document->verification_status === 'revision') class="inline-flex w-full items-center justify-center gap-2 rounded-xl border border-amber-200 px-3 py-2 text-xs font-semibold text-amber-700 transition hover:bg-amber-50 disabled:cursor-not-allowed disabled:bg-amber-100 disabled:text-amber-500"><i class="fa-solid fa-rotate-left"></i><span>Revisi</span></button>
</form>
<form action="{{ route('students.documents.quick-verify', [$student, $document]) }}" method="POST" class="inline-flex gap-2">
<form action="{{ route('students.documents.quick-verify', [$student, $document]) }}" method="POST">
@csrf
<input type="hidden" name="verification_status" value="invalid">
<input type="hidden" name="verification_note" value="Dokumen dinyatakan tidak valid.">
<button type="submit" @disabled($document->verification_status === 'invalid') class="rounded-xl border border-rose-200 px-3 py-2 text-xs font-semibold text-rose-700 transition hover:bg-rose-50 disabled:cursor-not-allowed disabled:bg-rose-100 disabled:text-rose-500">Invalid</button>
<button type="submit" @disabled($document->verification_status === 'invalid') class="inline-flex w-full items-center justify-center gap-2 rounded-xl border border-rose-200 px-3 py-2 text-xs font-semibold text-rose-700 transition hover:bg-rose-50 disabled:cursor-not-allowed disabled:bg-rose-100 disabled:text-rose-500"><i class="fa-solid fa-circle-xmark"></i><span>Invalid</span></button>
</form>
<form action="{{ route('students.documents.destroy', [$student, $document]) }}" method="POST" class="delete-form" data-entity="dokumen {{ $document->file_name }}">
@csrf
@method('DELETE')
<button type="submit" class="rounded-xl border border-rose-200 px-3 py-2 text-xs font-semibold text-rose-600 transition hover:bg-rose-50">Hapus</button>
<button type="submit" class="inline-flex w-full items-center justify-center gap-2 rounded-xl border border-rose-200 px-3 py-2 text-xs font-semibold text-rose-600 transition hover:bg-rose-50"><i class="fa-solid fa-trash"></i><span>Hapus</span></button>
</form>
@endif
</div>
+1
View File
@@ -81,6 +81,7 @@ Route::middleware(['auth', 'verified'])->group(function () {
Route::post('students/import/preview', [StudentController::class, 'previewImport'])->name('students.import.preview');
Route::post('students/import', [StudentController::class, 'import'])->name('students.import');
Route::get('students/import/template', [StudentController::class, 'downloadImportTemplate'])->name('students.import.template');
Route::get('students/import/example', [StudentController::class, 'downloadImportExample'])->name('students.import.example');
Route::delete('students/bulk-delete', [StudentController::class, 'bulkDestroy'])->name('students.bulk-destroy');
Route::resource('students', StudentController::class);
Route::get('student-promotions', [StudentPromotionController::class, 'index'])->name('student-promotions.index');