334 lines
22 KiB
PHP
334 lines
22 KiB
PHP
<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.hydrateSelectionFromClass();
|
|
this.syncDepartmentSelection();
|
|
this.syncClassSelection();
|
|
if (this.selectedProvince) await this.loadRegencies(false);
|
|
if (this.selectedRegency) await this.loadDistricts(false);
|
|
if (this.selectedDistrict) await this.loadVillages(false);
|
|
},
|
|
hydrateSelectionFromClass() {
|
|
if (!this.selectedClass) {
|
|
return;
|
|
}
|
|
|
|
const selectedClassOption = this.classOptions.find(item => String(item.id) === String(this.selectedClass));
|
|
|
|
if (!selectedClassOption) {
|
|
return;
|
|
}
|
|
|
|
if (!this.selectedAcademicYear) {
|
|
this.selectedAcademicYear = String(selectedClassOption.academic_year_id ?? '');
|
|
}
|
|
|
|
if (!this.selectedDepartment) {
|
|
this.selectedDepartment = String(selectedClassOption.department_id ?? '');
|
|
}
|
|
},
|
|
async loadRegencies(reset = true) {
|
|
if (reset) {
|
|
this.selectedRegency = '';
|
|
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-6 grid grid-cols-1 gap-4 xl:grid-cols-[1.15fr_0.85fr]">
|
|
<div class="rounded-3xl border border-slate-200 bg-white p-5 shadow-sm shadow-slate-200/50 ring-1 ring-white/70">
|
|
<div class="flex items-start gap-4">
|
|
<div class="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-indigo-100 text-indigo-600 shadow-sm shadow-indigo-100/60">
|
|
<i class="fa-solid fa-id-card text-lg"></i>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm font-semibold text-slate-800">Panduan Pengisian</div>
|
|
<div class="mt-2 text-sm leading-6 text-slate-600">Isi identitas inti terlebih dahulu, lalu lengkapi relasi akademik dan alamat agar validasi dependensi kelas berjalan mulus.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-1">
|
|
<div class="rounded-3xl border border-emerald-200 bg-gradient-to-br from-white via-emerald-50/60 to-emerald-100/70 p-5 shadow-sm shadow-emerald-100/60 ring-1 ring-white/70">
|
|
<div class="text-sm font-medium text-emerald-700">Mode Form</div>
|
|
<div class="mt-3 text-3xl font-bold text-emerald-800">{{ $formModeLabel ?? 'Create' }}</div>
|
|
<div class="mt-1 text-xs text-slate-500">input data siswa</div>
|
|
</div>
|
|
<div class="rounded-3xl border border-sky-200 bg-gradient-to-br from-white via-sky-50/60 to-sky-100/70 p-5 shadow-sm shadow-sky-100/60 ring-1 ring-white/70">
|
|
<div class="text-sm font-medium text-sky-700">Struktur Form</div>
|
|
<div class="mt-3 text-lg font-bold text-sky-800">3 Area Utama</div>
|
|
<div class="mt-1 text-xs text-slate-500">Identitas, akademik, dan alamat/kontak</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 gap-6 xl:grid-cols-[1.05fr_0.95fr]">
|
|
<div class="space-y-6">
|
|
<x-master-card title="Identitas Siswa" subtitle="Data inti siswa yang wajib rapi sejak awal.">
|
|
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-emerald-50/30 p-5 shadow-sm ring-1 ring-white/70">
|
|
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
|
|
<x-form-input name="student_number" label="NIS" :value="$student->student_number ?? ''" placeholder="Nomor induk siswa" />
|
|
<x-form-input name="nisn" label="NISN" :value="$student->nisn ?? ''" placeholder="Nomor induk siswa nasional" />
|
|
<x-form-input name="nik" label="NIK" :value="$student->nik ?? ''" placeholder="Nomor Induk Kependudukan" />
|
|
<div class="md:col-span-2">
|
|
<x-form-input name="full_name" label="Nama Lengkap" :value="$student->full_name ?? ''" placeholder="Nama lengkap siswa" required />
|
|
</div>
|
|
<x-form-select name="gender" label="Jenis Kelamin" :options="$genders" :value="$student->gender ?? null" required />
|
|
<x-form-select name="status" label="Status Siswa" :options="$statuses" :value="$student->status ?? 'active'" x-ref="statusField" x-on:change="statusValue = $event.target.value" required />
|
|
<x-form-input name="place_of_birth" label="Tempat Lahir" :value="$student->place_of_birth ?? ''" placeholder="Kota kelahiran" />
|
|
<x-form-input name="date_of_birth" label="Tanggal Lahir" type="date" :value="isset($student?->date_of_birth) ? $student->date_of_birth?->format('Y-m-d') : ''" />
|
|
<x-form-select name="religion" label="Agama" :options="$religions" :value="$student->religion ?? null" />
|
|
<x-form-input name="nationality" label="Kewarganegaraan" :value="$student->nationality ?? 'Indonesia'" placeholder="Contoh: Indonesia" />
|
|
<x-form-input name="family_card_number" label="No. Kartu Keluarga" :value="$student->family_card_number ?? ''" placeholder="Nomor KK" />
|
|
<x-form-input name="entry_date" label="Tanggal Masuk" type="date" :value="isset($student?->entry_date) ? $student->entry_date?->format('Y-m-d') : ''" />
|
|
<x-form-input name="child_order" label="Anak Ke" type="number" :value="$student->child_order ?? ''" placeholder="Contoh: 1" />
|
|
<x-form-input name="number_of_siblings" label="Jumlah Saudara" type="number" :value="$student->number_of_siblings ?? ''" placeholder="Contoh: 2" />
|
|
</div>
|
|
</div>
|
|
</x-master-card>
|
|
|
|
<x-master-card title="Akademik Siswa" subtitle="Pastikan relasi angkatan, jurusan, dan kelas konsisten.">
|
|
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-sky-50/30 p-5 shadow-sm ring-1 ring-white/70">
|
|
<div class="mb-4 rounded-2xl border border-sky-100 bg-sky-50 px-4 py-3 text-xs leading-6 text-sky-700">
|
|
Program Keahlian dan Kelas akan menyesuaikan otomatis berdasarkan Tahun Angkatan yang dipilih.
|
|
</div>
|
|
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
|
|
<x-form-select name="academic_year_id" label="Tahun Angkatan" :options="$academicYears->all()" :value="$student->academic_year_id ?? null" x-model="selectedAcademicYear" x-on:change="syncDepartmentSelection(); syncClassSelection()" required />
|
|
|
|
<div>
|
|
<label for="department_id" class="mb-2 block text-sm font-medium text-slate-700">Program Keahlian</label>
|
|
<select id="department_id" name="department_id" x-model="selectedDepartment" x-on:change="syncClassSelection()" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100 disabled:cursor-not-allowed disabled:bg-slate-100 disabled:text-slate-400" :disabled="!selectedAcademicYear || filteredDepartments.length === 0" required>
|
|
<option value="" x-text="departmentPlaceholder"></option>
|
|
@foreach ($departments as $departmentId => $departmentName)
|
|
<option
|
|
value="{{ $departmentId }}"
|
|
x-show="filteredDepartments.some(item => String(item.id) === '{{ (string) $departmentId }}')"
|
|
:selected="String(selectedDepartment) === '{{ (string) $departmentId }}'"
|
|
>{{ $departmentName }}</option>
|
|
@endforeach
|
|
</select>
|
|
<p class="mt-2 text-xs text-slate-500" x-text="!selectedAcademicYear ? 'Pilih Tahun Angkatan terlebih dahulu untuk membuka daftar Program Keahlian.' : (filteredDepartments.length === 0 ? 'Belum ada kelas yang menghubungkan Tahun Angkatan ini dengan Program Keahlian mana pun.' : 'Program Keahlian difilter otomatis berdasarkan Tahun Angkatan yang dipilih.')"></p>
|
|
<x-input-error :messages="$errors->get('department_id')" class="mt-2" />
|
|
</div>
|
|
|
|
<div class="md:col-span-2">
|
|
<label for="class_id" class="mb-2 block text-sm font-medium text-slate-700">Kelas</label>
|
|
<select id="class_id" name="class_id" x-model="selectedClass" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100 disabled:cursor-not-allowed disabled:bg-slate-100 disabled:text-slate-400" :disabled="!selectedAcademicYear || !selectedDepartment || filteredClasses.length === 0" required>
|
|
<option value="" x-text="classPlaceholder"></option>
|
|
@foreach ($classOptions as $classOption)
|
|
<option
|
|
value="{{ $classOption['id'] }}"
|
|
x-show="filteredClasses.some(item => String(item.id) === '{{ (string) $classOption['id'] }}')"
|
|
:selected="String(selectedClass) === '{{ (string) $classOption['id'] }}'"
|
|
>{{ $classOption['name'] }}</option>
|
|
@endforeach
|
|
</select>
|
|
<p class="mt-2 text-xs text-slate-500" x-text="!selectedAcademicYear ? 'Pilih Tahun Angkatan terlebih dahulu.' : (!selectedDepartment ? 'Pilih Program Keahlian terlebih dahulu.' : (filteredClasses.length === 0 ? 'Belum ada kelas aktif untuk kombinasi Tahun Angkatan dan Program Keahlian ini.' : 'Hanya kelas yang cocok dengan kombinasi pilihan yang ditampilkan.'))"></p>
|
|
<x-input-error :messages="$errors->get('class_id')" class="mt-2" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</x-master-card>
|
|
</div>
|
|
|
|
<div class="space-y-6">
|
|
<x-master-card title="Kontak & Domisili" subtitle="Simpan informasi yang paling sering dipakai saat follow-up siswa.">
|
|
<div class="rounded-3xl border border-slate-200 bg-gradient-to-br from-white via-slate-50 to-amber-50/30 p-5 shadow-sm ring-1 ring-white/70">
|
|
<div class="grid grid-cols-1 gap-5 md:grid-cols-2">
|
|
<x-form-input name="phone_number" label="No. HP" :value="$student->phone_number ?? ''" placeholder="Nomor HP siswa" />
|
|
<x-form-input name="email" label="Email" type="email" :value="$student->email ?? ''" placeholder="Alamat email" />
|
|
|
|
<div>
|
|
<label for="province_id" class="mb-2 block text-sm font-medium text-slate-700">Provinsi</label>
|
|
<select id="province_id" name="province_id" x-model="selectedProvince" @change="loadRegencies()" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100">
|
|
<option value="">-- Pilih Provinsi --</option>
|
|
@foreach($provinces as $id => $name)
|
|
<option value="{{ $id }}">{{ $name }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="regency_id" class="mb-2 block text-sm font-medium text-slate-700">Kota/Kabupaten</label>
|
|
<select id="regency_id" name="regency_id" x-model="selectedRegency" @change="loadDistricts()" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100">
|
|
<option value="" x-text="loadingRegencies ? 'Memuat...' : '-- Pilih Kota/Kabupaten --'"></option>
|
|
<template x-for="regency in regencies" :key="regency.id">
|
|
<option :value="regency.id" x-text="regency.name"></option>
|
|
</template>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="district_id" class="mb-2 block text-sm font-medium text-slate-700">Kecamatan</label>
|
|
<select id="district_id" name="district_id" x-model="selectedDistrict" @change="loadVillages()" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100">
|
|
<option value="" x-text="loadingDistricts ? 'Memuat...' : '-- Pilih Kecamatan --'"></option>
|
|
<template x-for="district in districts" :key="district.id">
|
|
<option :value="district.id" x-text="district.name"></option>
|
|
</template>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="village_id" class="mb-2 block text-sm font-medium text-slate-700">Desa/Kelurahan</label>
|
|
<select id="village_id" name="village_id" x-model="selectedVillage" class="h-12 w-full rounded-2xl border border-slate-200 bg-white px-4 text-sm text-slate-700 focus:border-indigo-300 focus:outline-none focus:ring-4 focus:ring-indigo-100">
|
|
<option value="" x-text="loadingVillages ? 'Memuat...' : '-- Pilih Desa/Kelurahan --'"></option>
|
|
<template x-for="village in villages" :key="village.id">
|
|
<option :value="village.id" x-text="village.name"></option>
|
|
</template>
|
|
</select>
|
|
</div>
|
|
|
|
<x-form-input name="postal_code" label="Kode Pos" :value="$student->postal_code ?? ''" placeholder="Kode pos" />
|
|
<div class="md:col-span-2">
|
|
<x-form-textarea name="address" label="Alamat" :value="$student->address ?? ''" placeholder="Alamat lengkap siswa" rows="4" />
|
|
</div>
|
|
<div class="md:col-span-2">
|
|
<x-form-textarea name="notes" label="Catatan" :value="$student->notes ?? ''" placeholder="Catatan tambahan terkait siswa" rows="4" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</x-master-card>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6 flex flex-col gap-3 rounded-3xl border border-slate-200 bg-white px-5 py-4 shadow-sm shadow-slate-200/50 ring-1 ring-white/70 lg:flex-row lg:items-center lg:justify-between">
|
|
<div class="text-sm text-slate-600">
|
|
<div class="font-semibold text-slate-800">Siap simpan data siswa?</div>
|
|
<div class="mt-1 text-xs text-slate-500">Pastikan identitas inti dan relasi akademik sudah sesuai sebelum menyimpan perubahan.</div>
|
|
</div>
|
|
<div class="flex flex-wrap items-center gap-3">
|
|
<a href="{{ route('students.index') }}" class="rounded-2xl border border-slate-200 px-5 py-3 text-sm font-semibold text-slate-700 transition hover:bg-slate-50">Batal</a>
|
|
<button type="submit" class="inline-flex items-center gap-2 rounded-2xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-200 transition hover:bg-indigo-700">
|
|
<i class="fa-solid fa-floppy-disk"></i>
|
|
<span>{{ $submitLabel }}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|