Initial commit

This commit is contained in:
2026-04-12 06:24:30 +00:00
commit 007f107be6
213 changed files with 25576 additions and 0 deletions
+252
View File
@@ -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>