feat: add graduation archive and public document services
This commit is contained in:
@@ -1,6 +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 asal dan tujuan untuk menyiapkan rekomendasi promosi.">
|
||||
<form method="GET" action="{{ route('student-promotions.index') }}" class="grid grid-cols-1 gap-4 lg:grid-cols-3 lg:items-end">
|
||||
<x-master-card title="Filter Promosi" subtitle="Pilih tahun ajaran, lalu fokuskan proses per kelas agar pengecualian siswa lebih mudah dikelola.">
|
||||
<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>
|
||||
<select id="from_academic_year_id" name="from_academic_year_id" 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-2 focus:ring-indigo-100">
|
||||
@@ -25,6 +25,18 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="class_id" class="mb-2 block text-sm font-medium text-slate-700">Kelas Asal</label>
|
||||
<select id="class_id" name="class_id" 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-2 focus:ring-indigo-100">
|
||||
<option value="">Pilih kelas asal</option>
|
||||
@foreach ($availableClasses as $classRoom)
|
||||
<option value="{{ $classRoom->id }}" @selected((int) $filters['class_id'] === (int) $classRoom->id)>
|
||||
{{ $classRoom->name }} — {{ $classRoom->department?->name }} ({{ $classRoom->active_students_count }} siswa)
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</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
|
||||
@@ -33,18 +45,64 @@
|
||||
</form>
|
||||
</x-master-card>
|
||||
|
||||
<x-master-card title="Daftar Rekomendasi Promosi" subtitle="Sistem memberi saran berdasarkan level kelas saat ini. Admin tetap bisa menyesuaikan sebelum diproses.">
|
||||
<x-master-card title="Kenaikan Kelas per Rombel" subtitle="Mayoritas siswa biasanya mengikuti hasil kelasnya, lalu admin tinggal mengubah siswa pengecualian.">
|
||||
@if (blank($filters['from_academic_year_id']) || blank($filters['to_academic_year_id']))
|
||||
<div class="rounded-2xl border border-amber-200 bg-amber-50 px-5 py-4 text-sm text-amber-700">
|
||||
Pilih tahun ajaran asal dan tujuan terlebih dahulu untuk memuat rekomendasi promosi siswa.
|
||||
Pilih tahun ajaran asal dan tujuan terlebih dahulu untuk melihat kelas yang siap diproses.
|
||||
</div>
|
||||
@elseif ($classSummaries->isEmpty())
|
||||
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-5 py-4 text-sm text-slate-500">
|
||||
Belum ada kelas aktif dengan siswa aktif pada tahun ajaran asal yang dipilih.
|
||||
</div>
|
||||
@elseif (blank($filters['class_id']) || ! $selectedClass)
|
||||
<div class="mb-6 rounded-2xl border border-indigo-200 bg-indigo-50 px-5 py-4 text-sm text-indigo-700">
|
||||
Pilih <span class="font-semibold">kelas asal</span> untuk memproses siswa per rombel. Ini lebih aman daripada menampilkan semua siswa sekaligus.
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 xl:grid-cols-2">
|
||||
@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"
|
||||
>
|
||||
<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="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="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="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>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
@elseif ($students->isEmpty())
|
||||
<div class="text-sm text-slate-500">Tidak ada siswa aktif pada tahun ajaran asal yang dipilih.</div>
|
||||
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-5 py-4 text-sm text-slate-500">
|
||||
Tidak ada siswa aktif pada kelas asal yang dipilih.
|
||||
</div>
|
||||
@else
|
||||
@php
|
||||
$promotionCount = $students->filter(fn ($student) => $student->promotion_recommendation['action'] === 'promote')->count();
|
||||
$repeatCount = $students->filter(fn ($student) => $student->promotion_recommendation['action'] === 'repeat')->count();
|
||||
$graduateCount = $students->filter(fn ($student) => $student->promotion_recommendation['action'] === 'graduate')->count();
|
||||
$skipCount = $students->filter(fn ($student) => $student->promotion_recommendation['action'] === 'skip')->count();
|
||||
@endphp
|
||||
|
||||
@if ($errors->any())
|
||||
@@ -58,36 +116,69 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="mb-6 grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
<div class="rounded-3xl border border-emerald-200 bg-emerald-50 px-5 py-4">
|
||||
<div class="mb-6 rounded-3xl border border-slate-200 bg-slate-50 px-5 py-4">
|
||||
<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>
|
||||
</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="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">{{ $promotionCount }}</div>
|
||||
<div class="mt-1 text-sm text-emerald-600">Siswa direkomendasikan naik ke level berikutnya.</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">
|
||||
<div class="rounded-3xl border border-amber-200 bg-amber-50 px-5 py-4" 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">{{ $repeatCount }}</div>
|
||||
<div class="mt-1 text-sm text-amber-600">Siswa akan tetap di level yang sama pada tahun ajaran baru.</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">
|
||||
<div class="rounded-3xl border border-sky-200 bg-sky-50 px-5 py-4" 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">{{ $graduateCount }}</div>
|
||||
<div class="mt-1 text-sm text-sky-600">Siswa kelas akhir yang direkomendasikan 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="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>
|
||||
|
||||
<form method="POST" action="{{ route('student-promotions.store') }}" class="space-y-6" data-student-promotion-form>
|
||||
@csrf
|
||||
<input type="hidden" name="from_academic_year_id" value="{{ $filters['from_academic_year_id'] }}">
|
||||
<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">
|
||||
<div class="text-sm font-medium text-slate-700">Aksi cepat:</div>
|
||||
<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="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>
|
||||
</div>
|
||||
|
||||
@if (strtoupper((string) $selectedClass->level) === 'XII')
|
||||
<div class="mt-4 rounded-2xl border border-sky-200 bg-sky-50 px-5 py-4 text-sm text-sky-700">
|
||||
Kelas ini berada di level <span class="font-semibold">XII</span>. Default operasionalnya biasanya <span class="font-semibold">Lulus</span>, lalu admin hanya mengubah siswa pengecualian bila perlu.
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<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">
|
||||
<table class="min-w-full divide-y divide-slate-200">
|
||||
<thead class="bg-slate-50">
|
||||
@@ -127,6 +218,7 @@
|
||||
'option_label' => $classRoom->option_label,
|
||||
'capacity_label' => $classRoom->capacity_label,
|
||||
'remaining_capacity' => $classRoom->remaining_capacity,
|
||||
'warning_level' => is_null($classRoom->remaining_capacity) ? 'normal' : ($classRoom->remaining_capacity <= 0 ? 'full' : ($classRoom->remaining_capacity <= 3 ? 'low' : 'normal')),
|
||||
])->values();
|
||||
$repeatClassOptions = $repeatClasses->map(fn ($classRoom) => [
|
||||
'id' => $classRoom->id,
|
||||
@@ -134,6 +226,7 @@
|
||||
'option_label' => $classRoom->option_label,
|
||||
'capacity_label' => $classRoom->capacity_label,
|
||||
'remaining_capacity' => $classRoom->remaining_capacity,
|
||||
'warning_level' => is_null($classRoom->remaining_capacity) ? 'normal' : ($classRoom->remaining_capacity <= 0 ? 'full' : ($classRoom->remaining_capacity <= 3 ? 'low' : 'normal')),
|
||||
])->values();
|
||||
$badgeClass = match ($recommendation['action']) {
|
||||
'promote' => 'bg-emerald-100 text-emerald-700',
|
||||
@@ -178,7 +271,7 @@
|
||||
<option value="graduate" @selected($oldAction === 'graduate')>Lulus</option>
|
||||
<option value="skip" @selected($oldAction === 'skip')>Lewati</option>
|
||||
</select>
|
||||
<div class="mt-2 text-xs text-slate-500">Admin bisa ubah rekomendasi sebelum proses.</div>
|
||||
<div class="mt-2 text-xs text-slate-500">Ubah hanya untuk siswa pengecualian.</div>
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-500 promotion-graduate-placeholder {{ $oldAction === 'graduate' ? '' : 'hidden' }}">
|
||||
@@ -204,10 +297,11 @@
|
||||
<div class="mt-2 text-xs {{ $defaultClasses->isEmpty() ? 'text-amber-600' : 'text-slate-500' }} promotion-target-help">
|
||||
{{ $defaultClasses->isEmpty() ? 'Belum ada kelas aktif yang cocok pada tahun ajaran target untuk aksi ini.' : 'Pilih kelas tujuan sesuai aksi. Naik kelas memakai level berikutnya, tinggal kelas memakai level yang sama.' }}
|
||||
</div>
|
||||
<div class="mt-2 hidden rounded-2xl border px-3 py-2 text-xs promotion-capacity-warning" data-promotion-capacity-warning></div>
|
||||
@if ($defaultClasses->isNotEmpty())
|
||||
<div class="mt-2 space-y-1 text-[11px] text-slate-500">
|
||||
@foreach ($defaultClasses as $classRoom)
|
||||
<div>• {{ $classRoom->name }} — {{ $classRoom->capacity_label }}</div>
|
||||
<div class="{{ filled($classRoom->remaining_capacity) && $classRoom->remaining_capacity <= 0 ? 'text-rose-600 font-semibold' : (filled($classRoom->remaining_capacity) && $classRoom->remaining_capacity <= 3 ? 'text-amber-600 font-semibold' : '') }}">• {{ $classRoom->name }} — {{ $classRoom->capacity_label }}</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@@ -223,11 +317,11 @@
|
||||
|
||||
<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="space-y-1 text-sm text-slate-500">
|
||||
<div>Total siswa siap diproses: <span class="font-semibold text-slate-800">{{ $students->count() }}</span></div>
|
||||
<div class="text-xs text-slate-400">Tips: untuk siswa tinggal kelas, pastikan tahun ajaran target punya kelas dengan level yang sama dan sisa kuota masih cukup.</div>
|
||||
<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">
|
||||
Proses Promosi Siswa
|
||||
<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>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user