Initial commit
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user