Improve document type cleanup and student import reliability
This commit is contained in:
@@ -3,9 +3,11 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\DocumentType;
|
||||
use App\Models\StudentDocument;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\View\View;
|
||||
use RealRashid\SweetAlert\Facades\Alert;
|
||||
@@ -17,6 +19,9 @@ class DocumentTypeController extends Controller
|
||||
$search = trim((string) $request->get('search'));
|
||||
|
||||
$documentTypes = DocumentType::withCount('studentDocuments')
|
||||
->withCount([
|
||||
'studentDocuments as student_documents_with_trashed_count' => fn ($query) => $query->withTrashed(),
|
||||
])
|
||||
->when($search !== '', function ($query) use ($search) {
|
||||
$query->where('code', 'like', "%{$search}%")
|
||||
->orWhere('name', 'like', "%{$search}%")
|
||||
@@ -93,23 +98,67 @@ class DocumentTypeController extends Controller
|
||||
|
||||
public function destroy(DocumentType $documentType): RedirectResponse
|
||||
{
|
||||
$documentType->loadCount('studentDocuments');
|
||||
$documentType->loadCount([
|
||||
'studentDocuments',
|
||||
'studentDocuments as student_documents_with_trashed_count' => fn ($query) => $query->withTrashed(),
|
||||
]);
|
||||
|
||||
if ($documentType->student_documents_count > 0) {
|
||||
Alert::error('Tidak bisa dihapus', 'Jenis dokumen ini masih digunakan oleh data dokumen siswa.');
|
||||
Alert::error('Tidak bisa dihapus', 'Jenis dokumen ini masih digunakan oleh data dokumen siswa aktif.');
|
||||
|
||||
return redirect()->route('document-types.index')->withErrors([
|
||||
'delete' => "Jenis dokumen '{$documentType->name}' tidak bisa dihapus karena masih digunakan oleh {$documentType->student_documents_count} dokumen siswa.",
|
||||
'delete' => "Jenis dokumen '{$documentType->name}' tidak bisa dihapus karena masih digunakan oleh {$documentType->student_documents_count} dokumen siswa aktif.",
|
||||
]);
|
||||
}
|
||||
|
||||
$trashedDocumentsCount = $documentType->student_documents_with_trashed_count - $documentType->student_documents_count;
|
||||
|
||||
if ($trashedDocumentsCount > 0) {
|
||||
Alert::error('Tidak bisa dihapus', 'Jenis dokumen ini masih dipakai oleh dokumen siswa yang sudah diarsipkan.');
|
||||
|
||||
return redirect()->route('document-types.index')->withErrors([
|
||||
'delete' => "Jenis dokumen '{$documentType->name}' tidak bisa dihapus karena masih direferensikan oleh {$trashedDocumentsCount} dokumen siswa yang sudah diarsipkan. Bersihkan arsip dokumen terkait terlebih dahulu atau gunakan aksi bersihkan arsip dari modul ini.",
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->deleteDocumentType($documentType);
|
||||
}
|
||||
|
||||
public function purgeArchivedDocuments(DocumentType $documentType): RedirectResponse
|
||||
{
|
||||
$trashedDocuments = StudentDocument::withTrashed()
|
||||
->where('document_type_id', $documentType->id)
|
||||
->whereNotNull('deleted_at')
|
||||
->get();
|
||||
|
||||
if ($trashedDocuments->isEmpty()) {
|
||||
Alert::info('Tidak ada arsip', 'Tidak ditemukan dokumen siswa arsip untuk jenis dokumen ini.');
|
||||
|
||||
return $this->deleteDocumentType($documentType);
|
||||
}
|
||||
|
||||
foreach ($trashedDocuments as $document) {
|
||||
if ($document->file_path && Storage::disk('public')->exists($document->file_path)) {
|
||||
Storage::disk('public')->delete($document->file_path);
|
||||
}
|
||||
|
||||
$document->forceDelete();
|
||||
}
|
||||
|
||||
Alert::success('Arsip dibersihkan', 'Arsip dokumen siswa yang terkait berhasil dibersihkan.');
|
||||
|
||||
return $this->deleteDocumentType($documentType);
|
||||
}
|
||||
|
||||
protected function deleteDocumentType(DocumentType $documentType): RedirectResponse
|
||||
{
|
||||
try {
|
||||
$documentType->delete();
|
||||
} catch (QueryException $exception) {
|
||||
Alert::error('Tidak bisa dihapus', 'Jenis dokumen ini masih direferensikan oleh data lain di sistem.');
|
||||
|
||||
return redirect()->route('document-types.index')->withErrors([
|
||||
'delete' => "Jenis dokumen '{$documentType->name}' tidak bisa dihapus karena masih direferensikan oleh data lain di sistem. Untuk kasus ini, cek dokumen siswa yang masih memakai jenis dokumen tersebut terlebih dahulu.",
|
||||
'delete' => "Jenis dokumen '{$documentType->name}' tidak bisa dihapus karena masih direferensikan oleh data lain di sistem. Cek relasi data lain yang masih memakai jenis dokumen ini terlebih dahulu.",
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -342,8 +342,15 @@ class StudentController extends Controller
|
||||
{
|
||||
$this->ensureAdmin();
|
||||
|
||||
return view('students.import', array_merge($this->importReferenceData(), [
|
||||
'preview' => $request->session()->get('student_import_preview'),
|
||||
$referenceData = $this->importReferenceData();
|
||||
$preview = $request->session()->get('student_import_preview');
|
||||
|
||||
if ($preview) {
|
||||
$preview = $this->hydrateImportPreviewDisplay($preview, $referenceData);
|
||||
}
|
||||
|
||||
return view('students.import', array_merge($referenceData, [
|
||||
'preview' => $preview,
|
||||
]));
|
||||
}
|
||||
|
||||
@@ -432,6 +439,8 @@ class StudentController extends Controller
|
||||
'notes',
|
||||
]);
|
||||
|
||||
$payload = $this->hydrateRegionNamesFromIds($payload);
|
||||
|
||||
$lookup = null;
|
||||
|
||||
if (! empty($payload['student_number'])) {
|
||||
@@ -447,8 +456,14 @@ class StudentController extends Controller
|
||||
$lookup->restore();
|
||||
}
|
||||
|
||||
$lookup->update($payload);
|
||||
$this->syncAcademicHistory($lookup, $payload);
|
||||
$updatePayload = $this->buildStudentImportUpdatePayload($lookup, $payload);
|
||||
$historyPayload = $this->buildStudentImportHistoryPayload($lookup, $payload);
|
||||
|
||||
$lookup->update($updatePayload);
|
||||
$this->syncAcademicHistory($lookup, $historyPayload);
|
||||
$this->syncExitRecord($lookup, $historyPayload);
|
||||
$this->syncTransferRecord($lookup, $historyPayload);
|
||||
$this->syncGraduationRecord($lookup, $historyPayload);
|
||||
$this->syncImportedParents($lookup, $row['normalized']['parents'] ?? []);
|
||||
$updated++;
|
||||
continue;
|
||||
@@ -456,6 +471,9 @@ class StudentController extends Controller
|
||||
|
||||
$student = Student::create($payload);
|
||||
$this->syncAcademicHistory($student, $payload);
|
||||
$this->syncExitRecord($student, $payload);
|
||||
$this->syncTransferRecord($student, $payload);
|
||||
$this->syncGraduationRecord($student, $payload);
|
||||
$this->syncImportedParents($student, $row['normalized']['parents'] ?? []);
|
||||
$inserted++;
|
||||
}
|
||||
@@ -804,14 +822,92 @@ class StudentController extends Controller
|
||||
|
||||
protected function importReferenceData(): array
|
||||
{
|
||||
$academicYears = AcademicYear::where('is_active', true)->orderBy('name')->pluck('name', 'id');
|
||||
$departments = Department::where('is_active', true)->orderBy('name')->pluck('name', 'id');
|
||||
$classes = SchoolClass::with(['academicYear', 'department'])->where('is_active', true)->orderBy('name')->get();
|
||||
|
||||
return [
|
||||
'academicYears' => AcademicYear::where('is_active', true)->orderBy('name')->pluck('name', 'id'),
|
||||
'departments' => Department::where('is_active', true)->orderBy('name')->pluck('name', 'id'),
|
||||
'classes' => SchoolClass::with(['academicYear', 'department'])->where('is_active', true)->orderBy('name')->get(),
|
||||
'academicYears' => $academicYears,
|
||||
'departments' => $departments,
|
||||
'classes' => $classes,
|
||||
'statuses' => $this->statusOptions(),
|
||||
'academicYearsById' => $academicYears->all(),
|
||||
'departmentsById' => $departments->all(),
|
||||
'classesById' => $classes->keyBy('id')->map(fn ($classRoom) => [
|
||||
'name' => $classRoom->name,
|
||||
'academic_year' => $classRoom->academicYear?->name,
|
||||
'department' => $classRoom->department?->name,
|
||||
])->all(),
|
||||
];
|
||||
}
|
||||
|
||||
protected function hydrateRegionNamesFromIds(array $payload): array
|
||||
{
|
||||
$payload['province'] = ! empty($payload['province_id']) ? Province::where('id', $payload['province_id'])->value('name') : null;
|
||||
$payload['city'] = ! empty($payload['regency_id']) ? Regency::where('id', $payload['regency_id'])->value('name') : null;
|
||||
$payload['district'] = ! empty($payload['district_id']) ? District::where('id', $payload['district_id'])->value('name') : null;
|
||||
$payload['village'] = ! empty($payload['village_id']) ? Village::where('id', $payload['village_id'])->value('name') : null;
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
protected function buildStudentImportUpdatePayload(Student $student, array $payload): array
|
||||
{
|
||||
$preserveWhenBlank = [
|
||||
'student_number',
|
||||
'nisn',
|
||||
'nik',
|
||||
'place_of_birth',
|
||||
'date_of_birth',
|
||||
'religion',
|
||||
'nationality',
|
||||
'family_card_number',
|
||||
'child_order',
|
||||
'number_of_siblings',
|
||||
'phone_number',
|
||||
'email',
|
||||
'address',
|
||||
'province_id',
|
||||
'regency_id',
|
||||
'district_id',
|
||||
'village_id',
|
||||
'province',
|
||||
'city',
|
||||
'district',
|
||||
'village',
|
||||
'postal_code',
|
||||
'entry_date',
|
||||
'notes',
|
||||
];
|
||||
|
||||
foreach ($preserveWhenBlank as $field) {
|
||||
if (array_key_exists($field, $payload) && blank($payload[$field])) {
|
||||
$payload[$field] = $student->{$field};
|
||||
}
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
protected function buildStudentImportHistoryPayload(Student $student, array $payload): array
|
||||
{
|
||||
$historyPayload = Arr::only($payload, [
|
||||
'academic_year_id',
|
||||
'department_id',
|
||||
'class_id',
|
||||
'status',
|
||||
'entry_date',
|
||||
]);
|
||||
|
||||
foreach (['academic_year_id', 'department_id', 'class_id', 'status', 'entry_date'] as $field) {
|
||||
if (! array_key_exists($field, $historyPayload) || blank($historyPayload[$field])) {
|
||||
$historyPayload[$field] = $student->{$field};
|
||||
}
|
||||
}
|
||||
|
||||
return $historyPayload;
|
||||
}
|
||||
|
||||
protected function statusOptions(): array
|
||||
{
|
||||
return [
|
||||
@@ -1241,6 +1337,40 @@ class StudentController extends Controller
|
||||
];
|
||||
}
|
||||
|
||||
protected function hydrateImportPreviewDisplay(array $preview, array $referenceData): array
|
||||
{
|
||||
$academicYearsById = $referenceData['academicYearsById'] ?? [];
|
||||
$departmentsById = $referenceData['departmentsById'] ?? [];
|
||||
$classesById = $referenceData['classesById'] ?? [];
|
||||
$statuses = $referenceData['statuses'] ?? [];
|
||||
|
||||
$preview['rows'] = array_map(function (array $row) use ($academicYearsById, $departmentsById, $classesById, $statuses) {
|
||||
$academicYearId = $row['data']['academic_year_id'] ?? null;
|
||||
$departmentId = $row['data']['department_id'] ?? null;
|
||||
$classId = $row['data']['class_id'] ?? null;
|
||||
$status = $row['data']['status'] ?? null;
|
||||
$classMeta = $classId ? ($classesById[$classId] ?? null) : null;
|
||||
|
||||
$row['display'] = [
|
||||
'academic_year' => $academicYearId ? ($academicYearsById[$academicYearId] ?? 'ID ' . $academicYearId) : '-',
|
||||
'department' => $departmentId ? ($departmentsById[$departmentId] ?? 'ID ' . $departmentId) : '-',
|
||||
'class' => $classMeta['name'] ?? ($classId ? 'ID ' . $classId : '-'),
|
||||
'class_context' => $classMeta
|
||||
? collect([$classMeta['academic_year'] ?? null, $classMeta['department'] ?? null])->filter()->implode(' · ')
|
||||
: null,
|
||||
'status' => $status ? ($statuses[$status] ?? strtoupper((string) $status)) : '-',
|
||||
'province' => $row['data']['province'] ?? '-',
|
||||
'city' => $row['data']['city'] ?? '-',
|
||||
'district' => $row['data']['district'] ?? '-',
|
||||
'village' => $row['data']['village'] ?? '-',
|
||||
];
|
||||
|
||||
return $row;
|
||||
}, $preview['rows'] ?? []);
|
||||
|
||||
return $preview;
|
||||
}
|
||||
|
||||
protected function normalizeImportRow(array $row): array
|
||||
{
|
||||
$normalized = [];
|
||||
@@ -1292,6 +1422,10 @@ class StudentController extends Controller
|
||||
$regencyId = $this->resolveRegencyId($regencySource, $provinceId);
|
||||
$districtId = $this->resolveDistrictId($districtSource, $regencyId);
|
||||
$villageId = $this->resolveVillageId($villageSource, $districtId);
|
||||
$provinceName = $provinceId ? Province::where('id', $provinceId)->value('name') : null;
|
||||
$regencyName = $regencyId ? Regency::where('id', $regencyId)->value('name') : null;
|
||||
$districtName = $districtId ? District::where('id', $districtId)->value('name') : null;
|
||||
$villageName = $villageId ? Village::where('id', $villageId)->value('name') : null;
|
||||
|
||||
return [
|
||||
'student_number' => $studentNumber,
|
||||
@@ -1313,10 +1447,10 @@ class StudentController extends Controller
|
||||
'regency_id' => $regencyId,
|
||||
'district_id' => $districtId,
|
||||
'village_id' => $villageId,
|
||||
'province' => $provinceId ? Province::where('id', $provinceId)->value('name') : null,
|
||||
'city' => $regencyId ? Regency::where('id', $regencyId)->value('name') : null,
|
||||
'district' => $districtId ? District::where('id', $districtId)->value('name') : null,
|
||||
'village' => $villageId ? Village::where('id', $villageId)->value('name') : null,
|
||||
'province' => $provinceName,
|
||||
'city' => $regencyName,
|
||||
'district' => $districtName,
|
||||
'village' => $villageName,
|
||||
'postal_code' => $postalCode,
|
||||
'academic_year_id' => $academicYearId,
|
||||
'department_id' => $departmentId,
|
||||
@@ -1355,7 +1489,12 @@ class StudentController extends Controller
|
||||
return $value;
|
||||
}
|
||||
|
||||
return Department::where('name', trim((string) $value))->value('id');
|
||||
$normalizedValue = trim((string) $value);
|
||||
|
||||
return Department::query()
|
||||
->where('name', $normalizedValue)
|
||||
->orWhere('code', strtoupper($normalizedValue))
|
||||
->value('id');
|
||||
}
|
||||
|
||||
protected function resolveClassId(mixed $value, mixed $academicYearId = null, mixed $departmentId = null): mixed
|
||||
@@ -1385,7 +1524,12 @@ class StudentController extends Controller
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
return Province::where('name', trim((string) $value))->value('id');
|
||||
$normalizedValue = $this->normalizeRegionLookupValue((string) $value);
|
||||
|
||||
return Province::query()
|
||||
->get(['id', 'name'])
|
||||
->first(fn ($province) => $this->normalizeRegionLookupValue($province->name) === $normalizedValue)
|
||||
?->id;
|
||||
}
|
||||
|
||||
protected function resolveRegencyId(mixed $value, mixed $provinceId = null): mixed
|
||||
@@ -1398,10 +1542,19 @@ class StudentController extends Controller
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
$normalizedValue = $this->normalizeRegionLookupValue((string) $value);
|
||||
|
||||
return Regency::query()
|
||||
->when($provinceId, fn ($query) => $query->where('province_id', $provinceId))
|
||||
->where('name', trim((string) $value))
|
||||
->value('id');
|
||||
->get(['id', 'name'])
|
||||
->first(function ($regency) use ($normalizedValue) {
|
||||
$candidate = $this->normalizeRegionLookupValue($regency->name);
|
||||
|
||||
return $candidate === $normalizedValue
|
||||
|| $candidate === 'KABUPATEN ' . $normalizedValue
|
||||
|| $candidate === 'KOTA ' . $normalizedValue;
|
||||
})
|
||||
?->id;
|
||||
}
|
||||
|
||||
protected function resolveDistrictId(mixed $value, mixed $regencyId = null): mixed
|
||||
@@ -1414,10 +1567,13 @@ class StudentController extends Controller
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
$normalizedValue = $this->normalizeRegionLookupValue((string) $value);
|
||||
|
||||
return District::query()
|
||||
->when($regencyId, fn ($query) => $query->where('regency_id', $regencyId))
|
||||
->where('name', trim((string) $value))
|
||||
->value('id');
|
||||
->get(['id', 'name'])
|
||||
->first(fn ($district) => $this->normalizeRegionLookupValue($district->name) === $normalizedValue)
|
||||
?->id;
|
||||
}
|
||||
|
||||
protected function resolveVillageId(mixed $value, mixed $districtId = null): mixed
|
||||
@@ -1430,10 +1586,22 @@ class StudentController extends Controller
|
||||
return (string) $value;
|
||||
}
|
||||
|
||||
$normalizedValue = $this->normalizeRegionLookupValue((string) $value);
|
||||
|
||||
return Village::query()
|
||||
->when($districtId, fn ($query) => $query->where('district_id', $districtId))
|
||||
->where('name', trim((string) $value))
|
||||
->value('id');
|
||||
->get(['id', 'name'])
|
||||
->first(fn ($village) => $this->normalizeRegionLookupValue($village->name) === $normalizedValue)
|
||||
?->id;
|
||||
}
|
||||
|
||||
protected function normalizeRegionLookupValue(string $value): string
|
||||
{
|
||||
$normalized = strtoupper(trim($value));
|
||||
$normalized = preg_replace('/\s+/', ' ', $normalized) ?? $normalized;
|
||||
$normalized = str_replace(['KAB.', 'KABUPATEN.', 'KOTA.'], ['KABUPATEN', 'KABUPATEN', 'KOTA'], $normalized);
|
||||
|
||||
return trim($normalized);
|
||||
}
|
||||
|
||||
protected function normalizeImportDate(mixed $value): mixed
|
||||
@@ -1463,13 +1631,68 @@ class StudentController extends Controller
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_int($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_float($value)) {
|
||||
return (int) round($value);
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return (int) $value;
|
||||
return (int) round((float) $value);
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
$normalized = trim($value);
|
||||
$normalized = str_replace([',', ' '], '', $normalized);
|
||||
|
||||
if (preg_match('/^\d+(?:\.0+)?$/', $normalized) === 1) {
|
||||
return (int) round((float) $normalized);
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected function normalizeImportYear(mixed $value): mixed
|
||||
{
|
||||
$value = $this->nullIfBlank($value);
|
||||
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($value instanceof \DateTimeInterface) {
|
||||
return (int) $value->format('Y');
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
$normalized = trim($value);
|
||||
|
||||
if (preg_match('/^(\d{4})[-\/]/', $normalized, $matches) === 1) {
|
||||
return (int) $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
$numericValue = (float) $value;
|
||||
$intValue = (int) round($numericValue);
|
||||
|
||||
if ($intValue >= 1900 && $intValue <= now()->year) {
|
||||
return $intValue;
|
||||
}
|
||||
|
||||
try {
|
||||
return (int) \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($numericValue)->format('Y');
|
||||
} catch (\Throwable) {
|
||||
return $intValue;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->normalizeImportInteger($value);
|
||||
}
|
||||
|
||||
protected function normalizeImportBoolean(mixed $value): ?bool
|
||||
{
|
||||
$value = $this->nullIfBlank($value);
|
||||
@@ -1496,7 +1719,7 @@ class StudentController extends Controller
|
||||
return [
|
||||
'name' => $this->nullIfBlank($normalized["parent_{$relationKey}_name"] ?? ($normalized["nama_{$legacyPrefix}"] ?? null)),
|
||||
'nik' => $this->nullIfBlank($normalized["parent_{$relationKey}_nik"] ?? ($normalized["nik_{$legacyPrefix}"] ?? null)),
|
||||
'birth_year' => $this->normalizeImportInteger($normalized["parent_{$relationKey}_birth_year"] ?? ($normalized["tahun_lahir_{$legacyPrefix}"] ?? null)),
|
||||
'birth_year' => $this->normalizeImportYear($normalized["parent_{$relationKey}_birth_year"] ?? ($normalized["tahun_lahir_{$legacyPrefix}"] ?? null)),
|
||||
'education' => $this->nullIfBlank($normalized["parent_{$relationKey}_education"] ?? ($normalized["pendidikan_{$legacyPrefix}"] ?? null)),
|
||||
'occupation' => $this->nullIfBlank($normalized["parent_{$relationKey}_occupation"] ?? ($normalized["pekerjaan_{$legacyPrefix}"] ?? null)),
|
||||
'phone_number' => $this->nullIfBlank($normalized["parent_{$relationKey}_phone_number"] ?? ($normalized["no_hp_{$legacyPrefix}"] ?? null)),
|
||||
@@ -1662,7 +1885,13 @@ class StudentController extends Controller
|
||||
}
|
||||
|
||||
ksort($values);
|
||||
$ordered = array_values($values);
|
||||
|
||||
$maxIndex = max(array_keys($values));
|
||||
$ordered = [];
|
||||
|
||||
for ($index = 0; $index <= $maxIndex; $index++) {
|
||||
$ordered[$index] = $values[$index] ?? null;
|
||||
}
|
||||
|
||||
if ($headers === null) {
|
||||
$headers = array_map(fn ($header) => trim((string) $header), $ordered);
|
||||
|
||||
+8
-4
@@ -14,15 +14,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
event.preventDefault();
|
||||
|
||||
const entity = form.dataset.entity || 'data ini';
|
||||
const title = form.dataset.confirmTitle || 'Hapus data?';
|
||||
const text = form.dataset.confirmText || `Yakin ingin menghapus ${entity}? Tindakan ini tidak bisa dibatalkan.`;
|
||||
const confirmButtonColor = form.dataset.confirmButtonColor || '#e11d48';
|
||||
const confirmButtonText = form.dataset.confirmButtonText || 'Ya, hapus';
|
||||
|
||||
Swal.fire({
|
||||
title: 'Hapus data?',
|
||||
text: `Yakin ingin menghapus ${entity}? Tindakan ini tidak bisa dibatalkan.`,
|
||||
title,
|
||||
text,
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#e11d48',
|
||||
confirmButtonColor,
|
||||
cancelButtonColor: '#475569',
|
||||
confirmButtonText: 'Ya, hapus',
|
||||
confirmButtonText,
|
||||
cancelButtonText: 'Batal',
|
||||
reverseButtons: true,
|
||||
}).then((result) => {
|
||||
|
||||
@@ -31,7 +31,13 @@
|
||||
{{ $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>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="rounded-full bg-indigo-100 px-3 py-1 text-xs font-semibold text-indigo-700">Aktif: {{ $documentType->student_documents_count }}</span>
|
||||
@php($trashedDocumentsCount = $documentType->student_documents_with_trashed_count - $documentType->student_documents_count)
|
||||
@if ($trashedDocumentsCount > 0)
|
||||
<span class="rounded-full bg-amber-100 px-3 py-1 text-xs font-semibold text-amber-700">Arsip: {{ $trashedDocumentsCount }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
@if ($documentType->is_required)
|
||||
@@ -50,16 +56,24 @@
|
||||
<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)
|
||||
@php($trashedDocumentsCount = $documentType->student_documents_with_trashed_count - $documentType->student_documents_count)
|
||||
@if ($documentType->student_documents_count === 0 && $trashedDocumentsCount === 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">
|
||||
@elseif ($documentType->student_documents_count > 0)
|
||||
<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 aktif">
|
||||
Dipakai
|
||||
</button>
|
||||
@else
|
||||
<form action="{{ route('document-types.purge-archived-documents', $documentType) }}" method="POST" class="delete-form" data-entity="arsip dokumen untuk jenis dokumen {{ $documentType->name }}" data-confirm-title="Bersihkan arsip dokumen?" data-confirm-text="Arsip dokumen siswa yang terkait akan dihapus permanen, lalu jenis dokumen ini ikut dihapus. Tindakan ini tidak bisa dibatalkan.">
|
||||
@csrf
|
||||
<button type="submit" class="rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 text-xs font-semibold text-amber-700 transition hover:bg-amber-100">
|
||||
Bersihkan Arsip
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -85,14 +85,22 @@
|
||||
<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>
|
||||
<div>Tahun: {{ $row['display']['academic_year'] ?? ($row['data']['academic_year_id'] ?: '-') }}</div>
|
||||
<div>Jurusan: {{ $row['display']['department'] ?? ($row['data']['department_id'] ?: '-') }}</div>
|
||||
<div>Kelas: {{ $row['display']['class'] ?? ($row['data']['class_id'] ?: '-') }}</div>
|
||||
@if (!empty($row['display']['class_context']))
|
||||
<div class="text-xs text-slate-400">{{ $row['display']['class_context'] }}</div>
|
||||
@endif
|
||||
</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>
|
||||
<div class="flex flex-col gap-2">
|
||||
<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>
|
||||
<span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-600">
|
||||
Status: {{ $row['display']['status'] ?? ($row['data']['status'] ?: '-') }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-4 text-sm">
|
||||
@if ($row['is_valid'])
|
||||
|
||||
@@ -49,6 +49,7 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
||||
Route::post('classes/generate', [SchoolClassController::class, 'generate'])->name('classes.generate');
|
||||
Route::delete('classes/bulk-delete', [SchoolClassController::class, 'bulkDestroy'])->name('classes.bulk-destroy');
|
||||
Route::resource('classes', SchoolClassController::class);
|
||||
Route::post('document-types/{documentType}/purge-archived-documents', [DocumentTypeController::class, 'purgeArchivedDocuments'])->name('document-types.purge-archived-documents');
|
||||
Route::resource('document-types', DocumentTypeController::class);
|
||||
Route::prefix('settings')->name('settings.')->group(function () {
|
||||
Route::resource('users', UserManagementController::class)->except(['show']);
|
||||
|
||||
Reference in New Issue
Block a user