Initial commit
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
<x-master-page title="Tambah Tahun Angkatan" description="Tambahkan data tahun ajaran baru untuk kebutuhan kesiswaan.">
|
||||
<x-master-card title="Form Tahun Angkatan" subtitle="Isi data tahun angkatan dengan lengkap dan benar.">
|
||||
<form action="{{ route('academic-years.store') }}" method="POST" class="space-y-6">
|
||||
@csrf
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<x-form-input name="name" label="Nama Tahun Angkatan" placeholder="Contoh: 2024/2025" required />
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<x-form-input name="start_year" label="Tahun Mulai" type="number" placeholder="2024" required />
|
||||
<x-form-input name="end_year" label="Tahun Selesai" type="number" placeholder="2025" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<x-form-checkbox name="is_active" label="Jadikan tahun angkatan aktif" :checked="false" />
|
||||
|
||||
<div class="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">Simpan</button>
|
||||
<a href="{{ route('academic-years.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>
|
||||
</form>
|
||||
</x-master-card>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,23 @@
|
||||
<x-master-page title="Edit Tahun Angkatan" description="Perbarui data tahun angkatan yang sudah ada.">
|
||||
<x-master-card title="Form Edit Tahun Angkatan" subtitle="Sesuaikan data lalu simpan perubahan.">
|
||||
<form action="{{ route('academic-years.update', $academicYear) }}" method="POST" class="space-y-6">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<x-form-input name="name" label="Nama Tahun Angkatan" :value="$academicYear->name" placeholder="Contoh: 2024/2025" required />
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<x-form-input name="start_year" label="Tahun Mulai" type="number" :value="$academicYear->start_year" placeholder="2024" required />
|
||||
<x-form-input name="end_year" label="Tahun Selesai" type="number" :value="$academicYear->end_year" placeholder="2025" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<x-form-checkbox name="is_active" label="Jadikan tahun angkatan aktif" :checked="$academicYear->is_active" />
|
||||
|
||||
<div class="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">Update</button>
|
||||
<a href="{{ route('academic-years.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>
|
||||
</form>
|
||||
</x-master-card>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,70 @@
|
||||
<x-master-page
|
||||
title="Tahun Angkatan"
|
||||
description="Kelola master tahun ajaran dan tahun angkatan siswa."
|
||||
action-label="Tambah Tahun Angkatan"
|
||||
:action-url="route('academic-years.create')"
|
||||
>
|
||||
<x-master-card title="Filter Data" subtitle="Cari tahun angkatan berdasarkan nama atau tahun.">
|
||||
<form method="GET" action="{{ route('academic-years.index') }}" class="flex flex-col gap-4 md:flex-row md:items-end">
|
||||
<div class="md:flex-1">
|
||||
<x-form-input name="search" label="Pencarian" :value="$search" placeholder="Cari tahun angkatan..." />
|
||||
</div>
|
||||
<div class="flex items-end gap-3">
|
||||
<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('academic-years.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>
|
||||
</form>
|
||||
</x-master-card>
|
||||
|
||||
<x-data-table :headers="['Nama', 'Tahun Mulai', 'Tahun Selesai', 'Dipakai', 'Status', 'Aksi']">
|
||||
@forelse ($academicYears as $academicYear)
|
||||
<tr>
|
||||
<td class="px-6 py-4">
|
||||
<div class="font-semibold text-slate-800">{{ $academicYear->name }}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-600">{{ $academicYear->start_year }}</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-600">{{ $academicYear->end_year }}</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="rounded-full bg-sky-100 px-3 py-1 text-xs font-semibold text-sky-700">Kelas: {{ $academicYear->classes_count }}</span>
|
||||
<span class="rounded-full bg-indigo-100 px-3 py-1 text-xs font-semibold text-indigo-700">Siswa: {{ $academicYear->students_count }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
@if ($academicYear->is_active)
|
||||
<span class="rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700">Aktif</span>
|
||||
@else
|
||||
<span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-600">Nonaktif</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<a href="{{ route('academic-years.edit', $academicYear) }}" 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>
|
||||
@php
|
||||
$canDelete = $academicYear->classes_count === 0 && $academicYear->students_count === 0;
|
||||
@endphp
|
||||
@if ($canDelete)
|
||||
<form action="{{ route('academic-years.destroy', $academicYear) }}" method="POST" class="delete-form" data-entity="tahun angkatan {{ $academicYear->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>
|
||||
@else
|
||||
<button type="button" disabled class="cursor-not-allowed rounded-xl border border-slate-200 bg-slate-100 px-3 py-2 text-xs font-semibold text-slate-400" title="Tidak bisa dihapus karena masih dipakai data lain">
|
||||
Dipakai
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="px-6 py-10 text-center text-sm text-slate-500">Belum ada data tahun angkatan.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</x-data-table>
|
||||
|
||||
<div>
|
||||
{{ $academicYears->links() }}
|
||||
</div>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,27 @@
|
||||
<x-guest-layout>
|
||||
<div class="mb-4 text-sm text-gray-600">
|
||||
{{ __('This is a secure area of the application. Please confirm your password before continuing.') }}
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ route('password.confirm') }}">
|
||||
@csrf
|
||||
|
||||
<!-- Password -->
|
||||
<div>
|
||||
<x-input-label for="password" :value="__('Password')" />
|
||||
|
||||
<x-text-input id="password" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password"
|
||||
required autocomplete="current-password" />
|
||||
|
||||
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end mt-4">
|
||||
<x-primary-button>
|
||||
{{ __('Confirm') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-guest-layout>
|
||||
@@ -0,0 +1,25 @@
|
||||
<x-guest-layout>
|
||||
<div class="mb-4 text-sm text-gray-600">
|
||||
{{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }}
|
||||
</div>
|
||||
|
||||
<!-- Session Status -->
|
||||
<x-auth-session-status class="mb-4" :status="session('status')" />
|
||||
|
||||
<form method="POST" action="{{ route('password.email') }}">
|
||||
@csrf
|
||||
|
||||
<!-- Email Address -->
|
||||
<div>
|
||||
<x-input-label for="email" :value="__('Email')" />
|
||||
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autofocus />
|
||||
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<x-primary-button>
|
||||
{{ __('Email Password Reset Link') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-guest-layout>
|
||||
@@ -0,0 +1,84 @@
|
||||
<x-guest-layout>
|
||||
<x-auth-session-status class="mb-4 rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm font-medium text-emerald-700" :status="session('status')" />
|
||||
|
||||
<div class="mb-6 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 text-sm text-slate-600">
|
||||
<div class="font-semibold text-slate-800">Akses Administrator</div>
|
||||
<p class="mt-1 leading-6">
|
||||
Masukkan Kredensial yang anda miliki, jika anda tidak punya/lupa silahkan hubungi Administrator.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ route('login') }}" class="space-y-5">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<label for="email" class="mb-2 block text-sm font-semibold text-slate-700">Email</label>
|
||||
<input
|
||||
id="email"
|
||||
class="w-full rounded-2xl border 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"
|
||||
type="email"
|
||||
name="email"
|
||||
value="{{ old('email') }}"
|
||||
required
|
||||
autofocus
|
||||
autocomplete="username"
|
||||
placeholder="Masukkan email admin"
|
||||
/>
|
||||
<x-input-error :messages="$errors->get('email')" class="mt-2 text-sm text-rose-600" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="mb-2 flex items-center justify-between gap-3">
|
||||
<label for="password" class="block text-sm font-semibold text-slate-700">Password</label>
|
||||
@if (Route::has('password.request'))
|
||||
<a class="text-xs font-medium text-indigo-600 transition hover:text-indigo-700" href="{{ route('password.request') }}">
|
||||
Lupa password?
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="relative">
|
||||
<input
|
||||
id="password"
|
||||
class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 pr-12 text-sm text-slate-700 placeholder:text-slate-400 focus:border-indigo-300 focus:outline-none focus:ring-2 focus:ring-indigo-100"
|
||||
type="password"
|
||||
name="password"
|
||||
required
|
||||
autocomplete="current-password"
|
||||
placeholder="Masukkan password admin"
|
||||
data-password-input
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute inset-y-0 right-0 flex items-center px-4 text-slate-400 transition hover:text-slate-600"
|
||||
data-password-toggle
|
||||
aria-label="Tampilkan password"
|
||||
>
|
||||
<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="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7Z" />
|
||||
<circle cx="12" cy="12" r="3" stroke-width="1.8" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<x-input-error :messages="$errors->get('password')" class="mt-2 text-sm text-rose-600" />
|
||||
</div>
|
||||
|
||||
<label for="remember_me" class="flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">
|
||||
<input id="remember_me" type="checkbox" class="rounded border-slate-300 text-indigo-600 shadow-sm focus:ring-indigo-500" name="remember">
|
||||
<span>Ingat saya di perangkat ini</span>
|
||||
</label>
|
||||
|
||||
<div class="rounded-2xl border border-indigo-100 bg-indigo-50 px-4 py-4 text-xs leading-6 text-indigo-700">
|
||||
<span class="font-semibold">Tips:</span> jika ini instalasi baru, gunakan akun admin yang sudah dibuat saat setup awal aplikasi.
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 pt-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="text-xs text-slate-400">
|
||||
Pastikan kredensial yang dipakai memang akun administrator aktif.
|
||||
</div>
|
||||
<button type="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">
|
||||
Masuk ke Dashboard
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</x-guest-layout>
|
||||
@@ -0,0 +1,52 @@
|
||||
<x-guest-layout>
|
||||
<form method="POST" action="{{ route('register') }}">
|
||||
@csrf
|
||||
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<x-input-label for="name" :value="__('Name')" />
|
||||
<x-text-input id="name" class="block mt-1 w-full" type="text" name="name" :value="old('name')" required autofocus autocomplete="name" />
|
||||
<x-input-error :messages="$errors->get('name')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Email Address -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="email" :value="__('Email')" />
|
||||
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autocomplete="username" />
|
||||
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password" :value="__('Password')" />
|
||||
|
||||
<x-text-input id="password" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password"
|
||||
required autocomplete="new-password" />
|
||||
|
||||
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Confirm Password -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
|
||||
|
||||
<x-text-input id="password_confirmation" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password_confirmation" required autocomplete="new-password" />
|
||||
|
||||
<x-input-error :messages="$errors->get('password_confirmation')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" href="{{ route('login') }}">
|
||||
{{ __('Already registered?') }}
|
||||
</a>
|
||||
|
||||
<x-primary-button class="ms-4">
|
||||
{{ __('Register') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-guest-layout>
|
||||
@@ -0,0 +1,39 @@
|
||||
<x-guest-layout>
|
||||
<form method="POST" action="{{ route('password.store') }}">
|
||||
@csrf
|
||||
|
||||
<!-- Password Reset Token -->
|
||||
<input type="hidden" name="token" value="{{ $request->route('token') }}">
|
||||
|
||||
<!-- Email Address -->
|
||||
<div>
|
||||
<x-input-label for="email" :value="__('Email')" />
|
||||
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email', $request->email)" required autofocus autocomplete="username" />
|
||||
<x-input-error :messages="$errors->get('email')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password" :value="__('Password')" />
|
||||
<x-text-input id="password" class="block mt-1 w-full" type="password" name="password" required autocomplete="new-password" />
|
||||
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<!-- Confirm Password -->
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
|
||||
|
||||
<x-text-input id="password_confirmation" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password_confirmation" required autocomplete="new-password" />
|
||||
|
||||
<x-input-error :messages="$errors->get('password_confirmation')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<x-primary-button>
|
||||
{{ __('Reset Password') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
</x-guest-layout>
|
||||
@@ -0,0 +1,31 @@
|
||||
<x-guest-layout>
|
||||
<div class="mb-4 text-sm text-gray-600">
|
||||
{{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }}
|
||||
</div>
|
||||
|
||||
@if (session('status') == 'verification-link-sent')
|
||||
<div class="mb-4 font-medium text-sm text-green-600">
|
||||
{{ __('A new verification link has been sent to the email address you provided during registration.') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="mt-4 flex items-center justify-between">
|
||||
<form method="POST" action="{{ route('verification.send') }}">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<x-primary-button>
|
||||
{{ __('Resend Verification Email') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
|
||||
<button type="submit" class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||
{{ __('Log Out') }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</x-guest-layout>
|
||||
@@ -0,0 +1,23 @@
|
||||
<x-master-page title="Tambah Kelas" description="Tambahkan kelas baru sesuai tahun angkatan dan program keahlian.">
|
||||
<x-master-card title="Form Kelas" subtitle="Lengkapi data kelas sebelum disimpan.">
|
||||
<form action="{{ route('classes.store') }}" method="POST" class="space-y-6">
|
||||
@csrf
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<x-form-select name="academic_year_id" label="Tahun Angkatan" :options="$academicYears->all()" required />
|
||||
<x-form-select name="department_id" label="Program Keahlian" :options="$departments->all()" required />
|
||||
<x-form-input name="name" label="Nama Kelas" placeholder="Contoh: X RPL 1" required />
|
||||
<x-form-select name="level" label="Tingkat" :options="['X' => 'X', 'XI' => 'XI', 'XII' => 'XII']" required />
|
||||
<x-form-input name="homeroom_teacher" label="Wali Kelas" placeholder="Nama wali kelas" />
|
||||
<x-form-input name="capacity" label="Kapasitas" type="number" placeholder="Contoh: 36" />
|
||||
</div>
|
||||
|
||||
<x-form-checkbox name="is_active" label="Kelas aktif" :checked="true" />
|
||||
|
||||
<div class="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">Simpan</button>
|
||||
<a href="{{ route('classes.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>
|
||||
</form>
|
||||
</x-master-card>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,24 @@
|
||||
<x-master-page title="Edit Kelas" description="Perbarui data kelas yang sudah ada.">
|
||||
<x-master-card title="Form Edit Kelas" subtitle="Sesuaikan data kelas lalu simpan perubahan.">
|
||||
<form action="{{ route('classes.update', $class) }}" method="POST" class="space-y-6">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<x-form-select name="academic_year_id" label="Tahun Angkatan" :options="$academicYears->all()" :value="$class->academic_year_id" required />
|
||||
<x-form-select name="department_id" label="Program Keahlian" :options="$departments->all()" :value="$class->department_id" required />
|
||||
<x-form-input name="name" label="Nama Kelas" :value="$class->name" placeholder="Contoh: X RPL 1" required />
|
||||
<x-form-select name="level" label="Tingkat" :options="['X' => 'X', 'XI' => 'XI', 'XII' => 'XII']" :value="$class->level" required />
|
||||
<x-form-input name="homeroom_teacher" label="Wali Kelas" :value="$class->homeroom_teacher" placeholder="Nama wali kelas" />
|
||||
<x-form-input name="capacity" label="Kapasitas" type="number" :value="$class->capacity" placeholder="Contoh: 36" />
|
||||
</div>
|
||||
|
||||
<x-form-checkbox name="is_active" label="Kelas aktif" :checked="$class->is_active" />
|
||||
|
||||
<div class="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">Update</button>
|
||||
<a href="{{ route('classes.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>
|
||||
</form>
|
||||
</x-master-card>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,112 @@
|
||||
<x-master-page
|
||||
title="Kelas"
|
||||
description="Kelola data kelas berdasarkan tahun angkatan dan program keahlian."
|
||||
action-label="Tambah Kelas"
|
||||
:action-url="route('classes.create')"
|
||||
>
|
||||
<x-master-card title="Generate Kelas Tahun Ajaran Baru" subtitle="Salin kelas dari tahun ajaran sumber ke tahun ajaran target. Cocok untuk menyiapkan kelas tahun ajaran baru sebelum promosi siswa.">
|
||||
<form method="POST" action="{{ route('classes.generate') }}" class="grid grid-cols-1 gap-4 xl:grid-cols-4 xl:items-end">
|
||||
@csrf
|
||||
<div>
|
||||
<label for="source_academic_year_id" class="mb-2 block text-sm font-medium text-slate-700">Tahun Ajaran Sumber</label>
|
||||
<select id="source_academic_year_id" name="source_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">
|
||||
<option value="">Pilih tahun sumber</option>
|
||||
@foreach ($academicYears as $academicYear)
|
||||
<option value="{{ $academicYear->id }}">{{ $academicYear->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="target_academic_year_id" class="mb-2 block text-sm font-medium text-slate-700">Tahun Ajaran Target</label>
|
||||
<select id="target_academic_year_id" name="target_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">
|
||||
<option value="">Pilih tahun target</option>
|
||||
@foreach ($academicYears as $academicYear)
|
||||
<option value="{{ $academicYear->id }}">{{ $academicYear->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="generation_mode" class="mb-2 block text-sm font-medium text-slate-700">Mode Generate</label>
|
||||
<select id="generation_mode" name="generation_mode" 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="promote_level">Naikkan level kelas (X→XI, XI→XII)</option>
|
||||
<option value="same_level">Salin level yang sama</option>
|
||||
</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">
|
||||
Generate Kelas
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="mt-4 rounded-2xl border border-sky-200 bg-sky-50 px-4 py-3 text-sm text-sky-700">
|
||||
Tips: untuk persiapan promosi siswa, biasanya pilih mode <span class="font-semibold">Naikkan level kelas</span> agar kelas X pada tahun lama menjadi XI di tahun baru, dan XI menjadi XII.
|
||||
</div>
|
||||
</x-master-card>
|
||||
|
||||
<x-master-card title="Filter Data" subtitle="Cari berdasarkan nama kelas, tingkat, atau wali kelas.">
|
||||
<form method="GET" action="{{ route('classes.index') }}" class="flex flex-col gap-4 md:flex-row md:items-end">
|
||||
<div class="md:flex-1">
|
||||
<x-form-input name="search" label="Pencarian" :value="$search" placeholder="Cari kelas..." />
|
||||
</div>
|
||||
<div class="flex items-end gap-3">
|
||||
<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('classes.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>
|
||||
</form>
|
||||
</x-master-card>
|
||||
|
||||
<x-data-table :headers="['Nama Kelas', 'Tingkat', 'Tahun Angkatan', 'Program Keahlian', 'Dipakai', 'Histori', 'Status', 'Aksi']">
|
||||
@forelse ($classes as $class)
|
||||
<tr>
|
||||
<td class="px-6 py-4">
|
||||
<div class="font-semibold text-slate-800">{{ $class->name }}</div>
|
||||
<div class="text-xs text-slate-400">Wali Kelas: {{ $class->homeroom_teacher ?: '-' }}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-600">{{ $class->level }}</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-600">{{ $class->academicYear?->name }}</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-600">{{ $class->department?->name }}</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="rounded-full bg-indigo-100 px-3 py-1 text-xs font-semibold text-indigo-700">Siswa: {{ $class->students_count }}</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="rounded-full bg-sky-100 px-3 py-1 text-xs font-semibold text-sky-700">Histori: {{ $class->academic_histories_count }}</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
@if ($class->is_active)
|
||||
<span class="rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700">Aktif</span>
|
||||
@else
|
||||
<span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-600">Nonaktif</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<a href="{{ route('classes.edit', $class) }}" 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>
|
||||
@if ($class->students_count === 0 && $class->academic_histories_count === 0)
|
||||
<form action="{{ route('classes.destroy', $class) }}" method="POST" class="delete-form" data-entity="kelas {{ $class->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>
|
||||
@else
|
||||
<button type="button" disabled class="cursor-not-allowed rounded-xl border border-slate-200 bg-slate-100 px-3 py-2 text-xs font-semibold text-slate-400" title="Tidak bisa dihapus karena masih dipakai data siswa atau histori akademik">
|
||||
Dipakai
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="8" class="px-6 py-10 text-center text-sm text-slate-500">Belum ada data kelas.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</x-data-table>
|
||||
|
||||
<div>
|
||||
{{ $classes->links() }}
|
||||
</div>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 316 316" xmlns="http://www.w3.org/2000/svg" {{ $attributes }}>
|
||||
<path d="M305.8 81.125C305.77 80.995 305.69 80.885 305.65 80.755C305.56 80.525 305.49 80.285 305.37 80.075C305.29 79.935 305.17 79.815 305.07 79.685C304.94 79.515 304.83 79.325 304.68 79.175C304.55 79.045 304.39 78.955 304.25 78.845C304.09 78.715 303.95 78.575 303.77 78.475L251.32 48.275C249.97 47.495 248.31 47.495 246.96 48.275L194.51 78.475C194.33 78.575 194.19 78.725 194.03 78.845C193.89 78.955 193.73 79.045 193.6 79.175C193.45 79.325 193.34 79.515 193.21 79.685C193.11 79.815 192.99 79.935 192.91 80.075C192.79 80.285 192.71 80.525 192.63 80.755C192.58 80.875 192.51 80.995 192.48 81.125C192.38 81.495 192.33 81.875 192.33 82.265V139.625L148.62 164.795V52.575C148.62 52.185 148.57 51.805 148.47 51.435C148.44 51.305 148.36 51.195 148.32 51.065C148.23 50.835 148.16 50.595 148.04 50.385C147.96 50.245 147.84 50.125 147.74 49.995C147.61 49.825 147.5 49.635 147.35 49.485C147.22 49.355 147.06 49.265 146.92 49.155C146.76 49.025 146.62 48.885 146.44 48.785L93.99 18.585C92.64 17.805 90.98 17.805 89.63 18.585L37.18 48.785C37 48.885 36.86 49.035 36.7 49.155C36.56 49.265 36.4 49.355 36.27 49.485C36.12 49.635 36.01 49.825 35.88 49.995C35.78 50.125 35.66 50.245 35.58 50.385C35.46 50.595 35.38 50.835 35.3 51.065C35.25 51.185 35.18 51.305 35.15 51.435C35.05 51.805 35 52.185 35 52.575V232.235C35 233.795 35.84 235.245 37.19 236.025L142.1 296.425C142.33 296.555 142.58 296.635 142.82 296.725C142.93 296.765 143.04 296.835 143.16 296.865C143.53 296.965 143.9 297.015 144.28 297.015C144.66 297.015 145.03 296.965 145.4 296.865C145.5 296.835 145.59 296.775 145.69 296.745C145.95 296.655 146.21 296.565 146.45 296.435L251.36 236.035C252.72 235.255 253.55 233.815 253.55 232.245V174.885L303.81 145.945C305.17 145.165 306 143.725 306 142.155V82.265C305.95 81.875 305.89 81.495 305.8 81.125ZM144.2 227.205L100.57 202.515L146.39 176.135L196.66 147.195L240.33 172.335L208.29 190.625L144.2 227.205ZM244.75 114.995V164.795L226.39 154.225L201.03 139.625V89.825L219.39 100.395L244.75 114.995ZM249.12 57.105L292.81 82.265L249.12 107.425L205.43 82.265L249.12 57.105ZM114.49 184.425L96.13 194.995V85.305L121.49 70.705L139.85 60.135V169.815L114.49 184.425ZM91.76 27.425L135.45 52.585L91.76 77.745L48.07 52.585L91.76 27.425ZM43.67 60.135L62.03 70.705L87.39 85.305V202.545V202.555V202.565C87.39 202.735 87.44 202.895 87.46 203.055C87.49 203.265 87.49 203.485 87.55 203.695V203.705C87.6 203.875 87.69 204.035 87.76 204.195C87.84 204.375 87.89 204.575 87.99 204.745C87.99 204.745 87.99 204.755 88 204.755C88.09 204.905 88.22 205.035 88.33 205.175C88.45 205.335 88.55 205.495 88.69 205.635L88.7 205.645C88.82 205.765 88.98 205.855 89.12 205.965C89.28 206.085 89.42 206.225 89.59 206.325C89.6 206.325 89.6 206.325 89.61 206.335C89.62 206.335 89.62 206.345 89.63 206.345L139.87 234.775V285.065L43.67 229.705V60.135ZM244.75 229.705L148.58 285.075V234.775L219.8 194.115L244.75 179.875V229.705ZM297.2 139.625L253.49 164.795V114.995L278.85 100.395L297.21 89.825V139.625H297.2Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
@@ -0,0 +1,7 @@
|
||||
@props(['status'])
|
||||
|
||||
@if ($status)
|
||||
<div {{ $attributes->merge(['class' => 'font-medium text-sm text-green-600']) }}>
|
||||
{{ $status }}
|
||||
</div>
|
||||
@endif
|
||||
@@ -0,0 +1,3 @@
|
||||
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 active:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition ease-in-out duration-150']) }}>
|
||||
{{ $slot }}
|
||||
</button>
|
||||
@@ -0,0 +1,18 @@
|
||||
@props(['headers' => []])
|
||||
|
||||
<div class="overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-sm shadow-slate-200/50">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-slate-200">
|
||||
<thead class="bg-slate-50">
|
||||
<tr>
|
||||
@foreach ($headers as $header)
|
||||
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">{{ $header }}</th>
|
||||
@endforeach
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 bg-white">
|
||||
{{ $slot }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
<a {{ $attributes->merge(['class' => 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out']) }}>{{ $slot }}</a>
|
||||
@@ -0,0 +1,35 @@
|
||||
@props(['align' => 'right', 'width' => '48', 'contentClasses' => 'py-1 bg-white'])
|
||||
|
||||
@php
|
||||
$alignmentClasses = match ($align) {
|
||||
'left' => 'ltr:origin-top-left rtl:origin-top-right start-0',
|
||||
'top' => 'origin-top',
|
||||
default => 'ltr:origin-top-right rtl:origin-top-left end-0',
|
||||
};
|
||||
|
||||
$width = match ($width) {
|
||||
'48' => 'w-48',
|
||||
default => $width,
|
||||
};
|
||||
@endphp
|
||||
|
||||
<div class="relative" x-data="{ open: false }" @click.outside="open = false" @close.stop="open = false">
|
||||
<div @click="open = ! open">
|
||||
{{ $trigger }}
|
||||
</div>
|
||||
|
||||
<div x-show="open"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
class="absolute z-50 mt-2 {{ $width }} rounded-md shadow-lg {{ $alignmentClasses }}"
|
||||
style="display: none;"
|
||||
@click="open = false">
|
||||
<div class="rounded-md ring-1 ring-black ring-opacity-5 {{ $contentClasses }}">
|
||||
{{ $content }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,17 @@
|
||||
@props([
|
||||
'name',
|
||||
'label',
|
||||
'checked' => false,
|
||||
'value' => 1,
|
||||
])
|
||||
|
||||
<label class="inline-flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm font-medium text-slate-700">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="{{ $name }}"
|
||||
value="{{ $value }}"
|
||||
@checked(old($name, $checked))
|
||||
class="h-4 w-4 rounded border-slate-300 text-indigo-600 focus:ring-indigo-500"
|
||||
>
|
||||
<span>{{ $label }}</span>
|
||||
</label>
|
||||
@@ -0,0 +1,21 @@
|
||||
@props([
|
||||
'name',
|
||||
'label',
|
||||
'type' => 'text',
|
||||
'value' => '',
|
||||
'placeholder' => '',
|
||||
'required' => false,
|
||||
])
|
||||
|
||||
<div>
|
||||
<label for="{{ $name }}" class="mb-2 block text-sm font-medium text-slate-700">{{ $label }}</label>
|
||||
<input
|
||||
id="{{ $name }}"
|
||||
name="{{ $name }}"
|
||||
type="{{ $type }}"
|
||||
value="{{ old($name, $value) }}"
|
||||
placeholder="{{ $placeholder }}"
|
||||
@if($required) required @endif
|
||||
{{ $attributes->merge(['class' => 'h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 placeholder:text-slate-400 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100']) }}
|
||||
>
|
||||
</div>
|
||||
@@ -0,0 +1,21 @@
|
||||
@props([
|
||||
'name',
|
||||
'label',
|
||||
'options' => [],
|
||||
'value' => null,
|
||||
'placeholder' => '-- Pilih --',
|
||||
])
|
||||
|
||||
<div>
|
||||
<label for="{{ $name }}" class="mb-2 block text-sm font-medium text-slate-700">{{ $label }}</label>
|
||||
<select
|
||||
id="{{ $name }}"
|
||||
name="{{ $name }}"
|
||||
{{ $attributes->merge(['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="">{{ $placeholder }}</option>
|
||||
@foreach ($options as $optionValue => $optionLabel)
|
||||
<option value="{{ $optionValue }}" @selected((string) old($name, $value) === (string) $optionValue)>{{ $optionLabel }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
@@ -0,0 +1,18 @@
|
||||
@props([
|
||||
'name',
|
||||
'label',
|
||||
'value' => '',
|
||||
'placeholder' => '',
|
||||
'rows' => 4,
|
||||
])
|
||||
|
||||
<div>
|
||||
<label for="{{ $name }}" class="mb-2 block text-sm font-medium text-slate-700">{{ $label }}</label>
|
||||
<textarea
|
||||
id="{{ $name }}"
|
||||
name="{{ $name }}"
|
||||
rows="{{ $rows }}"
|
||||
placeholder="{{ $placeholder }}"
|
||||
{{ $attributes->merge(['class' => 'w-full rounded-2xl border 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-4 focus:ring-indigo-100']) }}
|
||||
>{{ old($name, $value) }}</textarea>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
@props(['messages'])
|
||||
|
||||
@if ($messages)
|
||||
<ul {{ $attributes->merge(['class' => 'text-sm text-red-600 space-y-1']) }}>
|
||||
@foreach ((array) $messages as $message)
|
||||
<li>{{ $message }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
@@ -0,0 +1,5 @@
|
||||
@props(['value'])
|
||||
|
||||
<label {{ $attributes->merge(['class' => 'block font-medium text-sm text-gray-700']) }}>
|
||||
{{ $value ?? $slot }}
|
||||
</label>
|
||||
@@ -0,0 +1,18 @@
|
||||
@props(['title' => null, 'subtitle' => null])
|
||||
|
||||
<div class="overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-sm shadow-slate-200/50">
|
||||
@if ($title || $subtitle)
|
||||
<div class="border-b border-slate-200 px-6 py-5">
|
||||
@if ($title)
|
||||
<h3 class="text-base font-semibold text-slate-900">{{ $title }}</h3>
|
||||
@endif
|
||||
@if ($subtitle)
|
||||
<p class="mt-1 text-sm text-slate-500">{{ $subtitle }}</p>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="p-6">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,50 @@
|
||||
@props([
|
||||
'title',
|
||||
'description' => null,
|
||||
'actionLabel' => null,
|
||||
'actionUrl' => null,
|
||||
])
|
||||
|
||||
<x-app-layout>
|
||||
@php
|
||||
$viewErrors = $errors ?? new \Illuminate\Support\ViewErrorBag();
|
||||
@endphp
|
||||
|
||||
<x-slot name="header">
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold leading-tight text-slate-900">{{ $title }}</h2>
|
||||
@if ($description)
|
||||
<p class="mt-1 text-sm text-slate-500">{{ $description }}</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if ($actionLabel && $actionUrl)
|
||||
<a href="{{ $actionUrl }}" 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">
|
||||
{{ $actionLabel }}
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div class="space-y-6">
|
||||
@if (session('success'))
|
||||
<div class="rounded-2xl border border-emerald-200 bg-emerald-50 px-5 py-4 text-sm font-medium text-emerald-700 shadow-sm">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($viewErrors->any())
|
||||
<div class="rounded-2xl border border-rose-200 bg-rose-50 px-5 py-4 shadow-sm">
|
||||
<div class="text-sm font-semibold text-rose-700">Ada data yang perlu diperbaiki:</div>
|
||||
<ul class="mt-2 list-disc space-y-1 pl-5 text-sm text-rose-600">
|
||||
@foreach ($viewErrors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</x-app-layout>
|
||||
@@ -0,0 +1,78 @@
|
||||
@props([
|
||||
'name',
|
||||
'show' => false,
|
||||
'maxWidth' => '2xl'
|
||||
])
|
||||
|
||||
@php
|
||||
$maxWidth = [
|
||||
'sm' => 'sm:max-w-sm',
|
||||
'md' => 'sm:max-w-md',
|
||||
'lg' => 'sm:max-w-lg',
|
||||
'xl' => 'sm:max-w-xl',
|
||||
'2xl' => 'sm:max-w-2xl',
|
||||
][$maxWidth];
|
||||
@endphp
|
||||
|
||||
<div
|
||||
x-data="{
|
||||
show: @js($show),
|
||||
focusables() {
|
||||
// All focusable element types...
|
||||
let selector = 'a, button, input:not([type=\'hidden\']), textarea, select, details, [tabindex]:not([tabindex=\'-1\'])'
|
||||
return [...$el.querySelectorAll(selector)]
|
||||
// All non-disabled elements...
|
||||
.filter(el => ! el.hasAttribute('disabled'))
|
||||
},
|
||||
firstFocusable() { return this.focusables()[0] },
|
||||
lastFocusable() { return this.focusables().slice(-1)[0] },
|
||||
nextFocusable() { return this.focusables()[this.nextFocusableIndex()] || this.firstFocusable() },
|
||||
prevFocusable() { return this.focusables()[this.prevFocusableIndex()] || this.lastFocusable() },
|
||||
nextFocusableIndex() { return (this.focusables().indexOf(document.activeElement) + 1) % (this.focusables().length + 1) },
|
||||
prevFocusableIndex() { return Math.max(0, this.focusables().indexOf(document.activeElement)) -1 },
|
||||
}"
|
||||
x-init="$watch('show', value => {
|
||||
if (value) {
|
||||
document.body.classList.add('overflow-y-hidden');
|
||||
{{ $attributes->has('focusable') ? 'setTimeout(() => firstFocusable().focus(), 100)' : '' }}
|
||||
} else {
|
||||
document.body.classList.remove('overflow-y-hidden');
|
||||
}
|
||||
})"
|
||||
x-on:open-modal.window="$event.detail == '{{ $name }}' ? show = true : null"
|
||||
x-on:close-modal.window="$event.detail == '{{ $name }}' ? show = false : null"
|
||||
x-on:close.stop="show = false"
|
||||
x-on:keydown.escape.window="show = false"
|
||||
x-on:keydown.tab.prevent="$event.shiftKey || nextFocusable().focus()"
|
||||
x-on:keydown.shift.tab.prevent="prevFocusable().focus()"
|
||||
x-show="show"
|
||||
class="fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 z-50"
|
||||
style="display: {{ $show ? 'block' : 'none' }};"
|
||||
>
|
||||
<div
|
||||
x-show="show"
|
||||
class="fixed inset-0 transform transition-all"
|
||||
x-on:click="show = false"
|
||||
x-transition:enter="ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
>
|
||||
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
x-show="show"
|
||||
class="mb-6 bg-white rounded-lg overflow-hidden shadow-xl transform transition-all sm:w-full {{ $maxWidth }} sm:mx-auto"
|
||||
x-transition:enter="ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave="ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,11 @@
|
||||
@props(['active'])
|
||||
|
||||
@php
|
||||
$classes = ($active ?? false)
|
||||
? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out'
|
||||
: 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out';
|
||||
@endphp
|
||||
|
||||
<a {{ $attributes->merge(['class' => $classes]) }}>
|
||||
{{ $slot }}
|
||||
</a>
|
||||
@@ -0,0 +1,3 @@
|
||||
<button {{ $attributes->merge(['type' => 'submit', 'class' => 'inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150']) }}>
|
||||
{{ $slot }}
|
||||
</button>
|
||||
@@ -0,0 +1,11 @@
|
||||
@props(['active'])
|
||||
|
||||
@php
|
||||
$classes = ($active ?? false)
|
||||
? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 text-start text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition duration-150 ease-in-out'
|
||||
: 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out';
|
||||
@endphp
|
||||
|
||||
<a {{ $attributes->merge(['class' => $classes]) }}>
|
||||
{{ $slot }}
|
||||
</a>
|
||||
@@ -0,0 +1,3 @@
|
||||
<button {{ $attributes->merge(['type' => 'button', 'class' => 'inline-flex items-center px-4 py-2 bg-white border border-gray-300 rounded-md font-semibold text-xs text-gray-700 uppercase tracking-widest shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-25 transition ease-in-out duration-150']) }}>
|
||||
{{ $slot }}
|
||||
</button>
|
||||
@@ -0,0 +1,3 @@
|
||||
@props(['disabled' => false])
|
||||
|
||||
<input @disabled($disabled) {{ $attributes->merge(['class' => 'border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm']) }}>
|
||||
@@ -0,0 +1,270 @@
|
||||
<x-app-layout>
|
||||
@php
|
||||
$isAdmin = auth()->user()?->isAdmin() ?? false;
|
||||
@endphp
|
||||
|
||||
<x-slot name="header">
|
||||
<div class="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold leading-tight text-slate-900">
|
||||
Dashboard Admin
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">
|
||||
Ringkasan operasional Database Kesiswaan, mulai dari master data, siswa, hingga status kelengkapan dokumen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<a href="{{ route('students.create') }}" 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">
|
||||
Tambah Siswa
|
||||
</a>
|
||||
<a href="{{ route('student-promotions.index') }}" class="inline-flex items-center rounded-2xl border border-slate-200 bg-white px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">
|
||||
Buka Promosi Siswa
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="grid grid-cols-1 gap-6 xl:grid-cols-3">
|
||||
<div class="overflow-hidden rounded-[2rem] border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-indigo-50 p-6 text-slate-900 shadow-2xl shadow-slate-200/60 xl:col-span-2">
|
||||
<div class="flex flex-col gap-8 xl:flex-row xl:items-start xl:justify-between">
|
||||
<div class="max-w-2xl">
|
||||
<div class="inline-flex items-center gap-2 rounded-full border border-indigo-200 bg-indigo-50 px-3 py-1 text-xs font-semibold uppercase tracking-[0.16em] text-indigo-700">
|
||||
<span class="inline-flex h-2 w-2 rounded-full bg-emerald-300"></span>
|
||||
Ringkasan Utama
|
||||
</div>
|
||||
<h3 class="mt-4 text-3xl font-bold leading-tight text-slate-900">
|
||||
Ringkasan utama statistik siswa dalam satu panel.
|
||||
</h3>
|
||||
<p class="mt-3 max-w-xl text-sm leading-6 text-slate-600">
|
||||
Lihat total siswa, siswa keluar, siswa mutasi, dan siswa lulus secara cepat dari dashboard utama.
|
||||
</p>
|
||||
|
||||
<div class="mt-6 flex flex-wrap gap-3 text-xs text-slate-600">
|
||||
<div class="rounded-full border border-slate-200 bg-white px-3 py-2 shadow-sm">Ringkasan status siswa</div>
|
||||
<div class="rounded-full border border-slate-200 bg-white px-3 py-2 shadow-sm">Arsip keluar & mutasi</div>
|
||||
<div class="rounded-full border border-slate-200 bg-white px-3 py-2 shadow-sm">Monitoring lulusan</div>
|
||||
</div>
|
||||
</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="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>
|
||||
</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="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>
|
||||
</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="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>
|
||||
</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="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>
|
||||
</div>
|
||||
<p class="mt-4 text-xs leading-5 text-slate-500">Jumlah siswa yang sudah berstatus lulus.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<span class="text-sm font-semibold text-slate-900">{{ $stats['documentTypes'] }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</x-master-card>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-5 md:grid-cols-2 xl:grid-cols-4">
|
||||
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-200/50">
|
||||
<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>
|
||||
</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="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>
|
||||
</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="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>
|
||||
</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="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>
|
||||
</div>
|
||||
<p class="mt-4 text-xs text-slate-400">Siswa yang dokumen wajibnya belum valid semua.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
@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>
|
||||
<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>
|
||||
@endif
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
</x-master-card>
|
||||
|
||||
<x-master-card title="Catatan Operasional" subtitle="Fokus kerja paling masuk akal dari kondisi aplikasi saat ini.">
|
||||
<div class="space-y-4 text-sm text-slate-600">
|
||||
<div class="rounded-2xl bg-slate-50 px-4 py-4">
|
||||
<div class="font-semibold text-slate-800">Prioritas data</div>
|
||||
<p class="mt-1">Pastikan master kelas dan tahun ajaran selalu siap sebelum proses promosi siswa dilakukan.</p>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-slate-50 px-4 py-4">
|
||||
<div class="font-semibold text-slate-800">Prioritas validasi</div>
|
||||
<p class="mt-1">Gunakan indikator dokumen lengkap dan tervalidasi penuh untuk memantau kualitas data siswa.</p>
|
||||
</div>
|
||||
<div class="rounded-2xl bg-slate-50 px-4 py-4">
|
||||
<div class="font-semibold text-slate-800">Prioritas berikutnya</div>
|
||||
<p class="mt-1">Step paling sehat setelah ini adalah role & permission atau reporting/dashboard lanjutan.</p>
|
||||
</div>
|
||||
</div>
|
||||
</x-master-card>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
@@ -0,0 +1,21 @@
|
||||
<x-master-page title="Tambah Program Keahlian" description="Tambahkan jurusan atau program keahlian baru.">
|
||||
<x-master-card title="Form Program Keahlian" subtitle="Isi kode, nama, dan informasi tambahan program keahlian.">
|
||||
<form action="{{ route('departments.store') }}" method="POST" class="space-y-6">
|
||||
@csrf
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<x-form-input name="code" label="Kode" placeholder="Contoh: RPL" required />
|
||||
<x-form-input name="name" label="Nama Program Keahlian" placeholder="Contoh: Rekayasa Perangkat Lunak" required />
|
||||
</div>
|
||||
|
||||
<x-form-textarea name="description" label="Deskripsi" placeholder="Deskripsi singkat program keahlian (opsional)" />
|
||||
|
||||
<x-form-checkbox name="is_active" label="Program keahlian aktif" :checked="true" />
|
||||
|
||||
<div class="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">Simpan</button>
|
||||
<a href="{{ route('departments.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>
|
||||
</form>
|
||||
</x-master-card>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,22 @@
|
||||
<x-master-page title="Edit Program Keahlian" description="Perbarui data program keahlian yang sudah ada.">
|
||||
<x-master-card title="Form Edit Program Keahlian" subtitle="Sesuaikan data lalu simpan perubahan.">
|
||||
<form action="{{ route('departments.update', $department) }}" method="POST" class="space-y-6">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<x-form-input name="code" label="Kode" :value="$department->code" placeholder="Contoh: RPL" required />
|
||||
<x-form-input name="name" label="Nama Program Keahlian" :value="$department->name" placeholder="Contoh: Rekayasa Perangkat Lunak" required />
|
||||
</div>
|
||||
|
||||
<x-form-textarea name="description" label="Deskripsi" :value="$department->description" placeholder="Deskripsi singkat program keahlian (opsional)" />
|
||||
|
||||
<x-form-checkbox name="is_active" label="Program keahlian aktif" :checked="$department->is_active" />
|
||||
|
||||
<div class="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">Update</button>
|
||||
<a href="{{ route('departments.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>
|
||||
</form>
|
||||
</x-master-card>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,68 @@
|
||||
<x-master-page
|
||||
title="Program Keahlian"
|
||||
description="Kelola master jurusan atau program keahlian sekolah."
|
||||
action-label="Tambah Program Keahlian"
|
||||
:action-url="route('departments.create')"
|
||||
>
|
||||
<x-master-card title="Filter Data" subtitle="Cari berdasarkan kode, nama, atau deskripsi program keahlian.">
|
||||
<form method="GET" action="{{ route('departments.index') }}" class="flex flex-col gap-4 md:flex-row md:items-end">
|
||||
<div class="md:flex-1">
|
||||
<x-form-input name="search" label="Pencarian" :value="$search" placeholder="Cari program keahlian..." />
|
||||
</div>
|
||||
<div class="flex items-end gap-3">
|
||||
<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('departments.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>
|
||||
</form>
|
||||
</x-master-card>
|
||||
|
||||
<x-data-table :headers="['Kode', 'Nama', 'Dipakai', 'Deskripsi', 'Status', 'Aksi']">
|
||||
@forelse ($departments as $department)
|
||||
<tr>
|
||||
<td class="px-6 py-4 text-sm font-semibold text-slate-800">{{ $department->code }}</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-700">{{ $department->name }}</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="rounded-full bg-sky-100 px-3 py-1 text-xs font-semibold text-sky-700">Kelas: {{ $department->classes_count }}</span>
|
||||
<span class="rounded-full bg-indigo-100 px-3 py-1 text-xs font-semibold text-indigo-700">Siswa: {{ $department->students_count }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-500">{{ $department->description ?: '-' }}</td>
|
||||
<td class="px-6 py-4">
|
||||
@if ($department->is_active)
|
||||
<span class="rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700">Aktif</span>
|
||||
@else
|
||||
<span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-600">Nonaktif</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<a href="{{ route('departments.edit', $department) }}" 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>
|
||||
@php
|
||||
$canDelete = $department->classes_count === 0 && $department->students_count === 0;
|
||||
@endphp
|
||||
@if ($canDelete)
|
||||
<form action="{{ route('departments.destroy', $department) }}" method="POST" class="delete-form" data-entity="program keahlian {{ $department->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>
|
||||
@else
|
||||
<button type="button" disabled class="cursor-not-allowed rounded-xl border border-slate-200 bg-slate-100 px-3 py-2 text-xs font-semibold text-slate-400" title="Tidak bisa dihapus karena masih dipakai data lain">
|
||||
Dipakai
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="px-6 py-10 text-center text-sm text-slate-500">Belum ada data program keahlian.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</x-data-table>
|
||||
|
||||
<div>
|
||||
{{ $departments->links() }}
|
||||
</div>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,27 @@
|
||||
<x-master-page title="Tambah Jenis Dokumen" description="Tambahkan kategori dokumen baru untuk kebutuhan data siswa.">
|
||||
<x-master-card title="Form Jenis Dokumen" subtitle="Lengkapi kode, nama, dan aturan dokumen.">
|
||||
<form action="{{ route('document-types.store') }}" method="POST" class="space-y-6">
|
||||
@csrf
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<x-form-input name="code" label="Kode Dokumen" placeholder="Contoh: KK" required />
|
||||
<x-form-input name="name" label="Nama Dokumen" placeholder="Contoh: Kartu Keluarga" required />
|
||||
<x-form-input name="max_file_size_kb" label="Maksimal Ukuran File (KB)" type="number" placeholder="Contoh: 2048" />
|
||||
<x-form-input name="allowed_extensions" label="Format File Diizinkan" placeholder="Contoh: jpg,jpeg,png,pdf" />
|
||||
</div>
|
||||
|
||||
<x-form-textarea name="description" label="Deskripsi" placeholder="Deskripsi singkat dokumen (opsional)" />
|
||||
<p class="text-xs text-slate-400">Pisahkan format dengan koma. Contoh: <span class="font-semibold">jpg,jpeg,png,pdf</span></p>
|
||||
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<x-form-checkbox name="is_required" label="Dokumen wajib" :checked="false" />
|
||||
<x-form-checkbox name="is_active" label="Jenis dokumen aktif" :checked="true" />
|
||||
</div>
|
||||
|
||||
<div class="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">Simpan</button>
|
||||
<a href="{{ route('document-types.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>
|
||||
</form>
|
||||
</x-master-card>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,28 @@
|
||||
<x-master-page title="Edit Jenis Dokumen" description="Perbarui data jenis dokumen yang sudah ada.">
|
||||
<x-master-card title="Form Edit Jenis Dokumen" subtitle="Sesuaikan data lalu simpan perubahan.">
|
||||
<form action="{{ route('document-types.update', $documentType) }}" method="POST" class="space-y-6">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<x-form-input name="code" label="Kode Dokumen" :value="$documentType->code" placeholder="Contoh: KK" required />
|
||||
<x-form-input name="name" label="Nama Dokumen" :value="$documentType->name" placeholder="Contoh: Kartu Keluarga" required />
|
||||
<x-form-input name="max_file_size_kb" label="Maksimal Ukuran File (KB)" type="number" :value="$documentType->max_file_size_kb" placeholder="Contoh: 2048" />
|
||||
<x-form-input name="allowed_extensions" label="Format File Diizinkan" :value="$documentType->allowed_extensions" placeholder="Contoh: jpg,jpeg,png,pdf" />
|
||||
</div>
|
||||
|
||||
<x-form-textarea name="description" label="Deskripsi" :value="$documentType->description" placeholder="Deskripsi singkat dokumen (opsional)" />
|
||||
<p class="text-xs text-slate-400">Pisahkan format dengan koma. Contoh: <span class="font-semibold">jpg,jpeg,png,pdf</span></p>
|
||||
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<x-form-checkbox name="is_required" label="Dokumen wajib" :checked="$documentType->is_required" />
|
||||
<x-form-checkbox name="is_active" label="Jenis dokumen aktif" :checked="$documentType->is_active" />
|
||||
</div>
|
||||
|
||||
<div class="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">Update</button>
|
||||
<a href="{{ route('document-types.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>
|
||||
</form>
|
||||
</x-master-card>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,77 @@
|
||||
<x-master-page
|
||||
title="Jenis Dokumen"
|
||||
description="Kelola jenis dokumen yang dibutuhkan untuk data siswa."
|
||||
action-label="Tambah Jenis Dokumen"
|
||||
:action-url="route('document-types.create')"
|
||||
>
|
||||
<x-master-card title="Filter Data" subtitle="Cari berdasarkan kode, nama, atau deskripsi dokumen.">
|
||||
<form method="GET" action="{{ route('document-types.index') }}" class="flex flex-col gap-4 md:flex-row md:items-end">
|
||||
<div class="md:flex-1">
|
||||
<x-form-input name="search" label="Pencarian" :value="$search" placeholder="Cari jenis dokumen..." />
|
||||
</div>
|
||||
<div class="flex items-end gap-3">
|
||||
<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('document-types.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>
|
||||
</form>
|
||||
</x-master-card>
|
||||
|
||||
<x-data-table :headers="['Kode', 'Nama', 'Format', 'Maks. File', 'Dipakai', 'Wajib', 'Status', 'Aksi']">
|
||||
@forelse ($documentTypes as $documentType)
|
||||
<tr>
|
||||
<td class="px-6 py-4 text-sm font-semibold text-slate-800">{{ $documentType->code }}</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="font-semibold text-slate-800">{{ $documentType->name }}</div>
|
||||
<div class="text-xs text-slate-400">{{ $documentType->description ?: 'Tanpa deskripsi' }}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-600">
|
||||
{{ $documentType->allowed_extensions ?: 'jpg,jpeg,png,pdf,webp' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-600">
|
||||
{{ $documentType->max_file_size_kb ? number_format($documentType->max_file_size_kb / 1024, 2) . ' MB' : 'Default 5 MB' }}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="rounded-full bg-indigo-100 px-3 py-1 text-xs font-semibold text-indigo-700">Dokumen: {{ $documentType->student_documents_count }}</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
@if ($documentType->is_required)
|
||||
<span class="rounded-full bg-amber-100 px-3 py-1 text-xs font-semibold text-amber-700">Wajib</span>
|
||||
@else
|
||||
<span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-600">Opsional</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
@if ($documentType->is_active)
|
||||
<span class="rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700">Aktif</span>
|
||||
@else
|
||||
<span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-600">Nonaktif</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<a href="{{ route('document-types.edit', $documentType) }}" 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>
|
||||
@if ($documentType->student_documents_count === 0)
|
||||
<form action="{{ route('document-types.destroy', $documentType) }}" method="POST" class="delete-form" data-entity="jenis dokumen {{ $documentType->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>
|
||||
@else
|
||||
<button type="button" disabled class="cursor-not-allowed rounded-xl border border-slate-200 bg-slate-100 px-3 py-2 text-xs font-semibold text-slate-400" title="Tidak bisa dihapus karena masih dipakai data dokumen siswa">
|
||||
Dipakai
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="8" class="px-6 py-10 text-center text-sm text-slate-500">Belum ada data jenis dokumen.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</x-data-table>
|
||||
|
||||
<div>
|
||||
{{ $documentTypes->links() }}
|
||||
</div>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,32 @@
|
||||
@php
|
||||
try {
|
||||
$branding = \App\Models\AppSetting::branding();
|
||||
} catch (\Throwable $e) {
|
||||
$branding = \App\Models\AppSetting::brandingDefaults();
|
||||
}
|
||||
@endphp
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<title>{{ $branding['app_name'] ?? config('app.name', 'Database Kesiswaan') }}</title>
|
||||
|
||||
@if (!empty($branding['app_favicon']))
|
||||
<link rel="icon" type="image/png" href="{{ asset('storage/' . $branding['app_favicon']) }}">
|
||||
@endif
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
|
||||
|
||||
<!-- Scripts -->
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="bg-slate-50 font-sans antialiased text-slate-900">
|
||||
@include('sweetalert::alert')
|
||||
@include('layouts.navigation')
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,86 @@
|
||||
@php
|
||||
try {
|
||||
$branding = \App\Models\AppSetting::branding();
|
||||
} catch (\Throwable $e) {
|
||||
$branding = \App\Models\AppSetting::brandingDefaults();
|
||||
}
|
||||
@endphp
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
|
||||
<title>{{ $branding['app_name'] ?? config('app.name', 'Database Kesiswaan') }}</title>
|
||||
|
||||
@if (!empty($branding['app_favicon']))
|
||||
<link rel="icon" type="image/png" href="{{ asset('storage/' . $branding['app_favicon']) }}">
|
||||
@endif
|
||||
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600,700&display=swap" rel="stylesheet" />
|
||||
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="min-h-screen bg-slate-100 font-sans antialiased text-slate-900">
|
||||
<div class="relative min-h-screen overflow-hidden bg-[radial-gradient(circle_at_top_left,_rgba(99,102,241,0.18),_transparent_35%),radial-gradient(circle_at_bottom_right,_rgba(14,165,233,0.15),_transparent_30%)]">
|
||||
<div class="absolute inset-0 bg-[linear-gradient(to_bottom,_rgba(255,255,255,0.55),_rgba(248,250,252,0.9))]"></div>
|
||||
|
||||
<div class="relative mx-auto flex min-h-screen max-w-7xl items-center px-4 py-10 sm:px-6 lg:px-8">
|
||||
<div class="grid w-full grid-cols-1 gap-8 lg:grid-cols-[1.1fr_520px] lg:items-center">
|
||||
<div class="hidden lg:block">
|
||||
<div class="max-w-xl">
|
||||
<div class="inline-flex items-center gap-3 rounded-full border border-indigo-100 bg-white/80 px-4 py-2 shadow-sm shadow-slate-200/60 backdrop-blur">
|
||||
<div class="flex h-9 w-9 items-center justify-center overflow-hidden rounded-2xl bg-indigo-600 text-sm font-bold text-white shadow-lg shadow-indigo-200">
|
||||
@if (!empty($branding['app_logo']))
|
||||
<img src="{{ asset('storage/' . $branding['app_logo']) }}" alt="{{ $branding['app_name'] }}" class="h-full w-full object-contain bg-white p-1">
|
||||
@else
|
||||
{{ $branding['app_short_name'] ?? 'DK' }}
|
||||
@endif
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-semibold text-slate-900">{{ $branding['app_name'] ?? 'Database Kesiswaan' }}</div>
|
||||
<div class="text-xs text-slate-500">Sistem Administrasi Sekolah</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 class="mt-8 text-4xl font-bold leading-tight text-slate-900">
|
||||
Kelola data siswa, dokumen sekolah, dan proses kenaikan kelas dalam satu panel administrasi yang tertata.
|
||||
</h1>
|
||||
<p class="mt-4 text-base leading-7 text-slate-600">
|
||||
Masuk ke sistem untuk mengelola master data, verifikasi dokumen siswa, histori akademik, dan proses kenaikan kelas dengan alur kerja yang lebih nyaman.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="overflow-hidden rounded-[32px] border border-white/80 bg-white/95 shadow-[0_30px_80px_-35px_rgba(15,23,42,0.35)] backdrop-blur">
|
||||
<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="flex items-center gap-4">
|
||||
<div class="flex h-14 w-14 items-center justify-center overflow-hidden rounded-3xl bg-indigo-600 text-lg font-bold text-white shadow-lg shadow-indigo-200">
|
||||
@if (!empty($branding['app_logo']))
|
||||
<img src="{{ asset('storage/' . $branding['app_logo']) }}" alt="{{ $branding['app_name'] }}" class="h-full w-full object-contain bg-white p-2">
|
||||
@else
|
||||
{{ $branding['app_short_name'] ?? 'DK' }}
|
||||
@endif
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-semibold uppercase tracking-[0.18em] text-indigo-500">Login Admin</div>
|
||||
<h1 class="mt-1 text-2xl font-bold text-slate-900">Masuk ke {{ $branding['app_name'] ?? 'Database Kesiswaan' }}</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">Gunakan akun administrator untuk mengakses panel.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-6 py-6 sm:px-8 sm:py-8">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,48 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold leading-tight text-slate-900">{{ $title }}</h2>
|
||||
@isset($description)
|
||||
<p class="mt-1 text-sm text-slate-500">{{ $description }}</p>
|
||||
@endisset
|
||||
</div>
|
||||
|
||||
@isset($actionLabel)
|
||||
<button type="button" class="inline-flex items-center rounded-xl bg-slate-900 px-4 py-2.5 text-sm font-medium text-white transition hover:bg-slate-800">
|
||||
{{ $actionLabel }}
|
||||
</button>
|
||||
@endisset
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div>
|
||||
<div class="overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-sm shadow-slate-200/50">
|
||||
<div class="border-b border-slate-200 px-6 py-5">
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<div class="text-sm font-semibold text-slate-800">Halaman sedang disiapkan</div>
|
||||
<p class="mt-1 text-sm text-slate-500">
|
||||
Struktur route, layout, dan navigasi sudah aktif. Berikutnya tinggal implementasi CRUD penuh untuk modul ini.
|
||||
</p>
|
||||
</div>
|
||||
<span class="inline-flex rounded-full bg-indigo-50 px-4 py-2 text-xs font-semibold text-indigo-700">TailAdmin Inspired</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<div class="rounded-3xl border border-dashed border-slate-300 bg-slate-50 p-10 text-center">
|
||||
<div class="mx-auto flex h-16 w-16 items-center justify-center rounded-2xl bg-indigo-100 text-indigo-700">
|
||||
<svg class="h-7 w-7" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.8" d="M12 5v14M5 12h14" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="mt-5 text-lg font-semibold text-slate-800">{{ $title }}</div>
|
||||
<p class="mx-auto mt-2 max-w-2xl text-sm leading-6 text-slate-500">
|
||||
Placeholder aktif. Bro bisa lanjut langsung membuat tabel data, form tambah/edit, validasi, pencarian, pagination, dan aksi simpan/hapus dengan gaya UI yang konsisten.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
@@ -0,0 +1,342 @@
|
||||
@php
|
||||
$user = auth()->user();
|
||||
$isAdmin = $user && method_exists($user, 'isAdmin') ? $user->isAdmin() : false;
|
||||
try {
|
||||
$branding = \App\Models\AppSetting::branding();
|
||||
} catch (\Throwable $e) {
|
||||
$branding = \App\Models\AppSetting::brandingDefaults();
|
||||
}
|
||||
|
||||
$masterDataOpen = request()->routeIs('academic-years.*')
|
||||
|| request()->routeIs('departments.*')
|
||||
|| request()->routeIs('classes.*')
|
||||
|| request()->routeIs('document-types.*');
|
||||
|
||||
$kesiswaanOpen = request()->routeIs('students.*')
|
||||
|| request()->routeIs('student-exits.*')
|
||||
|| request()->routeIs('student-transfers.*')
|
||||
|| request()->routeIs('student-promotions.*')
|
||||
|| request()->routeIs('student-self-service.*');
|
||||
|
||||
$settingsOpen = request()->routeIs('settings.*');
|
||||
@endphp
|
||||
|
||||
<div x-data="{ sidebarOpen: false, masterDataOpen: @js($masterDataOpen), kesiswaanOpen: @js($kesiswaanOpen), settingsOpen: @js($settingsOpen), profileMenuOpen: false }" class="min-h-screen bg-slate-50">
|
||||
<div
|
||||
x-show="sidebarOpen"
|
||||
x-transition.opacity
|
||||
class="fixed inset-0 z-40 bg-slate-900/50 lg:hidden"
|
||||
@click="sidebarOpen = false"
|
||||
></div>
|
||||
|
||||
<aside
|
||||
:class="sidebarOpen ? 'translate-x-0' : '-translate-x-full'"
|
||||
class="fixed left-0 top-0 z-50 flex h-screen w-72 flex-col border-r border-slate-200 bg-white transition-transform duration-300 lg:translate-x-0"
|
||||
>
|
||||
<div class="flex h-20 items-center justify-between border-b border-slate-200 px-6">
|
||||
<a href="{{ route('dashboard') }}" class="flex items-center gap-3">
|
||||
<div class="flex h-12 w-12 items-center justify-center overflow-hidden rounded-2xl bg-indigo-600 text-base font-bold text-white shadow-lg shadow-indigo-200">
|
||||
@if (!empty($branding['app_logo']))
|
||||
<img src="{{ asset('storage/' . $branding['app_logo']) }}" alt="{{ $branding['app_name'] }}" class="h-full w-full object-contain bg-white p-2">
|
||||
@else
|
||||
{{ $branding['app_short_name'] ?? 'DK' }}
|
||||
@endif
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-semibold text-slate-900">{{ $branding['app_name'] ?? 'Database Kesiswaan' }}</div>
|
||||
<div class="text-xs text-slate-500">Admin Dashboard</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<button @click="sidebarOpen = false" class="rounded-lg p-2 text-slate-500 hover:bg-slate-100 lg:hidden">
|
||||
<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 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto px-4 py-6">
|
||||
<div class="mb-6 px-3">
|
||||
<p class="text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400">Menu Utama</p>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</span>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
|
||||
@if ($isAdmin)
|
||||
<div class="space-y-2">
|
||||
<button
|
||||
type="button"
|
||||
@click="masterDataOpen = !masterDataOpen"
|
||||
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>
|
||||
<span>Master Data</span>
|
||||
</div>
|
||||
<svg class="h-4 w-4 transition-transform" :class="masterDataOpen ? 'rotate-180' : ''" 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 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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<span>Jenis Dokumen</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="space-y-2">
|
||||
<button
|
||||
type="button"
|
||||
@click="kesiswaanOpen = !kesiswaanOpen"
|
||||
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>
|
||||
<span>Kesiswaan</span>
|
||||
</div>
|
||||
<svg class="h-4 w-4 transition-transform" :class="kesiswaanOpen ? 'rotate-180' : ''" 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 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>
|
||||
<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>
|
||||
<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>
|
||||
<span>Data Siswa Mutasi</span>
|
||||
</a>
|
||||
<a href="{{ route('students.graduated') }}"
|
||||
class="flex items-center gap-3 rounded-xl px-4 py-3 text-sm font-medium transition {{ request()->routeIs('students.graduated') ? '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>
|
||||
<span>Data Siswa Lulus</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>
|
||||
<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>
|
||||
<span>Self Servis Siswa</span>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($isAdmin)
|
||||
<div class="space-y-2">
|
||||
<button
|
||||
type="button"
|
||||
@click="settingsOpen = !settingsOpen"
|
||||
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>
|
||||
<span>Pengaturan</span>
|
||||
</div>
|
||||
<svg class="h-4 w-4 transition-transform" :class="settingsOpen ? 'rotate-180' : ''" 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 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>
|
||||
<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>
|
||||
<span>Branding Aplikasi</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>
|
||||
<span>Kosongkan Data</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="mt-auto border-t border-slate-200 px-4 py-3">
|
||||
<div class="text-center">
|
||||
<span class="text-[10px] font-semibold uppercase tracking-[0.22em] text-slate-400">Developed by</span>
|
||||
<a href="https://noctkj.net" target="_blank" rel="noopener noreferrer" class="mt-1 block text-sm font-semibold tracking-[0.04em] text-slate-700 transition hover:text-indigo-600">
|
||||
noctkj.net
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="lg:pl-72">
|
||||
<header class="sticky top-0 z-30 border-b border-slate-200 bg-white/80 backdrop-blur-xl">
|
||||
<div class="flex h-20 items-center justify-between px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center gap-3">
|
||||
<button @click="sidebarOpen = true" class="inline-flex h-11 w-11 items-center justify-center rounded-xl border border-slate-200 bg-white text-slate-500 shadow-sm lg:hidden">
|
||||
<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 6h16M4 12h16M4 18h16" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.2em] text-slate-400">Panel Admin</p>
|
||||
<h1 class="text-lg font-semibold text-slate-900">{{ $branding['app_name'] ?? 'Database Kesiswaan' }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="relative" @click.away="profileMenuOpen = false">
|
||||
@php
|
||||
$currentUser = auth()->user();
|
||||
$currentUserName = data_get($currentUser, 'name', 'User');
|
||||
$currentUserEmail = data_get($currentUser, 'email', '-');
|
||||
$currentUserInitial = strtoupper(substr((string) $currentUserName, 0, 1)) ?: 'U';
|
||||
@endphp
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click="profileMenuOpen = !profileMenuOpen"
|
||||
class="flex items-center gap-3 rounded-2xl border border-slate-200 bg-white px-3 py-2 shadow-sm transition hover:border-indigo-200 hover:bg-slate-50"
|
||||
>
|
||||
<div class="hidden text-right md:block">
|
||||
<div class="text-sm font-semibold text-slate-800">{{ $currentUserName }}</div>
|
||||
<div class="text-xs text-slate-500">{{ $isAdmin ? 'Administrator' : 'Staff' }}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex h-11 w-11 items-center justify-center rounded-full bg-indigo-100 text-sm font-bold text-indigo-700">
|
||||
{{ $currentUserInitial }}
|
||||
</div>
|
||||
|
||||
<svg class="hidden h-4 w-4 text-slate-400 transition-transform md:block" :class="profileMenuOpen ? 'rotate-180' : ''" 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
|
||||
x-show="profileMenuOpen"
|
||||
x-transition.origin.top.right
|
||||
class="absolute right-0 z-50 mt-3 w-60 overflow-hidden rounded-2xl border border-slate-200 bg-white shadow-xl shadow-slate-200/70"
|
||||
>
|
||||
<div class="border-b border-slate-100 px-4 py-4">
|
||||
<div class="text-sm font-semibold text-slate-800">{{ $currentUserName }}</div>
|
||||
<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>
|
||||
<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>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="px-4 py-6 sm:px-6 lg:px-8">
|
||||
@isset($header)
|
||||
<div class="mb-6 rounded-2xl border border-slate-200 bg-white px-6 py-5 shadow-sm">
|
||||
{{ $header }}
|
||||
</div>
|
||||
@endisset
|
||||
|
||||
{{ $slot }}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,78 @@
|
||||
<x-master-page title="Kosongkan Data Aplikasi" description="Pilih kelompok data yang ingin dikosongkan. Sistem akan menyesuaikan dependensi relasi agar proses aman dari konflik foreign key.">
|
||||
<x-master-card title="Reset Data Selektif" subtitle="Gunakan fitur ini dengan hati-hati. Data yang dikosongkan dari menu ini tidak bisa dipulihkan dari UI.">
|
||||
<div class="rounded-3xl border border-amber-200 bg-amber-50 px-5 py-4 text-sm text-amber-700">
|
||||
<div class="font-semibold">Catatan penting:</div>
|
||||
<ul class="mt-3 list-disc space-y-1 pl-5">
|
||||
<li>Mas bisa memilih tabel/kelompok data tertentu saja.</li>
|
||||
<li>Backend akan menambahkan dependensi yang wajib ikut dibersihkan supaya tidak bentrok foreign key.</li>
|
||||
<li>Untuk dokumen siswa, file fisik di storage juga ikut dibersihkan.</li>
|
||||
<li>Konfirmasi memakai password admin yang sedang login.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid grid-cols-1 gap-4 md:grid-cols-4 xl:grid-cols-8">
|
||||
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600"><div class="text-xs uppercase tracking-[0.14em] text-slate-400">Tahun</div><div class="mt-1 text-2xl font-bold text-slate-800">{{ $stats['academicYears'] }}</div></div>
|
||||
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600"><div class="text-xs uppercase tracking-[0.14em] text-slate-400">Jurusan</div><div class="mt-1 text-2xl font-bold text-slate-800">{{ $stats['departments'] }}</div></div>
|
||||
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600"><div class="text-xs uppercase tracking-[0.14em] text-slate-400">Kelas</div><div class="mt-1 text-2xl font-bold text-slate-800">{{ $stats['classes'] }}</div></div>
|
||||
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600"><div class="text-xs uppercase tracking-[0.14em] text-slate-400">Jenis Dokumen</div><div class="mt-1 text-2xl font-bold text-slate-800">{{ $stats['documentTypes'] }}</div></div>
|
||||
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600"><div class="text-xs uppercase tracking-[0.14em] text-slate-400">Siswa</div><div class="mt-1 text-2xl font-bold text-slate-800">{{ $stats['students'] }}</div></div>
|
||||
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600"><div class="text-xs uppercase tracking-[0.14em] text-slate-400">Ortu/Wali</div><div class="mt-1 text-2xl font-bold text-slate-800">{{ $stats['studentParents'] }}</div></div>
|
||||
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600"><div class="text-xs uppercase tracking-[0.14em] text-slate-400">Dokumen</div><div class="mt-1 text-2xl font-bold text-slate-800">{{ $stats['studentDocuments'] }}</div></div>
|
||||
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600"><div class="text-xs uppercase tracking-[0.14em] text-slate-400">Histori</div><div class="mt-1 text-2xl font-bold text-slate-800">{{ $stats['studentAcademicHistories'] }}</div></div>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ route('settings.reset-data.execute') }}" class="mt-6 space-y-6">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<div class="mb-3 text-sm font-medium text-slate-700">Pilih data yang ingin dikosongkan</div>
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
@foreach ($targets as $key => $label)
|
||||
<label class="flex items-start gap-3 rounded-2xl border border-slate-200 bg-white px-4 py-3 transition hover:border-rose-200 hover:bg-rose-50/40">
|
||||
<input type="checkbox" name="targets[]" value="{{ $key }}" class="mt-1 rounded border-slate-300 text-rose-600 focus:ring-rose-500" @checked(in_array($key, old('targets', []), true))>
|
||||
<div>
|
||||
<div class="text-sm font-semibold text-slate-800">{{ $label }}</div>
|
||||
<div class="mt-1 text-xs text-slate-500">
|
||||
@switch($key)
|
||||
@case('students')
|
||||
Menghapus data siswa; sistem otomatis ikut membersihkan dokumen, orang tua/wali, dan histori akademik.
|
||||
@break
|
||||
@case('classes')
|
||||
Menghapus master kelas; sistem akan ikut memaksa pembersihan data siswa dan histori yang bergantung.
|
||||
@break
|
||||
@case('document_types')
|
||||
Menghapus master jenis dokumen; sistem akan ikut membersihkan dokumen siswa yang masih memakai jenis dokumen.
|
||||
@break
|
||||
@default
|
||||
Data pada kelompok ini akan dikosongkan sesuai relasi yang diperlukan.
|
||||
@endswitch
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="admin_password" class="mb-2 block text-sm font-medium text-slate-700">Password Admin</label>
|
||||
<input
|
||||
id="admin_password"
|
||||
name="admin_password"
|
||||
type="password"
|
||||
autocomplete="current-password"
|
||||
class="w-full rounded-2xl border border-slate-200 bg-white px-4 py-3 text-sm text-slate-700 placeholder:text-slate-400 focus:border-rose-300 focus:outline-none focus:ring-2 focus:ring-rose-100"
|
||||
placeholder="Masukkan password admin untuk konfirmasi"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<button type="submit" class="inline-flex items-center rounded-2xl bg-rose-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-rose-200 transition hover:bg-rose-700">
|
||||
Proses Kosongkan Data
|
||||
</button>
|
||||
<a href="{{ route('dashboard') }}" 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>
|
||||
</form>
|
||||
</x-master-card>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,16 @@
|
||||
<x-master-page title="Profil Pengguna" description="Kelola informasi akun, password, dan pengaturan dasar profil Anda.">
|
||||
<div class="space-y-6">
|
||||
<div class="rounded-3xl border border-slate-200 bg-white p-4 shadow-sm shadow-slate-200/50 sm:p-8">
|
||||
<div class="max-w-xl">
|
||||
@include('profile.partials.update-profile-information-form')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-3xl border border-slate-200 bg-white p-4 shadow-sm shadow-slate-200/50 sm:p-8">
|
||||
<div class="max-w-xl">
|
||||
@include('profile.partials.update-password-form')
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,47 @@
|
||||
<section>
|
||||
<header>
|
||||
<h2 class="text-lg font-semibold text-slate-900">Keamanan Akun</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">Gunakan password yang kuat dan tidak mudah ditebak untuk menjaga keamanan akun Anda.</p>
|
||||
</header>
|
||||
|
||||
<form method="post" action="{{ route('password.update') }}" class="mt-6 space-y-6">
|
||||
@csrf
|
||||
@method('put')
|
||||
|
||||
<div class="grid grid-cols-1 gap-5">
|
||||
<div>
|
||||
<label for="update_password_current_password" class="mb-2 block text-sm font-medium text-slate-700">Password Saat Ini</label>
|
||||
<input id="update_password_current_password" name="current_password" type="password" 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" autocomplete="current-password">
|
||||
<x-input-error :messages="$errors->updatePassword->get('current_password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="update_password_password" class="mb-2 block text-sm font-medium text-slate-700">Password Baru</label>
|
||||
<input id="update_password_password" name="password" type="password" 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" autocomplete="new-password">
|
||||
<x-input-error :messages="$errors->updatePassword->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="update_password_password_confirmation" class="mb-2 block text-sm font-medium text-slate-700">Konfirmasi Password Baru</label>
|
||||
<input id="update_password_password_confirmation" name="password_confirmation" type="password" 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" autocomplete="new-password">
|
||||
<x-input-error :messages="$errors->updatePassword->get('password_confirmation')" class="mt-2" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<button type="submit" class="inline-flex items-center justify-center rounded-2xl bg-slate-900 px-5 py-3 text-sm font-semibold text-white transition hover:bg-slate-800">
|
||||
Simpan Password Baru
|
||||
</button>
|
||||
|
||||
@if (session('status') === 'password-updated')
|
||||
<p
|
||||
x-data="{ show: true }"
|
||||
x-show="show"
|
||||
x-transition
|
||||
x-init="setTimeout(() => show = false, 2000)"
|
||||
class="text-sm font-medium text-emerald-600"
|
||||
>Password berhasil diperbarui.</p>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -0,0 +1,60 @@
|
||||
<section>
|
||||
<header>
|
||||
<h2 class="text-lg font-semibold text-slate-900">Informasi Akun</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">Perbarui nama dan email akun yang digunakan untuk masuk ke panel admin.</p>
|
||||
</header>
|
||||
|
||||
<form id="send-verification" method="post" action="{{ route('verification.send') }}">
|
||||
@csrf
|
||||
</form>
|
||||
|
||||
<form method="post" action="{{ route('profile.update') }}" class="mt-6 space-y-6">
|
||||
@csrf
|
||||
@method('patch')
|
||||
|
||||
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
|
||||
<div class="md:col-span-2">
|
||||
<label for="name" class="mb-2 block text-sm font-medium text-slate-700">Nama Lengkap</label>
|
||||
<input id="name" name="name" type="text" value="{{ old('name', $user->name) }}" 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 autofocus autocomplete="name">
|
||||
<x-input-error class="mt-2" :messages="$errors->get('name')" />
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-2">
|
||||
<label for="email" class="mb-2 block text-sm font-medium text-slate-700">Email</label>
|
||||
<input id="email" name="email" type="email" value="{{ old('email', $user->email) }}" 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 autocomplete="username">
|
||||
<x-input-error class="mt-2" :messages="$errors->get('email')" />
|
||||
|
||||
@if ($user instanceof \Illuminate\Contracts\Auth\MustVerifyEmail && ! $user->hasVerifiedEmail())
|
||||
<div class="mt-3 rounded-2xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-700">
|
||||
<p>Email Anda belum terverifikasi.</p>
|
||||
<button form="send-verification" class="mt-2 font-semibold text-amber-800 underline underline-offset-4 hover:text-amber-900">
|
||||
Kirim ulang email verifikasi
|
||||
</button>
|
||||
|
||||
@if (session('status') === 'verification-link-sent')
|
||||
<p class="mt-2 text-sm font-medium text-emerald-600">
|
||||
Link verifikasi baru sudah dikirim ke email Anda.
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<button type="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">
|
||||
Simpan Informasi Akun
|
||||
</button>
|
||||
|
||||
@if (session('status') === 'profile-updated')
|
||||
<p
|
||||
x-data="{ show: true }"
|
||||
x-show="show"
|
||||
x-transition
|
||||
x-init="setTimeout(() => show = false, 2000)"
|
||||
class="text-sm font-medium text-emerald-600"
|
||||
>Perubahan berhasil disimpan.</p>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
@@ -0,0 +1,54 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Preview Dokumen - {{ config('app.name', 'Database Kesiswaan') }}</title>
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="min-h-screen bg-slate-100 font-sans text-slate-900 antialiased">
|
||||
<div class="mx-auto max-w-6xl px-4 py-8 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 gap-6 xl:grid-cols-3">
|
||||
<div class="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm shadow-slate-200/50">
|
||||
<h1 class="text-lg font-semibold text-slate-900">Preview Dokumen</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">Informasi dokumen siswa yang dibuka dari portal publik.</p>
|
||||
|
||||
<div class="mt-6 space-y-4 text-sm text-slate-600">
|
||||
<div><span class="font-semibold text-slate-800">Jenis Dokumen:</span> {{ $document->documentType?->name ?: '-' }}</div>
|
||||
<div><span class="font-semibold text-slate-800">Nama File:</span> {{ $document->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> {{ $document->file_size ? number_format($document->file_size / 1024, 1) . ' KB' : '-' }}</div>
|
||||
<div><span class="font-semibold text-slate-800">Status Verifikasi:</span> {{ ucfirst($document->verification_status) }}</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex flex-wrap gap-3">
|
||||
<a href="{{ route('public.students.documents.download', $document) }}" class="rounded-2xl bg-sky-600 px-4 py-3 text-sm font-semibold text-white transition hover:bg-sky-700">Download</a>
|
||||
<a href="javascript:history.back()" 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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="xl:col-span-2 rounded-3xl border border-slate-200 bg-white p-6 shadow-sm shadow-slate-200/50">
|
||||
<h2 class="text-base font-semibold text-slate-900">Tampilan Dokumen</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">Preview langsung untuk gambar dan PDF.</p>
|
||||
|
||||
<div class="mt-6">
|
||||
@if ($isImage)
|
||||
<div class="overflow-hidden rounded-2xl border border-slate-200 bg-slate-50 p-4">
|
||||
<img src="{{ $fileUrl }}" alt="{{ $document->file_name }}" class="mx-auto max-h-[70vh] rounded-xl object-contain shadow-sm">
|
||||
</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 PDF"></iframe>
|
||||
</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 download.</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,300 @@
|
||||
@php
|
||||
try {
|
||||
$branding = \App\Models\AppSetting::branding();
|
||||
} catch (\Throwable $e) {
|
||||
$branding = \App\Models\AppSetting::brandingDefaults();
|
||||
}
|
||||
|
||||
$publicLogo = $branding['public_portal_logo'] ?: ($branding['app_logo'] ?? null);
|
||||
@endphp
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ $branding['app_name'] ?? config('app.name', 'Database Kesiswaan') }}</title>
|
||||
@if (!empty($branding['app_favicon']))
|
||||
<link rel="icon" type="image/png" href="{{ asset('storage/' . $branding['app_favicon']) }}">
|
||||
@endif
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="min-h-screen bg-slate-100 font-sans text-slate-900 antialiased">
|
||||
<div class="relative min-h-screen overflow-hidden bg-[radial-gradient(circle_at_top_left,_rgba(99,102,241,0.18),_transparent_30%),radial-gradient(circle_at_bottom_right,_rgba(14,165,233,0.12),_transparent_30%)]">
|
||||
<div class="absolute inset-0 bg-[linear-gradient(to_bottom,_rgba(255,255,255,0.78),_rgba(248,250,252,0.98))]"></div>
|
||||
|
||||
<div class="relative mx-auto max-w-6xl px-4 py-10 sm:px-6 lg:px-8">
|
||||
<div class="mb-8 flex justify-center text-center">
|
||||
<div class="inline-flex items-center gap-3 rounded-full border border-indigo-100 bg-white/90 px-4 py-2 shadow-sm shadow-slate-200/60">
|
||||
<div class="flex h-9 w-9 items-center justify-center overflow-hidden rounded-2xl bg-indigo-600 text-sm font-bold text-white shadow-lg shadow-indigo-200">
|
||||
@if (!empty($publicLogo))
|
||||
<img src="{{ asset('storage/' . $publicLogo) }}" alt="{{ $branding['app_name'] }}" class="h-full w-full object-contain bg-white p-1">
|
||||
@else
|
||||
{{ $branding['app_short_name'] ?? 'DK' }}
|
||||
@endif
|
||||
</div>
|
||||
<div class="text-left">
|
||||
<div class="text-sm font-semibold text-slate-900">{{ $branding['app_name'] ?? 'Database Kesiswaan' }}</div>
|
||||
<div class="text-xs text-slate-500">Portal Informasi Siswa</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">Pencarian Publik</div>
|
||||
<h2 class="mt-2 text-2xl font-bold text-slate-900">Cari siswa dengan cepat</h2>
|
||||
<p class="mt-2 text-sm leading-6 text-slate-500">
|
||||
Hasil pencarian akan menampilkan biodata dasar, informasi akademik, dan progres kelengkapan dokumen siswa.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6 sm:p-8">
|
||||
<div class="mb-6 flex flex-col gap-3 rounded-2xl border border-indigo-100 bg-indigo-50/70 px-4 py-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<div class="text-sm font-semibold text-indigo-900">Butuh melengkapi biodata siswa?</div>
|
||||
<p class="mt-1 text-sm text-indigo-700">Gunakan portal self servis untuk melengkapi data siswa secara mandiri dengan NIS/NISN dan kode akses dari sekolah.</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ route('public.student-self-service.index') }}" 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">
|
||||
Biodata Siswa
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="GET" action="{{ route('public.student-lookup') }}" class="space-y-4">
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-end">
|
||||
<div class="flex-1">
|
||||
<label for="q" class="mb-2 block text-sm font-semibold text-slate-700">Pencarian Siswa</label>
|
||||
<input
|
||||
id="q"
|
||||
name="q"
|
||||
type="text"
|
||||
value="{{ $query }}"
|
||||
class="h-[50px] w-full rounded-2xl border border-slate-200 bg-white px-4 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: 12345, 9988776655, atau nama siswa"
|
||||
>
|
||||
</div>
|
||||
<div class="lg:pb-[1px]">
|
||||
<button type="submit" class="rounded-xl bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-indigo-700">
|
||||
Cari Data Siswa
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-slate-400">Pencarian akan memprioritaskan kecocokan exact pada NIS atau NISN sebelum nama siswa.</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($query !== '')
|
||||
<div class="mt-8">
|
||||
@if ($student)
|
||||
<div class="space-y-6">
|
||||
@if ($students->count() > 1)
|
||||
<div class="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm shadow-slate-200/50">
|
||||
<div class="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-slate-900">Ditemukan {{ $students->count() }} hasil pencarian</h3>
|
||||
<p class="mt-1 text-sm text-slate-500">Sistem menampilkan hasil terbaik terlebih dahulu. Detail di bawah ini menggunakan hasil yang sedang dipilih.</p>
|
||||
</div>
|
||||
<div class="rounded-full bg-slate-100 px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-slate-500">
|
||||
Multi-match nama / data mirip
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
@foreach ($students as $matchedStudent)
|
||||
<a href="{{ route('public.student-lookup', ['q' => $query, 'student' => $matchedStudent->id]) }}" class="block rounded-2xl border {{ (int) $selectedStudentId === (int) $matchedStudent->id ? 'border-indigo-200 bg-indigo-50/60' : 'border-slate-200 bg-slate-50' }} px-4 py-4 text-sm text-slate-600 transition hover:border-indigo-200 hover:bg-indigo-50/40">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex min-w-0 items-start gap-3">
|
||||
@if ($matchedStudent->public_photo_url)
|
||||
<img src="{{ $matchedStudent->public_photo_url }}" alt="{{ $matchedStudent->full_name }}" class="h-14 w-14 shrink-0 rounded-2xl object-cover ring-1 ring-slate-200">
|
||||
@else
|
||||
<div class="flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-gradient-to-br from-slate-100 via-white to-slate-200 text-sm font-bold text-slate-700 ring-1 ring-slate-200">
|
||||
{{ strtoupper(mb_substr($matchedStudent->full_name, 0, 1)) }}
|
||||
</div>
|
||||
@endif
|
||||
<div class="min-w-0">
|
||||
<div class="font-semibold text-slate-800">{{ $matchedStudent->full_name }}</div>
|
||||
<div class="mt-2 text-xs text-slate-500">NIS: {{ $matchedStudent->student_number ?: '-' }}</div>
|
||||
<div class="text-xs text-slate-500">NISN: {{ $matchedStudent->nisn ?: '-' }}</div>
|
||||
<div class="mt-2 text-xs text-slate-500">{{ $matchedStudent->classRoom?->name ?: '-' }} · {{ $matchedStudent->department?->name ?: '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="rounded-full {{ (int) $selectedStudentId === (int) $matchedStudent->id ? 'bg-indigo-100 text-indigo-700' : 'bg-white text-slate-500' }} px-3 py-1 text-[11px] font-semibold">
|
||||
{{ (int) $selectedStudentId === (int) $matchedStudent->id ? 'Terpilih' : 'Pilih' }}
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="overflow-hidden rounded-3xl border border-emerald-200 bg-white shadow-sm shadow-emerald-100/60">
|
||||
<div class="border-b border-emerald-100 bg-emerald-50 px-6 py-5">
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div>
|
||||
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-emerald-600">Data ditemukan</div>
|
||||
<h2 class="mt-2 text-2xl font-bold text-slate-900">{{ $student->full_name }}</h2>
|
||||
<p class="mt-2 text-sm text-slate-500">Pencarian cocok berdasarkan {{ strtolower($matchedBy ?? 'data siswa') }} dan menampilkan ringkasan informasi siswa yang tersedia untuk publik.</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4 text-right">
|
||||
<div>
|
||||
<div class="text-3xl font-bold text-slate-900">{{ $requiredDocumentSummary['percentage'] }}%</div>
|
||||
<div class="text-sm {{ $requiredDocumentSummary['missing'] === 0 ? 'text-emerald-600' : 'text-amber-600' }}">
|
||||
{{ $requiredDocumentSummary['missing'] === 0 ? 'Upload lengkap' : 'Kurang ' . $requiredDocumentSummary['missing'] . ' dokumen' }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-3xl font-bold text-slate-900">{{ $requiredDocumentSummary['verification_percentage'] }}%</div>
|
||||
<div class="text-sm {{ $requiredDocumentSummary['unverified'] === 0 && $requiredDocumentSummary['missing'] === 0 ? 'text-emerald-600' : 'text-sky-600' }}">
|
||||
{{ $requiredDocumentSummary['unverified'] === 0 && $requiredDocumentSummary['missing'] === 0 ? 'Terverifikasi penuh' : $requiredDocumentSummary['unverified'] . ' belum valid' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 xl:grid-cols-3">
|
||||
<div class="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm shadow-slate-200/50">
|
||||
<h3 class="text-base font-semibold text-slate-900">Biodata Siswa</h3>
|
||||
<div class="mt-4 space-y-3 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">Tempat, Tanggal Lahir:</span> {{ $student->place_of_birth ?: '-' }}, {{ $student->date_of_birth?->format('d-m-Y') ?: '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm shadow-slate-200/50">
|
||||
<h3 class="text-base font-semibold text-slate-900">Informasi Akademik</h3>
|
||||
<div class="mt-4 space-y-3 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">Status:</span> {{ ucfirst($student->status) }}</div>
|
||||
<div><span class="font-semibold text-slate-800">Tanggal Masuk:</span> {{ $student->entry_date?->format('d-m-Y') ?: '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm shadow-slate-200/50">
|
||||
<h3 class="text-base font-semibold text-slate-900">Status Kelengkapan</h3>
|
||||
<div class="mt-4 space-y-4 text-sm text-slate-600">
|
||||
<div>
|
||||
<div class="mb-2 flex items-center justify-between text-xs font-semibold uppercase tracking-[0.16em] text-slate-400">
|
||||
<span>Upload Dokumen Wajib</span>
|
||||
<span>{{ $requiredDocumentSummary['uploaded'] }}/{{ $requiredDocumentSummary['total'] }}</span>
|
||||
</div>
|
||||
<div class="h-3 overflow-hidden rounded-full bg-slate-200">
|
||||
<div class="h-full rounded-full {{ $requiredDocumentSummary['missing'] === 0 ? 'bg-emerald-500' : 'bg-amber-500' }}" style="width: {{ $requiredDocumentSummary['percentage'] }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-2 flex items-center justify-between text-xs font-semibold uppercase tracking-[0.16em] text-slate-400">
|
||||
<span>Verifikasi Dokumen Wajib</span>
|
||||
<span>{{ $requiredDocumentSummary['verified'] }}/{{ $requiredDocumentSummary['total'] }}</span>
|
||||
</div>
|
||||
<div class="h-3 overflow-hidden rounded-full bg-slate-200">
|
||||
<div class="h-full rounded-full {{ $requiredDocumentSummary['unverified'] === 0 && $requiredDocumentSummary['missing'] === 0 ? 'bg-sky-500' : 'bg-slate-500' }}" style="width: {{ $requiredDocumentSummary['verification_percentage'] }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-3xl border border-slate-200 bg-white p-6 shadow-sm shadow-slate-200/50">
|
||||
<div class="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-slate-900">Dokumen Kelengkapan Siswa</h3>
|
||||
<p class="mt-1 text-sm text-slate-500">Informasi kelengkapan ditampilkan berdasarkan dokumen yang sudah diunggah siswa.</p>
|
||||
</div>
|
||||
<div class="rounded-full bg-slate-100 px-4 py-2 text-xs font-semibold uppercase tracking-[0.14em] text-slate-500">
|
||||
Informasi publik terbatas
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($requiredDocumentSummary['missing_items']->isNotEmpty() || $requiredDocumentSummary['unverified_items']->isNotEmpty())
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
@foreach ($requiredDocumentSummary['missing_items'] as $missingDocument)
|
||||
<span class="rounded-full bg-rose-100 px-3 py-1 text-xs font-semibold text-rose-700">Belum upload: {{ $missingDocument }}</span>
|
||||
@endforeach
|
||||
@foreach ($requiredDocumentSummary['unverified_items'] as $unverifiedDocument)
|
||||
<span class="rounded-full bg-sky-100 px-3 py-1 text-xs font-semibold text-sky-700">Belum valid: {{ $unverifiedDocument }}</span>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<div class="mt-4 rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-4 text-sm font-medium text-emerald-700">
|
||||
Seluruh dokumen wajib siswa ini sudah lengkap dan tervalidasi penuh.
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="mt-6 space-y-4">
|
||||
@forelse ($student->documents->sortBy('documentType.name') as $document)
|
||||
@php
|
||||
$verificationMap = [
|
||||
'pending' => 'bg-slate-100 text-slate-600',
|
||||
'valid' => 'bg-emerald-100 text-emerald-700',
|
||||
'invalid' => 'bg-rose-100 text-rose-700',
|
||||
'revision' => 'bg-amber-100 text-amber-700',
|
||||
];
|
||||
@endphp
|
||||
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 text-sm text-slate-600">
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div>
|
||||
<div class="font-semibold text-slate-800">{{ $document->documentType?->name ?: '-' }}</div>
|
||||
<div class="mt-1 text-xs text-slate-500">{{ $document->file_name }}</div>
|
||||
<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>
|
||||
<span class="rounded-full bg-white px-3 py-1 text-xs font-semibold text-slate-500">
|
||||
{{ strtoupper($document->file_extension ?: '-') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="{{ route('public.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">Preview</a>
|
||||
<a href="{{ route('public.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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-4 text-sm text-slate-500">
|
||||
Belum ada dokumen siswa yang diunggah.
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="overflow-hidden rounded-3xl border border-amber-200 bg-white shadow-sm shadow-amber-100/60">
|
||||
<div class="border-b border-amber-100 bg-amber-50 px-6 py-5">
|
||||
<div class="text-xs font-semibold uppercase tracking-[0.16em] text-amber-600">Data tidak ditemukan</div>
|
||||
<h2 class="mt-2 text-xl font-bold text-slate-900">Belum ada siswa yang cocok dengan pencarian tersebut</h2>
|
||||
<p class="mt-2 text-sm text-slate-500">Periksa kembali NIS, NISN, atau nama siswa yang dimasukkan, lalu coba cari ulang.</p>
|
||||
</div>
|
||||
<div class="px-6 py-5 text-sm text-slate-600">
|
||||
<ul class="list-disc space-y-2 pl-5">
|
||||
<li>Pastikan NIS atau NISN yang dimasukkan benar.</li>
|
||||
<li>Jika mencari berdasarkan nama, coba gunakan nama lengkap atau sebagian nama yang lebih spesifik.</li>
|
||||
<li>Jika data seharusnya ada tetapi belum muncul, hubungi admin sekolah.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="mt-16 text-center text-xs text-slate-400">
|
||||
Powered by <a href="https://noctkj.net" target="_blank" rel="noopener noreferrer" class="font-semibold text-slate-500 transition hover:text-indigo-600">noctkj.net</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,296 @@
|
||||
@php
|
||||
try {
|
||||
$branding = \App\Models\AppSetting::branding();
|
||||
} catch (\Throwable $e) {
|
||||
$branding = \App\Models\AppSetting::brandingDefaults();
|
||||
}
|
||||
@endphp
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>Form Pelengkapan Data Siswa - {{ $branding['app_name'] ?? 'Database Kesiswaan' }}</title>
|
||||
@if (!empty($branding['app_favicon']))
|
||||
<link rel="icon" type="image/png" href="{{ asset('storage/' . $branding['app_favicon']) }}">
|
||||
@endif
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="min-h-screen bg-slate-100 text-slate-900">
|
||||
<div
|
||||
x-data="studentSelfServiceForm()"
|
||||
x-init="init()"
|
||||
class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8"
|
||||
>
|
||||
<script>
|
||||
function studentSelfServiceForm() {
|
||||
return {
|
||||
selectedProvince: @js((string) old('province_id', $student->province_id ?? '')),
|
||||
selectedRegency: @js((string) old('regency_id', $student->regency_id ?? '')),
|
||||
selectedDistrict: @js((string) old('district_id', $student->district_id ?? '')),
|
||||
selectedVillage: @js((string) old('village_id', $student->village_id ?? '')),
|
||||
regencies: [],
|
||||
districts: [],
|
||||
villages: [],
|
||||
loadingRegencies: false,
|
||||
loadingDistricts: false,
|
||||
loadingVillages: false,
|
||||
async init() {
|
||||
if (this.selectedProvince) await this.loadRegencies(false);
|
||||
if (this.selectedRegency) await this.loadDistricts(false);
|
||||
if (this.selectedDistrict) await this.loadVillages(false);
|
||||
},
|
||||
async loadRegencies(reset = true) {
|
||||
if (reset) {
|
||||
this.selectedRegency = '';
|
||||
this.selectedDistrict = '';
|
||||
this.selectedVillage = '';
|
||||
this.districts = [];
|
||||
this.villages = [];
|
||||
}
|
||||
this.regencies = [];
|
||||
if (!this.selectedProvince) return;
|
||||
this.loadingRegencies = true;
|
||||
const response = await fetch(`/regions/provinces/${this.selectedProvince}/regencies`);
|
||||
this.regencies = await response.json();
|
||||
this.loadingRegencies = false;
|
||||
},
|
||||
async loadDistricts(reset = true) {
|
||||
if (reset) {
|
||||
this.selectedDistrict = '';
|
||||
this.selectedVillage = '';
|
||||
this.villages = [];
|
||||
}
|
||||
this.districts = [];
|
||||
if (!this.selectedRegency) return;
|
||||
this.loadingDistricts = true;
|
||||
const response = await fetch(`/regions/regencies/${this.selectedRegency}/districts`);
|
||||
this.districts = await response.json();
|
||||
this.loadingDistricts = false;
|
||||
},
|
||||
async loadVillages(reset = true) {
|
||||
if (reset) this.selectedVillage = '';
|
||||
this.villages = [];
|
||||
if (!this.selectedDistrict) return;
|
||||
this.loadingVillages = true;
|
||||
const response = await fetch(`/regions/districts/${this.selectedDistrict}/villages`);
|
||||
this.villages = await response.json();
|
||||
this.loadingVillages = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</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>
|
||||
<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><strong>NIS:</strong> {{ $student->student_number ?: '-' }}</div>
|
||||
<div><strong>NISN:</strong> {{ $student->nisn ?: '-' }}</div>
|
||||
<div><strong>Tahun Angkatan:</strong> {{ $student->academicYear?->name ?: '-' }}</div>
|
||||
<div><strong>Program Keahlian:</strong> {{ $student->department?->name ?: '-' }}</div>
|
||||
<div><strong>Kelas:</strong> {{ $student->classRoom?->name ?: '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (session('success'))
|
||||
<div class="mb-6 rounded-2xl border border-emerald-200 bg-emerald-50 px-5 py-4 text-sm text-emerald-700">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($errors->any())
|
||||
<div class="mb-6 rounded-2xl border border-rose-200 bg-rose-50 px-5 py-4 text-sm text-rose-700">
|
||||
<ul class="list-disc space-y-1 pl-5">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('public.student-self-service.update') }}" class="space-y-6">
|
||||
@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">
|
||||
<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">
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Nama Lengkap</label>
|
||||
<input name="full_name" type="text" value="{{ old('full_name', $student->full_name) }}" class="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Jenis Kelamin</label>
|
||||
<select name="gender" class="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm" required>
|
||||
<option value="">-- Pilih --</option>
|
||||
@foreach ($genders as $value => $label)
|
||||
<option value="{{ $value }}" @selected(old('gender', $student->gender) === $value)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Agama</label>
|
||||
<select name="religion" class="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm">
|
||||
<option value="">-- Pilih --</option>
|
||||
@foreach ($religions as $value => $label)
|
||||
<option value="{{ $value }}" @selected(old('religion', $student->religion) === $value)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Tempat Lahir</label>
|
||||
<input name="place_of_birth" type="text" value="{{ old('place_of_birth', $student->place_of_birth) }}" 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">Tanggal Lahir</label>
|
||||
<input name="date_of_birth" type="date" value="{{ old('date_of_birth', optional($student->date_of_birth)->format('Y-m-d')) }}" 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">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">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">
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Anak Ke</label>
|
||||
<input name="child_order" type="number" value="{{ old('child_order', $student->child_order) }}" 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">Jumlah Saudara</label>
|
||||
<input name="number_of_siblings" type="number" value="{{ old('number_of_siblings', $student->number_of_siblings) }}" 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. HP</label>
|
||||
<input name="phone_number" type="text" value="{{ old('phone_number', $student->phone_number) }}" 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">Email</label>
|
||||
<input name="email" type="email" value="{{ old('email', $student->email) }}" class="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-[2rem] bg-white p-6 shadow-sm shadow-slate-200">
|
||||
<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>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Provinsi</label>
|
||||
<select name="province_id" x-model="selectedProvince" @change="loadRegencies()" class="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm">
|
||||
<option value="">-- Pilih Provinsi --</option>
|
||||
@foreach ($provinces as $id => $name)
|
||||
<option value="{{ $id }}">{{ $name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Kabupaten/Kota</label>
|
||||
<select name="regency_id" x-model="selectedRegency" @change="loadDistricts()" class="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm">
|
||||
<option value="" x-text="loadingRegencies ? 'Memuat...' : '-- Pilih Kabupaten/Kota --'"></option>
|
||||
<template x-for="regency in regencies" :key="regency.id">
|
||||
<option :value="regency.id" x-text="regency.name"></option>
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Kecamatan</label>
|
||||
<select name="district_id" x-model="selectedDistrict" @change="loadVillages()" class="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm">
|
||||
<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 class="mb-2 block text-sm font-medium text-slate-700">Desa/Kelurahan</label>
|
||||
<select name="village_id" x-model="selectedVillage" class="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm">
|
||||
<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>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Kode Pos</label>
|
||||
<input name="postal_code" type="text" value="{{ old('postal_code', $student->postal_code) }}" class="h-12 w-full rounded-2xl border border-slate-200 px-4 text-sm">
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Alamat Lengkap</label>
|
||||
<textarea name="address" rows="4" class="w-full rounded-2xl border border-slate-200 px-4 py-3 text-sm">{{ old('address', $student->address) }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-[2rem] bg-white p-6 shadow-sm shadow-slate-200">
|
||||
<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">
|
||||
<h3 class="text-base font-semibold text-slate-800">{{ $label }}</h3>
|
||||
<div class="mt-4 space-y-4">
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Nama</label>
|
||||
<input name="parents[{{ $relationType }}][name]" type="text" value="{{ old("parents.$relationType.name", $parent?->name) }}" class="h-11 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="parents[{{ $relationType }}][nik]" type="text" value="{{ old("parents.$relationType.nik", $parent?->nik) }}" class="h-11 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">Tahun Lahir</label>
|
||||
<input name="parents[{{ $relationType }}][birth_year]" type="number" value="{{ old("parents.$relationType.birth_year", $parent?->birth_year) }}" class="h-11 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">Pendidikan</label>
|
||||
<select name="parents[{{ $relationType }}][education]" class="h-11 w-full rounded-2xl border border-slate-200 px-4 text-sm">
|
||||
<option value="">-- Pilih Pendidikan --</option>
|
||||
@foreach ($educationOptions as $value => $label)
|
||||
<option value="{{ $value }}" @selected(old("parents.$relationType.education", $parent?->education) === $value)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-slate-700">Pekerjaan</label>
|
||||
<input name="parents[{{ $relationType }}][occupation]" type="text" value="{{ old("parents.$relationType.occupation", $parent?->occupation) }}" class="h-11 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. HP</label>
|
||||
<input name="parents[{{ $relationType }}][phone_number]" type="text" value="{{ old("parents.$relationType.phone_number", $parent?->phone_number) }}" class="h-11 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">Alamat</label>
|
||||
<textarea name="parents[{{ $relationType }}][address]" rows="3" class="w-full rounded-2xl border border-slate-200 px-4 py-3 text-sm">{{ old("parents.$relationType.address", $parent?->address) }}</textarea>
|
||||
</div>
|
||||
<label class="inline-flex items-center gap-3 text-sm text-slate-700">
|
||||
<input type="hidden" name="parents[{{ $relationType }}][is_alive]" value="0">
|
||||
<input type="checkbox" name="parents[{{ $relationType }}][is_alive]" value="1" class="rounded border-slate-300 text-indigo-600 focus:ring-indigo-500" @checked(old("parents.$relationType.is_alive", $parent?->is_alive ?? true))>
|
||||
<span>Masih hidup</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</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
|
||||
</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>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,112 @@
|
||||
@php
|
||||
try {
|
||||
$branding = \App\Models\AppSetting::branding();
|
||||
} catch (\Throwable $e) {
|
||||
$branding = \App\Models\AppSetting::brandingDefaults();
|
||||
}
|
||||
|
||||
$publicLogo = $branding['public_portal_logo'] ?: ($branding['app_logo'] ?? null);
|
||||
@endphp
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>Pelengkapan Data Siswa - {{ $branding['app_name'] ?? 'Database Kesiswaan' }}</title>
|
||||
@if (!empty($branding['app_favicon']))
|
||||
<link rel="icon" type="image/png" href="{{ asset('storage/' . $branding['app_favicon']) }}">
|
||||
@endif
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="min-h-screen bg-slate-100 font-sans text-slate-900 antialiased">
|
||||
<div class="relative min-h-screen overflow-hidden bg-[radial-gradient(circle_at_top_left,_rgba(99,102,241,0.18),_transparent_30%),radial-gradient(circle_at_bottom_right,_rgba(14,165,233,0.12),_transparent_30%)]">
|
||||
<div class="absolute inset-0 bg-[linear-gradient(to_bottom,_rgba(255,255,255,0.78),_rgba(248,250,252,0.98))]"></div>
|
||||
|
||||
<div class="relative mx-auto max-w-6xl px-4 py-10 sm:px-6 lg:px-8">
|
||||
<div class="mb-8 flex justify-center text-center">
|
||||
<div class="inline-flex items-center gap-3 rounded-full border border-indigo-100 bg-white/90 px-4 py-2 shadow-sm shadow-slate-200/60">
|
||||
<div class="flex h-9 w-9 items-center justify-center overflow-hidden rounded-2xl bg-indigo-600 text-sm font-bold text-white shadow-lg shadow-indigo-200">
|
||||
@if (!empty($publicLogo))
|
||||
<img src="{{ asset('storage/' . $publicLogo) }}" alt="{{ $branding['app_name'] }}" class="h-full w-full object-contain bg-white p-1">
|
||||
@else
|
||||
{{ $branding['app_short_name'] ?? 'DK' }}
|
||||
@endif
|
||||
</div>
|
||||
<div class="text-left">
|
||||
<div class="text-sm font-semibold text-slate-900">{{ $branding['app_name'] ?? 'Database Kesiswaan' }}</div>
|
||||
<div class="text-xs text-slate-500">Portal Pelengkapan Siswa</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</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>
|
||||
|
||||
@if ($errors->any())
|
||||
<div class="mb-6 rounded-2xl border border-rose-200 bg-rose-50 px-5 py-4 text-sm text-rose-700">
|
||||
<ul class="list-disc space-y-1 pl-5">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('public.student-self-service.verify') }}" class="space-y-4">
|
||||
@csrf
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-end">
|
||||
<div class="flex-1">
|
||||
<label for="identity_number" class="mb-2 block text-sm font-semibold text-slate-700">NIS atau NISN</label>
|
||||
<input
|
||||
id="identity_number"
|
||||
name="identity_number"
|
||||
type="text"
|
||||
value="{{ old('identity_number') }}"
|
||||
class="h-[50px] w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 placeholder:text-slate-400 focus:border-indigo-300 focus:outline-none focus:ring-2 focus:ring-indigo-100"
|
||||
placeholder="Masukkan NIS atau NISN"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label for="access_code" class="mb-2 block text-sm font-semibold text-slate-700">Kode Akses</label>
|
||||
<input
|
||||
id="access_code"
|
||||
name="access_code"
|
||||
type="text"
|
||||
value="{{ old('access_code') }}"
|
||||
class="h-[50px] w-full rounded-2xl border border-slate-200 bg-white px-4 uppercase text-sm text-slate-700 placeholder:text-slate-400 focus:border-indigo-300 focus:outline-none focus:ring-2 focus:ring-indigo-100"
|
||||
placeholder="Masukkan kode akses dari sekolah"
|
||||
required
|
||||
>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-slate-400">Setelah verifikasi berhasil, Anda dapat melengkapi biodata, alamat, dan data orang tua/wali.</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,105 @@
|
||||
<x-master-page title="Branding Aplikasi" description="Atur nama aplikasi, logo utama, favicon, dan logo portal publik dari satu halaman pengaturan.">
|
||||
<x-master-card title="Identitas Aplikasi" subtitle="Kelola branding utama yang dipakai di berbagai halaman dashboard dan portal publik.">
|
||||
<form method="POST" action="{{ route('settings.branding.update') }}" enctype="multipart/form-data" class="space-y-8">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
<div>
|
||||
<label for="app_name" class="mb-2 block text-sm font-medium text-slate-700">Nama Aplikasi</label>
|
||||
<input id="app_name" name="app_name" type="text" value="{{ old('app_name', $branding['app_name']) }}" 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>
|
||||
<x-input-error :messages="$errors->get('app_name')" class="mt-2" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="app_short_name" class="mb-2 block text-sm font-medium text-slate-700">Nama Singkat / Inisial</label>
|
||||
<input id="app_short_name" name="app_short_name" type="text" value="{{ old('app_short_name', $branding['app_short_name']) }}" 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>
|
||||
<x-input-error :messages="$errors->get('app_short_name')" class="mt-2" />
|
||||
<p class="mt-2 text-xs text-slate-500">Dipakai sebagai fallback teks jika logo belum diupload, misalnya: DK.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 xl:grid-cols-3">
|
||||
<div class="rounded-3xl border border-slate-200 bg-slate-50 p-5">
|
||||
<div class="mb-4 flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-slate-900">Logo Utama</h3>
|
||||
<p class="mt-1 text-xs text-slate-500">Untuk dashboard, login, dan area admin.</p>
|
||||
</div>
|
||||
@if ($branding['app_logo'])
|
||||
<label class="inline-flex items-center gap-2 text-xs text-rose-600">
|
||||
<input type="checkbox" name="remove_app_logo" value="1" class="rounded border-slate-300 text-rose-600 focus:ring-rose-500">
|
||||
<span>Hapus</span>
|
||||
</label>
|
||||
@endif
|
||||
</div>
|
||||
<div class="mb-4 flex h-32 items-center justify-center rounded-2xl border border-dashed border-slate-300 bg-white p-4">
|
||||
@if ($branding['app_logo'])
|
||||
<img src="{{ asset('storage/' . $branding['app_logo']) }}" alt="Logo utama" class="max-h-24 max-w-full object-contain">
|
||||
@else
|
||||
<div class="text-center text-sm text-slate-400">Belum ada logo utama</div>
|
||||
@endif
|
||||
</div>
|
||||
<input type="file" name="app_logo" accept=".jpg,.jpeg,.png,.webp,.svg" class="block w-full text-sm text-slate-600 file:mr-4 file:rounded-xl file:border-0 file:bg-white file:px-4 file:py-2 file:font-semibold file:text-slate-700 hover:file:bg-slate-100">
|
||||
<x-input-error :messages="$errors->get('app_logo')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="rounded-3xl border border-slate-200 bg-slate-50 p-5">
|
||||
<div class="mb-4 flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-slate-900">Favicon</h3>
|
||||
<p class="mt-1 text-xs text-slate-500">Untuk tab browser dan shortcut ikon kecil.</p>
|
||||
</div>
|
||||
@if ($branding['app_favicon'])
|
||||
<label class="inline-flex items-center gap-2 text-xs text-rose-600">
|
||||
<input type="checkbox" name="remove_app_favicon" value="1" class="rounded border-slate-300 text-rose-600 focus:ring-rose-500">
|
||||
<span>Hapus</span>
|
||||
</label>
|
||||
@endif
|
||||
</div>
|
||||
<div class="mb-4 flex h-32 items-center justify-center rounded-2xl border border-dashed border-slate-300 bg-white p-4">
|
||||
@if ($branding['app_favicon'])
|
||||
<img src="{{ asset('storage/' . $branding['app_favicon']) }}" alt="Favicon" class="max-h-16 max-w-full object-contain">
|
||||
@else
|
||||
<div class="text-center text-sm text-slate-400">Belum ada favicon</div>
|
||||
@endif
|
||||
</div>
|
||||
<input type="file" name="app_favicon" accept=".jpg,.jpeg,.png,.webp,.svg,.ico" class="block w-full text-sm text-slate-600 file:mr-4 file:rounded-xl file:border-0 file:bg-white file:px-4 file:py-2 file:font-semibold file:text-slate-700 hover:file:bg-slate-100">
|
||||
<x-input-error :messages="$errors->get('app_favicon')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="rounded-3xl border border-slate-200 bg-slate-50 p-5">
|
||||
<div class="mb-4 flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-slate-900">Logo Portal Publik</h3>
|
||||
<p class="mt-1 text-xs text-slate-500">Opsional untuk portal pencarian dan pelengkapan siswa.</p>
|
||||
</div>
|
||||
@if ($branding['public_portal_logo'])
|
||||
<label class="inline-flex items-center gap-2 text-xs text-rose-600">
|
||||
<input type="checkbox" name="remove_public_portal_logo" value="1" class="rounded border-slate-300 text-rose-600 focus:ring-rose-500">
|
||||
<span>Hapus</span>
|
||||
</label>
|
||||
@endif
|
||||
</div>
|
||||
<div class="mb-4 flex h-32 items-center justify-center rounded-2xl border border-dashed border-slate-300 bg-white p-4">
|
||||
@if ($branding['public_portal_logo'])
|
||||
<img src="{{ asset('storage/' . $branding['public_portal_logo']) }}" alt="Logo portal publik" class="max-h-24 max-w-full object-contain">
|
||||
@else
|
||||
<div class="text-center text-sm text-slate-400">Belum ada logo portal publik</div>
|
||||
@endif
|
||||
</div>
|
||||
<input type="file" name="public_portal_logo" accept=".jpg,.jpeg,.png,.webp,.svg" class="block w-full text-sm text-slate-600 file:mr-4 file:rounded-xl file:border-0 file:bg-white file:px-4 file:py-2 file:font-semibold file:text-slate-700 hover:file:bg-slate-100">
|
||||
<x-input-error :messages="$errors->get('public_portal_logo')" class="mt-2" />
|
||||
</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">
|
||||
Simpan Branding
|
||||
</button>
|
||||
<a href="{{ route('dashboard') }}" class="rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">
|
||||
Kembali
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</x-master-card>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,72 @@
|
||||
@php
|
||||
$defaultAllowedExtensions = 'jpg,jpeg,png,pdf,webp';
|
||||
$documentTypeOptions = $documentTypes->mapWithKeys(function ($documentType) use ($defaultMaxFileSizeKb, $defaultAllowedExtensions) {
|
||||
return [
|
||||
$documentType->id => [
|
||||
'label' => $documentType->name,
|
||||
'max' => $documentType->max_file_size_kb ?: $defaultMaxFileSizeKb,
|
||||
'extensions' => $documentType->allowed_extensions ?: $defaultAllowedExtensions,
|
||||
],
|
||||
];
|
||||
});
|
||||
$selectedDocumentTypeId = old('document_type_id', $document->document_type_id ?? null);
|
||||
$selectedMaxSizeKb = $selectedDocumentTypeId && $documentTypeOptions->has((int) $selectedDocumentTypeId)
|
||||
? $documentTypeOptions[(int) $selectedDocumentTypeId]['max']
|
||||
: $defaultMaxFileSizeKb;
|
||||
$selectedExtensions = $selectedDocumentTypeId && $documentTypeOptions->has((int) $selectedDocumentTypeId)
|
||||
? $documentTypeOptions[(int) $selectedDocumentTypeId]['extensions']
|
||||
: $defaultAllowedExtensions;
|
||||
@endphp
|
||||
|
||||
<div class="grid grid-cols-1 gap-6 xl:grid-cols-2">
|
||||
<x-master-card title="Form Dokumen Siswa" subtitle="Unggah file dan lengkapi metadata dokumen siswa.">
|
||||
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
|
||||
<div>
|
||||
<label for="document_type_id" class="mb-2 block text-sm font-medium text-slate-700">Jenis Dokumen</label>
|
||||
<select id="document_type_id" name="document_type_id" 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" data-document-type-select>
|
||||
<option value="">-- Pilih --</option>
|
||||
@foreach ($documentTypeOptions as $id => $option)
|
||||
<option value="{{ $id }}" data-max-size-kb="{{ $option['max'] }}" data-extensions="{{ $option['extensions'] }}" @selected((string) old('document_type_id', $document->document_type_id ?? null) === (string) $id)>{{ $option['label'] }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<x-form-select name="verification_status" label="Status Verifikasi" :options="$verificationStatuses" :value="$document->verification_status ?? 'pending'" required />
|
||||
<x-form-input name="document_number" label="Nomor Dokumen" :value="$document->document_number ?? ''" placeholder="Nomor dokumen (opsional)" />
|
||||
<x-form-input name="issued_date" label="Tanggal Terbit" type="date" :value="isset($document?->issued_date) ? $document->issued_date?->format('Y-m-d') : ''" />
|
||||
<x-form-input name="expired_date" label="Tanggal Kadaluarsa" type="date" :value="isset($document?->expired_date) ? $document->expired_date?->format('Y-m-d') : ''" />
|
||||
<div class="md:col-span-2">
|
||||
<label for="document_file" class="mb-2 block text-sm font-medium text-slate-700">File Dokumen</label>
|
||||
<input id="document_file" name="document_file" type="file" accept=".jpg,.jpeg,.png,.pdf,.webp" 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-indigo-50 file:px-4 file:py-2 file:text-sm file:font-semibold file:text-indigo-700 hover:file:bg-indigo-100">
|
||||
<p class="mt-2 text-xs text-slate-400" data-allowed-extensions-info>Format diizinkan: {{ strtoupper(str_replace(',', ', ', $selectedExtensions)) }}.</p>
|
||||
<p class="mt-1 text-xs font-medium text-indigo-600" data-max-file-size-info>
|
||||
Maksimal ukuran file untuk jenis dokumen ini: {{ number_format($selectedMaxSizeKb / 1024, 2) }} MB
|
||||
</p>
|
||||
@if(isset($document) && $document->file_name)
|
||||
<p class="mt-2 text-xs text-slate-500">File saat ini: {{ $document->file_name }}</p>
|
||||
@endif
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<x-form-textarea name="description" label="Deskripsi" :value="$document->description ?? ''" placeholder="Catatan tambahan untuk dokumen ini" rows="4" />
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<x-form-textarea name="verification_note" label="Catatan Verifikasi" :value="$document->verification_note ?? ''" placeholder="Catatan verifikasi, alasan revisi, atau keterangan tambahan" rows="3" />
|
||||
</div>
|
||||
</div>
|
||||
</x-master-card>
|
||||
|
||||
<x-master-card title="Ringkasan Siswa" subtitle="Dokumen ini akan terhubung ke siswa berikut.">
|
||||
<div class="space-y-4 text-sm text-slate-600">
|
||||
<div><span class="font-semibold text-slate-800">Nama Siswa:</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">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>
|
||||
</x-master-card>
|
||||
</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.show', $student) }}" class="rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">Kembali</a>
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
<x-master-page title="Tambah Dokumen Siswa" description="Unggah dokumen pendukung untuk siswa terpilih.">
|
||||
<form action="{{ route('students.documents.store', $student) }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@include('student-documents._form', ['submitLabel' => 'Upload Dokumen'])
|
||||
</form>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,7 @@
|
||||
<x-master-page title="Edit Dokumen Siswa" description="Perbarui file atau metadata dokumen siswa.">
|
||||
<form action="{{ route('students.documents.update', [$student, $document]) }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
@include('student-documents._form', ['submitLabel' => 'Update Dokumen'])
|
||||
</form>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,45 @@
|
||||
<x-master-page title="Preview Dokumen Siswa" description="Lihat dokumen siswa langsung di browser bila format mendukung.">
|
||||
<div class="grid grid-cols-1 gap-6 xl:grid-cols-3">
|
||||
<x-master-card title="Informasi Dokumen" subtitle="Metadata dokumen siswa.">
|
||||
<div class="space-y-4 text-sm text-slate-600">
|
||||
<div><span class="font-semibold text-slate-800">Nama File:</span> {{ $document->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> {{ $document->file_size ? number_format($document->file_size / 1024, 1) . ' KB' : '-' }}</div>
|
||||
<div><span class="font-semibold text-slate-800">No. Dokumen:</span> {{ $document->document_number ?: '-' }}</div>
|
||||
<div><span class="font-semibold text-slate-800">Tanggal Terbit:</span> {{ $document->issued_date?->format('d-m-Y') ?: '-' }}</div>
|
||||
<div><span class="font-semibold text-slate-800">Kadaluarsa:</span> {{ $document->expired_date?->format('d-m-Y') ?: '-' }}</div>
|
||||
<div><span class="font-semibold text-slate-800">Status Verifikasi:</span> {{ ucfirst($document->verification_status) }}</div>
|
||||
@if ($document->verification_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:</span> {{ $document->verification_note }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex flex-wrap gap-3">
|
||||
<a href="{{ route('students.documents.download', $document) }}" class="rounded-2xl bg-sky-600 px-4 py-3 text-sm font-semibold text-white transition hover:bg-sky-700">Download</a>
|
||||
<a href="{{ route('students.documents.edit', [$document->student_id, $document]) }}" class="rounded-2xl border border-slate-200 px-4 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">Edit Dokumen</a>
|
||||
<a href="{{ route('students.show', $document->student_id) }}" 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 Siswa</a>
|
||||
</div>
|
||||
</x-master-card>
|
||||
|
||||
<div class="xl:col-span-2">
|
||||
<x-master-card title="Tampilan Dokumen" 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="{{ $document->file_name }}" class="mx-auto max-h-[70vh] rounded-xl object-contain shadow-sm">
|
||||
</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 PDF"></iframe>
|
||||
</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 download.</p>
|
||||
</div>
|
||||
@endif
|
||||
</x-master-card>
|
||||
</div>
|
||||
</div>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,71 @@
|
||||
<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>
|
||||
</form>
|
||||
</x-master-card>
|
||||
|
||||
<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">
|
||||
<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">Akademik Terakhir</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Tanggal Keluar</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Surat Keluar</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Catatan</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 ($studentExits as $studentExit)
|
||||
<tr>
|
||||
<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>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-600">
|
||||
<div>{{ $studentExit->academicYear?->name ?: '-' }}</div>
|
||||
<div>{{ $studentExit->department?->name ?: '-' }}</div>
|
||||
<div>{{ $studentExit->classRoom?->name ?: '-' }}</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>
|
||||
<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>
|
||||
</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>
|
||||
@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">
|
||||
<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>
|
||||
</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>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
{{ $studentExits->links() }}
|
||||
</div>
|
||||
</x-master-card>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,38 @@
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</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">
|
||||
</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>
|
||||
@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>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,86 @@
|
||||
<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-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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
</x-master-card>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<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>
|
||||
</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>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('student-exits.update', $studentExit) }}" enctype="multipart/form-data" class="space-y-4">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
|
||||
<x-form-input name="exit_date" label="Tanggal Keluar" type="date" :value="optional($studentExit->exit_date)->format('Y-m-d')" />
|
||||
<x-form-textarea name="exit_reason" label="Catatan / Alasan Keluar" :value="$studentExit->exit_reason" rows="4" placeholder="Masukkan catatan tambahan bila perlu" />
|
||||
|
||||
<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">
|
||||
<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>
|
||||
</form>
|
||||
</div>
|
||||
</x-master-card>
|
||||
</div>
|
||||
</div>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,35 @@
|
||||
<div class="grid grid-cols-1 gap-6 xl:grid-cols-2">
|
||||
<x-master-card title="Informasi Orang Tua / Wali" subtitle="Masukkan data orang tua atau wali siswa.">
|
||||
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
|
||||
<x-form-select name="relation_type" label="Hubungan" :options="$relations" :value="$parent->relation_type ?? null" required />
|
||||
<x-form-input name="name" label="Nama" :value="$parent->name ?? ''" placeholder="Nama lengkap" required />
|
||||
<x-form-input name="nik" label="NIK" :value="$parent->nik ?? ''" placeholder="Nomor induk kependudukan" />
|
||||
<x-form-input name="birth_year" label="Tahun Lahir" type="number" :value="$parent->birth_year ?? ''" placeholder="Contoh: 1980" />
|
||||
<x-form-select name="education" label="Pendidikan" :options="$educationOptions" :value="$parent->education ?? null" />
|
||||
<x-form-input name="occupation" label="Pekerjaan" :value="$parent->occupation ?? ''" placeholder="Pekerjaan" />
|
||||
<x-form-input name="phone_number" label="No. HP" :value="$parent->phone_number ?? ''" placeholder="Nomor HP" />
|
||||
<div class="md:col-span-2">
|
||||
<x-form-textarea name="address" label="Alamat" :value="$parent->address ?? ''" placeholder="Alamat lengkap orang tua / wali" rows="4" />
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<x-form-checkbox name="is_alive" label="Masih hidup" :checked="isset($parent) ? (bool) $parent->is_alive : true" />
|
||||
</div>
|
||||
</div>
|
||||
</x-master-card>
|
||||
|
||||
<x-master-card title="Ringkasan Siswa" subtitle="Relasi data orang tua / wali ke siswa terpilih.">
|
||||
<div class="space-y-4 text-sm text-slate-600">
|
||||
<div><span class="font-semibold text-slate-800">Nama Siswa:</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">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>
|
||||
</x-master-card>
|
||||
</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.show', $student) }}" class="rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">Kembali</a>
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
<x-master-page title="Tambah Orang Tua / Wali" description="Tambahkan data orang tua atau wali untuk siswa terpilih.">
|
||||
<form action="{{ route('students.parents.store', $student) }}" method="POST">
|
||||
@csrf
|
||||
@include('student-parents._form', ['submitLabel' => 'Simpan'])
|
||||
</form>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,7 @@
|
||||
<x-master-page title="Edit Orang Tua / Wali" description="Perbarui data orang tua atau wali yang sudah tersimpan.">
|
||||
<form action="{{ route('students.parents.update', [$student, $parent]) }}" method="POST">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
@include('student-parents._form', ['submitLabel' => 'Update'])
|
||||
</form>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,236 @@
|
||||
<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">
|
||||
<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">
|
||||
<option value="">Pilih tahun ajaran asal</option>
|
||||
@foreach ($academicYears as $academicYear)
|
||||
<option value="{{ $academicYear->id }}" @selected((int) $filters['from_academic_year_id'] === (int) $academicYear->id)>
|
||||
{{ $academicYear->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="to_academic_year_id" class="mb-2 block text-sm font-medium text-slate-700">Tahun Ajaran Tujuan</label>
|
||||
<select id="to_academic_year_id" name="to_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">
|
||||
<option value="">Pilih tahun ajaran tujuan</option>
|
||||
@foreach ($academicYears as $academicYear)
|
||||
<option value="{{ $academicYear->id }}" @selected((int) $filters['to_academic_year_id'] === (int) $academicYear->id)>
|
||||
{{ $academicYear->name }}
|
||||
</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
|
||||
</button>
|
||||
</div>
|
||||
</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.">
|
||||
@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.
|
||||
</div>
|
||||
@elseif ($students->isEmpty())
|
||||
<div class="text-sm text-slate-500">Tidak ada siswa aktif pada tahun ajaran 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();
|
||||
@endphp
|
||||
|
||||
@if ($errors->any())
|
||||
<div class="mb-6 rounded-3xl border border-rose-200 bg-rose-50 px-5 py-4 text-sm text-rose-700">
|
||||
<div class="font-semibold">Proses promosi belum bisa dilanjutkan.</div>
|
||||
<ul class="mt-2 list-disc space-y-1 pl-5 text-sm">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</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="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>
|
||||
<div class="rounded-3xl border border-amber-200 bg-amber-50 px-5 py-4">
|
||||
<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>
|
||||
<div class="rounded-3xl border border-sky-200 bg-sky-50 px-5 py-4">
|
||||
<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>
|
||||
</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'] }}">
|
||||
|
||||
<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>
|
||||
<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-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>
|
||||
|
||||
<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">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Siswa</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Posisi Saat Ini</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Rekomendasi</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Aksi Admin</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Kelas Tujuan</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Catatan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 bg-white">
|
||||
@foreach ($students as $student)
|
||||
@php
|
||||
$recommendation = $student->promotion_recommendation;
|
||||
$promoteClasses = $recommendation['available_classes']['promote'];
|
||||
$repeatClasses = $recommendation['available_classes']['repeat'];
|
||||
$actionField = 'promotions.'.$loop->index.'.action';
|
||||
$targetClassField = 'promotions.'.$loop->index.'.target_class_id';
|
||||
$notesField = 'promotions.'.$loop->index.'.notes';
|
||||
$studentField = 'promotions.'.$loop->index.'.student_id';
|
||||
$oldAction = old($actionField, $recommendation['action']);
|
||||
$oldTargetClassId = old($targetClassField, $recommendation['target_class_id']);
|
||||
$defaultClasses = $oldAction === 'repeat' ? $repeatClasses : $promoteClasses;
|
||||
$rowErrors = collect([
|
||||
$errors->first($studentField),
|
||||
$errors->first($actionField),
|
||||
$errors->first($targetClassField),
|
||||
$errors->first($notesField),
|
||||
])->filter();
|
||||
$selectedPromote = old($targetClassField, $promoteClasses->firstWhere('id', $recommendation['target_class_id'])?->id ?? ($promoteClasses->first()?->id ?? ''));
|
||||
$selectedRepeat = old($targetClassField, $repeatClasses->firstWhere('id', $recommendation['target_class_id'])?->id ?? ($repeatClasses->first()?->id ?? ''));
|
||||
$promoteClassOptions = $promoteClasses->map(fn ($classRoom) => [
|
||||
'id' => $classRoom->id,
|
||||
'name' => $classRoom->name,
|
||||
'option_label' => $classRoom->option_label,
|
||||
'capacity_label' => $classRoom->capacity_label,
|
||||
'remaining_capacity' => $classRoom->remaining_capacity,
|
||||
])->values();
|
||||
$repeatClassOptions = $repeatClasses->map(fn ($classRoom) => [
|
||||
'id' => $classRoom->id,
|
||||
'name' => $classRoom->name,
|
||||
'option_label' => $classRoom->option_label,
|
||||
'capacity_label' => $classRoom->capacity_label,
|
||||
'remaining_capacity' => $classRoom->remaining_capacity,
|
||||
])->values();
|
||||
$badgeClass = match ($recommendation['action']) {
|
||||
'promote' => 'bg-emerald-100 text-emerald-700',
|
||||
'repeat' => 'bg-amber-100 text-amber-700',
|
||||
'graduate' => 'bg-sky-100 text-sky-700',
|
||||
default => 'bg-slate-100 text-slate-700',
|
||||
};
|
||||
@endphp
|
||||
<tr class="align-top 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>
|
||||
@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>
|
||||
<ul class="mt-1 list-disc space-y-1 pl-4">
|
||||
@foreach ($rowErrors as $rowError)
|
||||
<li>{{ $rowError }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-4 text-sm text-slate-600">
|
||||
<div class="font-medium text-slate-700">{{ $student->classRoom?->name ?: '-' }}</div>
|
||||
<div class="mt-1">{{ $student->academicYear?->name ?: '-' }}</div>
|
||||
<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>
|
||||
@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'] }}">
|
||||
<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>
|
||||
<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>
|
||||
</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' }}">
|
||||
{{ $recommendation['target_class_name'] ?: 'Aksi ini tidak memerlukan kelas tujuan.' }}
|
||||
</div>
|
||||
<input type="hidden" name="promotions[{{ $loop->index }}][target_class_id]" value="" data-promotion-target-hidden>
|
||||
|
||||
<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"
|
||||
data-options-promote='@json($promoteClassOptions)'
|
||||
data-options-repeat='@json($repeatClassOptions)'
|
||||
data-selected-promote="{{ $selectedPromote }}"
|
||||
data-selected-repeat="{{ $selectedRepeat }}"
|
||||
>
|
||||
<option value="">Pilih kelas tujuan</option>
|
||||
@foreach ($defaultClasses as $classRoom)
|
||||
<option value="{{ $classRoom->id }}" @selected((int) $oldTargetClassId === (int) $classRoom->id)>
|
||||
{{ $classRoom->option_label }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<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>
|
||||
@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>
|
||||
@endforeach
|
||||
</div>
|
||||
@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>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</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="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>
|
||||
<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>
|
||||
</div>
|
||||
</form>
|
||||
@endif
|
||||
</x-master-card>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,94 @@
|
||||
<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-4">
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<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="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>
|
||||
</x-master-card>
|
||||
|
||||
<div class="overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-sm shadow-slate-200/50">
|
||||
<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">Akademik</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Status</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Kode Akses</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Aktivitas</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 bg-white">
|
||||
@forelse ($students as $student)
|
||||
@php
|
||||
$statusMap = [
|
||||
'not_started' => 'bg-slate-100 text-slate-600',
|
||||
'draft' => 'bg-amber-100 text-amber-700',
|
||||
'submitted' => 'bg-emerald-100 text-emerald-700',
|
||||
];
|
||||
@endphp
|
||||
<tr>
|
||||
<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>
|
||||
</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>
|
||||
<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' }}">
|
||||
{{ $statuses[$student->self_service_status] ?? 'Belum Mulai' }}
|
||||
</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="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>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="5" class="px-6 py-10 text-center text-sm text-slate-500">Belum ada data siswa untuk self servis.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
{{ $students->onEachSide(1)->links() }}
|
||||
</div>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,71 @@
|
||||
<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>
|
||||
</form>
|
||||
</x-master-card>
|
||||
|
||||
<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">
|
||||
<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">Akademik Terakhir</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Tanggal Pindah</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Surat Pindah Sekolah</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Catatan</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 ($studentTransfers as $studentTransfer)
|
||||
<tr>
|
||||
<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>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-600">
|
||||
<div>{{ $studentTransfer->academicYear?->name ?: '-' }}</div>
|
||||
<div>{{ $studentTransfer->department?->name ?: '-' }}</div>
|
||||
<div>{{ $studentTransfer->classRoom?->name ?: '-' }}</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>
|
||||
<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>
|
||||
</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>
|
||||
@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">
|
||||
<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>
|
||||
</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>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
{{ $studentTransfers->links() }}
|
||||
</div>
|
||||
</x-master-card>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,38 @@
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</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">
|
||||
</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>
|
||||
@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>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,86 @@
|
||||
<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-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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
</x-master-card>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<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>
|
||||
</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>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('student-transfers.update', $studentTransfer) }}" enctype="multipart/form-data" class="space-y-4">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
|
||||
<x-form-input name="transfer_date" label="Tanggal Pindah" type="date" :value="optional($studentTransfer->transfer_date)->format('Y-m-d')" />
|
||||
<x-form-textarea name="transfer_reason" label="Catatan / Alasan Mutasi" :value="$studentTransfer->transfer_reason" rows="4" placeholder="Masukkan catatan tambahan bila perlu" />
|
||||
|
||||
<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">
|
||||
<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>
|
||||
</form>
|
||||
</div>
|
||||
</x-master-card>
|
||||
</div>
|
||||
</div>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,252 @@
|
||||
<div
|
||||
x-data="studentFormState()"
|
||||
x-init="init()"
|
||||
>
|
||||
<script>
|
||||
function studentFormState() {
|
||||
return {
|
||||
selectedAcademicYear: @js((string) old('academic_year_id', $student->academic_year_id ?? '')),
|
||||
selectedDepartment: @js((string) old('department_id', $student->department_id ?? '')),
|
||||
selectedClass: @js((string) old('class_id', $student->class_id ?? '')),
|
||||
selectedProvince: @js((string) old('province_id', $student->province_id ?? '')),
|
||||
selectedRegency: @js((string) old('regency_id', $student->regency_id ?? '')),
|
||||
selectedDistrict: @js((string) old('district_id', $student->district_id ?? '')),
|
||||
selectedVillage: @js((string) old('village_id', $student->village_id ?? '')),
|
||||
classOptions: @js($classOptions),
|
||||
departments: @js(collect($departments)->map(fn ($name, $id) => ['id' => (string) $id, 'name' => $name])->values()),
|
||||
regencies: [],
|
||||
districts: [],
|
||||
villages: [],
|
||||
loadingRegencies: false,
|
||||
loadingDistricts: false,
|
||||
loadingVillages: false,
|
||||
get filteredDepartments() {
|
||||
if (!this.selectedAcademicYear) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const departmentIds = [...new Set(
|
||||
this.classOptions
|
||||
.filter(item => String(item.academic_year_id) === String(this.selectedAcademicYear))
|
||||
.map(item => String(item.department_id))
|
||||
)];
|
||||
|
||||
return this.departments.filter(item => departmentIds.includes(String(item.id)));
|
||||
},
|
||||
get filteredClasses() {
|
||||
if (!this.selectedAcademicYear || !this.selectedDepartment) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.classOptions.filter(item => {
|
||||
const matchAcademicYear = String(item.academic_year_id) === String(this.selectedAcademicYear);
|
||||
const matchDepartment = String(item.department_id) === String(this.selectedDepartment);
|
||||
return matchAcademicYear && matchDepartment;
|
||||
});
|
||||
},
|
||||
get departmentPlaceholder() {
|
||||
if (!this.selectedAcademicYear) {
|
||||
return 'Pilih Tahun Angkatan dulu';
|
||||
}
|
||||
|
||||
if (this.filteredDepartments.length === 0) {
|
||||
return 'Belum ada Program Keahlian untuk Tahun Angkatan ini';
|
||||
}
|
||||
|
||||
return '-- Pilih Program Keahlian --';
|
||||
},
|
||||
get classPlaceholder() {
|
||||
if (!this.selectedAcademicYear) {
|
||||
return 'Pilih Tahun Angkatan dulu';
|
||||
}
|
||||
|
||||
if (!this.selectedDepartment) {
|
||||
return 'Pilih Program Keahlian dulu';
|
||||
}
|
||||
|
||||
if (this.filteredClasses.length === 0) {
|
||||
return 'Belum ada Kelas untuk kombinasi ini';
|
||||
}
|
||||
|
||||
return '-- Pilih Kelas --';
|
||||
},
|
||||
syncDepartmentSelection() {
|
||||
if (!this.selectedAcademicYear) {
|
||||
this.selectedDepartment = '';
|
||||
this.selectedClass = '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.selectedDepartment) {
|
||||
this.selectedClass = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const exists = this.filteredDepartments.some(item => String(item.id) === String(this.selectedDepartment));
|
||||
if (!exists) {
|
||||
this.selectedDepartment = '';
|
||||
this.selectedClass = '';
|
||||
}
|
||||
},
|
||||
syncClassSelection() {
|
||||
this.syncDepartmentSelection();
|
||||
if (!this.selectedClass) return;
|
||||
const exists = this.filteredClasses.some(item => String(item.id) === String(this.selectedClass));
|
||||
if (!exists) this.selectedClass = '';
|
||||
},
|
||||
async init() {
|
||||
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);
|
||||
},
|
||||
async loadRegencies(reset = true) {
|
||||
if (reset) {
|
||||
this.selectedRegency = '';
|
||||
this.selectedDistrict = '';
|
||||
this.selectedVillage = '';
|
||||
this.districts = [];
|
||||
this.villages = [];
|
||||
}
|
||||
this.regencies = [];
|
||||
if (!this.selectedProvince) return;
|
||||
this.loadingRegencies = true;
|
||||
const response = await fetch(`/regions/provinces/${this.selectedProvince}/regencies`);
|
||||
this.regencies = await response.json();
|
||||
this.loadingRegencies = false;
|
||||
},
|
||||
async loadDistricts(reset = true) {
|
||||
if (reset) {
|
||||
this.selectedDistrict = '';
|
||||
this.selectedVillage = '';
|
||||
this.villages = [];
|
||||
}
|
||||
this.districts = [];
|
||||
if (!this.selectedRegency) return;
|
||||
this.loadingDistricts = true;
|
||||
const response = await fetch(`/regions/regencies/${this.selectedRegency}/districts`);
|
||||
this.districts = await response.json();
|
||||
this.loadingDistricts = false;
|
||||
},
|
||||
async loadVillages(reset = true) {
|
||||
if (reset) this.selectedVillage = '';
|
||||
this.villages = [];
|
||||
if (!this.selectedDistrict) return;
|
||||
this.loadingVillages = true;
|
||||
const response = await fetch(`/regions/districts/${this.selectedDistrict}/villages`);
|
||||
this.villages = await response.json();
|
||||
this.loadingVillages = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</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>
|
||||
<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>
|
||||
</div>
|
||||
</x-master-card>
|
||||
</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>
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
<x-master-page title="Tambah Siswa" description="Tambahkan data siswa baru ke dalam sistem.">
|
||||
<form action="{{ route('students.store') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@include('students._form', ['submitLabel' => 'Simpan'])
|
||||
</form>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,7 @@
|
||||
<x-master-page title="Edit Siswa" description="Perbarui data siswa yang sudah tersimpan.">
|
||||
<form action="{{ route('students.update', $student) }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
@include('students._form', ['submitLabel' => 'Update'])
|
||||
</form>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,176 @@
|
||||
<x-master-page title="Import Data Siswa" description="Import data siswa esensial 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.">
|
||||
<form method="POST" action="{{ route('students.import.preview') }}" enctype="multipart/form-data" class="space-y-6">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<label for="import_file" class="mb-2 block text-sm font-medium text-slate-700">File Excel / CSV</label>
|
||||
<input id="import_file" name="import_file" type="file" accept=".xlsx,.csv,.txt" 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-indigo-50 file:px-4 file:py-2 file:font-semibold file:text-indigo-700 hover:file:bg-indigo-100">
|
||||
<p class="mt-2 text-xs text-slate-500">Format didukung: <strong>.xlsx</strong> dan <strong>.csv</strong>. Untuk paling aman, pakai template yang disediakan lalu isi di Excel.</p>
|
||||
</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 Import
|
||||
</button>
|
||||
<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.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>
|
||||
</div>
|
||||
</form>
|
||||
</x-master-card>
|
||||
|
||||
@if ($preview)
|
||||
<x-master-card title="Preview Import" subtitle="Periksa dulu data yang akan diproses sebelum disimpan ke database.">
|
||||
<div class="mb-6 grid grid-cols-1 gap-4 md:grid-cols-4">
|
||||
<div class="rounded-3xl border border-slate-200 bg-slate-50 px-5 py-4">
|
||||
<div class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-500">Total Baris</div>
|
||||
<div class="mt-2 text-3xl font-bold text-slate-800">{{ $preview['summary']['total_rows'] }}</div>
|
||||
</div>
|
||||
<div class="rounded-3xl border border-emerald-200 bg-emerald-50 px-5 py-4">
|
||||
<div class="text-xs font-semibold uppercase tracking-[0.18em] text-emerald-600">Valid</div>
|
||||
<div class="mt-2 text-3xl font-bold text-emerald-700">{{ $preview['summary']['valid_rows'] }}</div>
|
||||
</div>
|
||||
<div class="rounded-3xl border border-sky-200 bg-sky-50 px-5 py-4">
|
||||
<div class="text-xs font-semibold uppercase tracking-[0.18em] text-sky-600">Insert Baru</div>
|
||||
<div class="mt-2 text-3xl font-bold text-sky-700">{{ $preview['summary']['insert_rows'] }}</div>
|
||||
</div>
|
||||
<div class="rounded-3xl border border-amber-200 bg-amber-50 px-5 py-4">
|
||||
<div class="text-xs font-semibold uppercase tracking-[0.18em] text-amber-600">Update Data</div>
|
||||
<div class="mt-2 text-3xl font-bold text-amber-700">{{ $preview['summary']['update_rows'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!empty($preview['errors']))
|
||||
<div class="mb-6 rounded-2xl border border-rose-200 bg-rose-50 px-5 py-4 text-sm text-rose-700">
|
||||
<div class="font-semibold">Ada baris yang belum valid.</div>
|
||||
<ul class="mt-2 list-disc space-y-1 pl-5">
|
||||
@foreach (array_slice($preview['errors'], 0, 10) as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="overflow-x-auto rounded-3xl border border-slate-200">
|
||||
<table class="min-w-full divide-y divide-slate-200 text-sm">
|
||||
<thead class="bg-slate-50">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Baris</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">NIS / NISN</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Nama</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Akademik</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Aksi</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Status Validasi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 bg-white">
|
||||
@foreach ($preview['rows'] as $row)
|
||||
<tr class="align-top {{ $row['is_valid'] ? '' : 'bg-rose-50/50' }}">
|
||||
<td class="px-4 py-4 font-semibold text-slate-700">{{ $row['row_number'] }}</td>
|
||||
<td class="px-4 py-4 text-slate-600">
|
||||
<div>{{ $row['data']['student_number'] ?: '-' }}</div>
|
||||
<div class="text-xs text-slate-400">NISN: {{ $row['data']['nisn'] ?: '-' }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-4 text-slate-700">
|
||||
<div class="font-medium">{{ $row['data']['full_name'] ?: '-' }}</div>
|
||||
<div class="text-xs text-slate-400">{{ $row['data']['gender'] ?: '-' }} · {{ $row['data']['phone_number'] ?: '-' }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-4 text-slate-600">
|
||||
<div>Tahun: {{ $row['data']['academic_year_id'] ?: '-' }}</div>
|
||||
<div>Jurusan: {{ $row['data']['department_id'] ?: '-' }}</div>
|
||||
<div>Kelas: {{ $row['data']['class_id'] ?: '-' }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-4">
|
||||
<span class="rounded-full px-3 py-1 text-xs font-semibold {{ $row['action'] === 'update' ? 'bg-amber-100 text-amber-700' : 'bg-sky-100 text-sky-700' }}">
|
||||
{{ $row['action'] === 'update' ? 'Update' : 'Insert' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-4 text-sm">
|
||||
@if ($row['is_valid'])
|
||||
<span class="rounded-full bg-emerald-100 px-3 py-1 text-xs font-semibold text-emerald-700">Valid</span>
|
||||
@else
|
||||
<div class="space-y-1 text-rose-700">
|
||||
<div class="inline-flex rounded-full bg-rose-100 px-3 py-1 text-xs font-semibold">Perlu perbaikan</div>
|
||||
<ul class="list-disc pl-4 text-xs">
|
||||
@foreach ($row['errors'] as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if ($preview['summary']['valid_rows'] > 0)
|
||||
<form method="POST" action="{{ route('students.import') }}" 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 focus:outline-none focus:ring-2 focus:ring-emerald-200">
|
||||
Konfirmasi Import ke Database
|
||||
</button>
|
||||
<span class="text-sm text-slate-500">Hanya baris valid yang akan diproses.</span>
|
||||
</form>
|
||||
@endif
|
||||
</x-master-card>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<x-master-card title="Kolom Esensial" subtitle="Kolom inti yang harus ada di file import.">
|
||||
<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>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>
|
||||
</ul>
|
||||
</x-master-card>
|
||||
|
||||
<x-master-card title="Referensi ID Master" subtitle="Pakai ID ini saat mengisi template import.">
|
||||
<div class="space-y-4 text-sm text-slate-600">
|
||||
<div>
|
||||
<div class="mb-2 text-xs font-semibold uppercase tracking-[0.14em] text-slate-500">Tahun Ajaran</div>
|
||||
<div class="space-y-1">
|
||||
@foreach ($academicYears as $id => $label)
|
||||
<div>• <strong>{{ $id }}</strong> — {{ $label }}</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="mb-2 text-xs font-semibold uppercase tracking-[0.14em] text-slate-500">Program Keahlian</div>
|
||||
<div class="space-y-1">
|
||||
@foreach ($departments as $id => $label)
|
||||
<div>• <strong>{{ $id }}</strong> — {{ $label }}</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="mb-2 text-xs font-semibold uppercase tracking-[0.14em] text-slate-500">Kelas</div>
|
||||
<div class="max-h-72 space-y-1 overflow-y-auto pr-2">
|
||||
@foreach ($classes as $classRoom)
|
||||
<div>• <strong>{{ $classRoom->id }}</strong> — {{ $classRoom->name }} <span class="text-slate-400">({{ $classRoom->academicYear?->name }} · {{ $classRoom->department?->name }})</span></div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-master-card>
|
||||
</div>
|
||||
</div>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,210 @@
|
||||
<x-master-page
|
||||
:title="$pageTitle ?? 'Data Siswa'"
|
||||
:description="$pageDescription ?? 'Kelola data siswa lengkap berdasarkan master akademik yang tersedia.'"
|
||||
>
|
||||
@php
|
||||
$isAdmin = auth()->user()?->isAdmin() ?? false;
|
||||
|
||||
$indexRoute = match ($viewMode ?? 'all') {
|
||||
'graduated' => route('students.graduated'),
|
||||
'transferred' => route('students.transferred'),
|
||||
default => route('students.index'),
|
||||
};
|
||||
|
||||
$sortUrl = function (string $column) use ($sort, $direction, $indexRoute) {
|
||||
$nextDirection = $sort === $column && $direction === 'asc' ? 'desc' : 'asc';
|
||||
return $indexRoute . '?' . http_build_query(array_merge(request()->query(), ['sort' => $column, 'direction' => $nextDirection]));
|
||||
};
|
||||
|
||||
$sortIcon = function (string $column) use ($sort, $direction) {
|
||||
if ($sort !== $column) {
|
||||
return '↕';
|
||||
}
|
||||
|
||||
return $direction === 'asc' ? '↑' : '↓';
|
||||
};
|
||||
@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 ($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>
|
||||
</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']" />
|
||||
<x-form-select name="status" label="Status" :options="$statuses" :value="$filters['status']" />
|
||||
|
||||
<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>
|
||||
</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="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>
|
||||
</div>
|
||||
@if ($isAdmin)
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<label class="inline-flex items-center gap-2 text-sm text-slate-600">
|
||||
<input type="checkbox" id="select-all-students" class="rounded border-slate-300 text-indigo-600 focus:ring-indigo-500">
|
||||
<span>Pilih semua di halaman ini</span>
|
||||
</label>
|
||||
<button type="submit" class="inline-flex items-center rounded-2xl border border-rose-200 bg-rose-50 px-5 py-3 text-sm font-semibold text-rose-700 transition hover:bg-rose-100" id="bulk-delete-students-button" disabled>
|
||||
Hapus Banyak Data Siswa
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
{{ $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-x-auto">
|
||||
<table class="min-w-full divide-y divide-slate-200">
|
||||
<thead class="bg-slate-50">
|
||||
<tr>
|
||||
@if ($isAdmin)
|
||||
<th class="px-6 py-4 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">
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 bg-white">
|
||||
@forelse ($students as $student)
|
||||
<tr>
|
||||
@if ($isAdmin)
|
||||
<td class="px-6 py-4 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>
|
||||
</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>
|
||||
<td class="px-6 py-4 text-sm text-slate-600">
|
||||
<div>{{ $student->phone_number ?: '-' }}</div>
|
||||
<div>{{ $student->email ?: '-' }}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="min-w-[180px]">
|
||||
<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>
|
||||
</div>
|
||||
<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">
|
||||
<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') }}">
|
||||
{{ $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">
|
||||
@php
|
||||
$statusMap = [
|
||||
'active' => 'bg-emerald-100 text-emerald-700',
|
||||
'graduated' => 'bg-sky-100 text-sky-700',
|
||||
'transferred' => 'bg-amber-100 text-amber-700',
|
||||
'dropped' => 'bg-rose-100 text-rose-700',
|
||||
'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' }}">
|
||||
{{ $studentStatusLabels[$student->status] ?? $student->status }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<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.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>
|
||||
@if ($isAdmin)
|
||||
<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>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="{{ $isAdmin ? 7 : 6 }}" class="px-6 py-10 text-center text-sm text-slate-500">Belum ada data siswa.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</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">
|
||||
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>
|
||||
{{ $students->onEachSide(1)->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,264 @@
|
||||
<x-master-page title="Detail Siswa" description="Ringkasan lengkap data siswa yang tersimpan di sistem.">
|
||||
@php
|
||||
$isAdmin = auth()->user()?->isAdmin() ?? false;
|
||||
@endphp
|
||||
<div class="mb-6 rounded-3xl border border-slate-200 bg-white p-6 shadow-sm shadow-slate-200/50">
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-slate-900">Kelengkapan Dokumen Wajib</h3>
|
||||
<p class="mt-1 text-sm text-slate-500">Ringkasan upload dan verifikasi dokumen wajib siswa.</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4 text-right">
|
||||
<div>
|
||||
<div class="text-3xl font-bold text-slate-900">{{ $requiredDocumentSummary['percentage'] }}%</div>
|
||||
<div class="text-sm {{ $requiredDocumentSummary['missing'] === 0 ? 'text-emerald-600' : 'text-amber-600' }}">
|
||||
{{ $requiredDocumentSummary['missing'] === 0 ? 'Upload lengkap' : 'Kurang ' . $requiredDocumentSummary['missing'] . ' dokumen' }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-3xl font-bold text-slate-900">{{ $requiredDocumentSummary['verification_percentage'] }}%</div>
|
||||
<div class="text-sm {{ $requiredDocumentSummary['unverified'] === 0 && $requiredDocumentSummary['missing'] === 0 ? 'text-emerald-600' : 'text-sky-600' }}">
|
||||
{{ $requiredDocumentSummary['unverified'] === 0 && $requiredDocumentSummary['missing'] === 0 ? 'Terverifikasi penuh' : $requiredDocumentSummary['unverified'] . ' belum valid' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 space-y-4">
|
||||
<div>
|
||||
<div class="mb-2 flex items-center justify-between text-xs font-semibold uppercase tracking-[0.16em] text-slate-400">
|
||||
<span>Upload Dokumen Wajib</span>
|
||||
<span>{{ $requiredDocumentSummary['uploaded'] }}/{{ $requiredDocumentSummary['total'] }}</span>
|
||||
</div>
|
||||
<div class="h-3 overflow-hidden rounded-full bg-slate-200">
|
||||
<div class="h-full rounded-full {{ $requiredDocumentSummary['missing'] === 0 ? 'bg-emerald-500' : 'bg-amber-500' }}" style="width: {{ $requiredDocumentSummary['percentage'] }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="mb-2 flex items-center justify-between text-xs font-semibold uppercase tracking-[0.16em] text-slate-400">
|
||||
<span>Verifikasi Dokumen Wajib</span>
|
||||
<span>{{ $requiredDocumentSummary['verified'] }}/{{ $requiredDocumentSummary['total'] }}</span>
|
||||
</div>
|
||||
<div class="h-3 overflow-hidden rounded-full bg-slate-200">
|
||||
<div class="h-full rounded-full {{ $requiredDocumentSummary['unverified'] === 0 && $requiredDocumentSummary['missing'] === 0 ? 'bg-sky-500' : 'bg-slate-500' }}" style="width: {{ $requiredDocumentSummary['verification_percentage'] }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($requiredDocumentSummary['missing_items']->isNotEmpty() || $requiredDocumentSummary['unverified_items']->isNotEmpty())
|
||||
<div class="mt-4 flex flex-wrap gap-2">
|
||||
@foreach ($requiredDocumentSummary['missing_items'] as $missingDocument)
|
||||
<span class="rounded-full bg-rose-100 px-3 py-1 text-xs font-semibold text-rose-700">Belum upload: {{ $missingDocument }}</span>
|
||||
@endforeach
|
||||
@foreach ($requiredDocumentSummary['unverified_items'] as $unverifiedDocument)
|
||||
<span class="rounded-full bg-sky-100 px-3 py-1 text-xs font-semibold text-sky-700">Belum valid: {{ $unverifiedDocument }}</span>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</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>
|
||||
</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>
|
||||
</x-master-card>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<x-master-card title="Histori Akademik Siswa" subtitle="Riwayat penempatan siswa per tahun angkatan, jurusan, dan kelas.">
|
||||
@if ($student->academicHistories->isEmpty())
|
||||
<div class="text-sm text-slate-500">Belum ada histori akademik untuk siswa ini.</div>
|
||||
@else
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-slate-200">
|
||||
<thead class="bg-slate-50">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Tahun</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Jurusan</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Kelas</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Status</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold uppercase tracking-[0.16em] text-slate-500">Periode</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 bg-white">
|
||||
@foreach ($student->academicHistories as $history)
|
||||
<tr>
|
||||
<td class="px-4 py-3 text-sm text-slate-700">{{ $history->academicYear?->name ?: '-' }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-600">{{ $history->department?->name ?: '-' }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-600">{{ $history->classRoom?->name ?: '-' }}</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-600">
|
||||
<span class="rounded-full {{ $history->is_current ? 'bg-emerald-100 text-emerald-700' : 'bg-slate-100 text-slate-600' }} px-3 py-1 text-xs font-semibold">
|
||||
{{ $history->is_current ? 'Aktif Sekarang' : ucfirst($history->status) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm text-slate-500">
|
||||
{{ $history->start_date?->format('d-m-Y') ?: '-' }} s/d {{ $history->end_date?->format('d-m-Y') ?: 'Sekarang' }}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</x-master-card>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
@if ($student->parents->isEmpty())
|
||||
<div class="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>
|
||||
<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>
|
||||
@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>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</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>
|
||||
|
||||
@if ($student->documents->isEmpty())
|
||||
<div class="text-sm text-slate-500">Belum ada dokumen siswa yang diunggah.</div>
|
||||
@else
|
||||
<div class="space-y-4">
|
||||
@foreach ($student->documents as $document)
|
||||
@php
|
||||
$verificationMap = [
|
||||
'pending' => 'bg-slate-100 text-slate-600',
|
||||
'valid' => 'bg-emerald-100 text-emerald-700',
|
||||
'invalid' => 'bg-rose-100 text-rose-700',
|
||||
'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>
|
||||
@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>
|
||||
@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>
|
||||
@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>
|
||||
|
||||
@if ($isAdmin)
|
||||
<form action="{{ route('students.documents.quick-verify', [$student, $document]) }}" method="POST" class="inline-flex gap-2">
|
||||
@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>
|
||||
</form>
|
||||
|
||||
<form action="{{ route('students.documents.quick-verify', [$student, $document]) }}" method="POST" class="inline-flex gap-2">
|
||||
@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>
|
||||
</form>
|
||||
|
||||
<form action="{{ route('students.documents.quick-verify', [$student, $document]) }}" method="POST" class="inline-flex gap-2">
|
||||
@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>
|
||||
</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>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</x-master-card>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex flex-wrap items-center gap-3">
|
||||
<a href="{{ route('students.edit', $student) }}" 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">Edit Siswa</a>
|
||||
<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">Kembali</a>
|
||||
</div>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,13 @@
|
||||
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
|
||||
<x-form-input name="name" label="Nama Lengkap" :value="$user->name ?? ''" required />
|
||||
<x-form-input name="email" label="Email" type="email" :value="$user->email ?? ''" required />
|
||||
<x-form-select name="role" label="Role" :options="$roleOptions" :value="$user->role ?? 'staff'" placeholder="-- Pilih role --" required />
|
||||
|
||||
<div class="rounded-3xl border border-slate-200 bg-slate-50 p-5 md:col-span-2">
|
||||
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
|
||||
<x-form-input name="password" label="Password {{ isset($user) ? '(opsional jika tidak diubah)' : '' }}" type="password" />
|
||||
<x-form-input name="password_confirmation" label="Konfirmasi Password" type="password" />
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-slate-500">Password minimal 8 karakter.</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,14 @@
|
||||
<x-master-page title="Tambah Pengguna" description="Buat akun administrator atau staff baru untuk panel Database Kesiswaan.">
|
||||
<form method="POST" action="{{ route('settings.users.store') }}" class="space-y-6">
|
||||
@csrf
|
||||
|
||||
<x-master-card title="Form Pengguna" subtitle="Isi data akun dengan lengkap sebelum disimpan.">
|
||||
@include('users._form')
|
||||
</x-master-card>
|
||||
|
||||
<div class="flex justify-end gap-3">
|
||||
<a href="{{ route('settings.users.index') }}" class="inline-flex items-center rounded-2xl border border-slate-200 bg-white 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 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 Pengguna</button>
|
||||
</div>
|
||||
</form>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,15 @@
|
||||
<x-master-page title="Edit Pengguna" description="Perbarui data akun administrator atau staff.">
|
||||
<form method="POST" action="{{ route('settings.users.update', $user) }}" class="space-y-6">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<x-master-card title="Form Pengguna" subtitle="Kosongkan password jika tidak ingin mengubah password akun ini.">
|
||||
@include('users._form')
|
||||
</x-master-card>
|
||||
|
||||
<div class="flex justify-end gap-3">
|
||||
<a href="{{ route('settings.users.index') }}" class="inline-flex items-center rounded-2xl border border-slate-200 bg-white 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 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">Update Pengguna</button>
|
||||
</div>
|
||||
</form>
|
||||
</x-master-page>
|
||||
@@ -0,0 +1,58 @@
|
||||
<x-master-page title="Manajemen Pengguna" description="Kelola akun administrator dan staff yang dapat mengakses panel Database Kesiswaan." action-label="Tambah Pengguna" :action-url="route('settings.users.create')">
|
||||
<x-master-card title="Daftar Pengguna" subtitle="Gunakan halaman ini untuk menambah, mengubah, dan menghapus akun admin internal.">
|
||||
<form method="GET" class="mb-5">
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-[1fr_auto]">
|
||||
<x-form-input name="search" label="Cari Pengguna" :value="$search" placeholder="Cari nama atau email..." />
|
||||
<div class="flex items-end">
|
||||
<button type="submit" class="inline-flex h-12 items-center justify-center rounded-2xl bg-indigo-600 px-5 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">Cari</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-slate-200 text-sm">
|
||||
<thead class="bg-slate-50">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left font-semibold text-slate-600">Nama</th>
|
||||
<th class="px-4 py-3 text-left font-semibold text-slate-600">Email</th>
|
||||
<th class="px-4 py-3 text-left font-semibold text-slate-600">Role</th>
|
||||
<th class="px-4 py-3 text-left font-semibold text-slate-600">Status</th>
|
||||
<th class="px-4 py-3 text-right font-semibold text-slate-600">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 bg-white">
|
||||
@forelse ($users as $user)
|
||||
<tr>
|
||||
<td class="px-4 py-4 font-medium text-slate-800">{{ $user->name }}</td>
|
||||
<td class="px-4 py-4 text-slate-600">{{ $user->email }}</td>
|
||||
<td class="px-4 py-4">
|
||||
<span class="rounded-full px-3 py-1 text-xs font-semibold {{ $user->role === 'admin' ? 'bg-indigo-100 text-indigo-700' : 'bg-slate-100 text-slate-700' }}">
|
||||
{{ $roleOptions[$user->role] ?? ucfirst($user->role) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-4 text-slate-500">{{ auth()->id() === $user->id ? 'Sedang dipakai login' : 'Aktif' }}</td>
|
||||
<td class="px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<a href="{{ route('settings.users.edit', $user) }}" class="inline-flex items-center 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 method="POST" action="{{ route('settings.users.destroy', $user) }}" onsubmit="return confirm('Hapus akun ini?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="inline-flex items-center 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="5" class="px-4 py-8 text-center text-slate-500">Belum ada data pengguna.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-5">
|
||||
{{ $users->links() }}
|
||||
</div>
|
||||
</x-master-card>
|
||||
</x-master-page>
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user