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
@@ -0,0 +1,241 @@
<?php
namespace App\Http\Controllers;
use App\Models\DocumentType;
use App\Models\Student;
use App\Models\StudentDocument;
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;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpFoundation\Response;
class StudentDocumentController extends Controller
{
public function create(Student $student): View
{
return view('student-documents.create', [
'student' => $student,
'documentTypes' => DocumentType::where('is_active', true)->orderBy('name')->get(),
'verificationStatuses' => $this->verificationStatuses(),
'defaultMaxFileSizeKb' => 5120,
]);
}
public function store(Request $request, Student $student): RedirectResponse
{
$data = $this->validateDocument($request);
$uploadedFile = $request->file('document_file');
$path = $uploadedFile->store("student-documents/{$student->id}", 'public');
StudentDocument::create([
'student_id' => $student->id,
'document_type_id' => $data['document_type_id'],
'file_name' => $uploadedFile->getClientOriginalName(),
'file_path' => $path,
'file_extension' => $uploadedFile->getClientOriginalExtension(),
'mime_type' => $uploadedFile->getMimeType(),
'file_size' => $uploadedFile->getSize(),
'document_number' => $data['document_number'] ?? null,
'issued_date' => $data['issued_date'] ?? null,
'expired_date' => $data['expired_date'] ?? null,
'description' => $data['description'] ?? null,
'verification_status' => $data['verification_status'],
'verification_note' => $data['verification_note'] ?? null,
'uploaded_by' => auth()->id(),
'verified_by' => in_array($data['verification_status'], ['valid', 'invalid', 'revision']) ? auth()->id() : null,
'verified_at' => in_array($data['verification_status'], ['valid', 'invalid', 'revision']) ? now() : null,
]);
Alert::success('Berhasil', 'Dokumen siswa berhasil diunggah.');
return redirect()->route('students.show', $student)->with('success', 'Dokumen siswa berhasil diunggah.');
}
public function edit(Student $student, StudentDocument $document): View
{
abort_unless($document->student_id === $student->id, 404);
return view('student-documents.edit', [
'student' => $student,
'document' => $document,
'documentTypes' => DocumentType::where('is_active', true)->orderBy('name')->get(),
'verificationStatuses' => $this->verificationStatuses(),
'defaultMaxFileSizeKb' => 5120,
]);
}
public function update(Request $request, Student $student, StudentDocument $document): RedirectResponse
{
abort_unless($document->student_id === $student->id, 404);
$data = $this->validateDocument($request, false);
if ($request->hasFile('document_file')) {
if ($document->file_path && Storage::disk('public')->exists($document->file_path)) {
Storage::disk('public')->delete($document->file_path);
}
$uploadedFile = $request->file('document_file');
$path = $uploadedFile->store("student-documents/{$student->id}", 'public');
$document->file_name = $uploadedFile->getClientOriginalName();
$document->file_path = $path;
$document->file_extension = $uploadedFile->getClientOriginalExtension();
$document->mime_type = $uploadedFile->getMimeType();
$document->file_size = $uploadedFile->getSize();
}
$document->fill([
'document_type_id' => $data['document_type_id'],
'document_number' => $data['document_number'] ?? null,
'issued_date' => $data['issued_date'] ?? null,
'expired_date' => $data['expired_date'] ?? null,
'description' => $data['description'] ?? null,
'verification_status' => $data['verification_status'],
'verification_note' => $data['verification_note'] ?? null,
]);
if (in_array($data['verification_status'], ['valid', 'invalid', 'revision'])) {
$document->verified_by = auth()->id();
$document->verified_at = now();
} else {
$document->verified_by = null;
$document->verified_at = null;
}
$document->save();
Alert::success('Berhasil', 'Dokumen siswa berhasil diperbarui.');
return redirect()->route('students.show', $student)->with('success', 'Dokumen siswa berhasil diperbarui.');
}
public function destroy(Student $student, StudentDocument $document): RedirectResponse
{
$this->ensureAdmin();
abort_unless($document->student_id === $student->id, 404);
if ($document->file_path && Storage::disk('public')->exists($document->file_path)) {
Storage::disk('public')->delete($document->file_path);
}
$document->delete();
Alert::success('Berhasil', 'Dokumen siswa berhasil dihapus.');
return redirect()->route('students.show', $student)->with('success', 'Dokumen siswa berhasil dihapus.');
}
public function preview(StudentDocument $document): Response|View
{
abort_unless($document->file_path && Storage::disk('public')->exists($document->file_path), 404);
$mimeType = $document->mime_type ?: Storage::disk('public')->mimeType($document->file_path);
$url = asset('storage/' . $document->file_path);
$isImage = str_starts_with((string) $mimeType, 'image/');
$isPdf = $mimeType === 'application/pdf';
return view('student-documents.preview', [
'document' => $document,
'fileUrl' => $url,
'mimeType' => $mimeType,
'isImage' => $isImage,
'isPdf' => $isPdf,
]);
}
public function quickVerify(Request $request, Student $student, StudentDocument $document): RedirectResponse
{
$this->ensureAdmin();
abort_unless($document->student_id === $student->id, 404);
$data = $request->validate([
'verification_status' => ['required', Rule::in(['valid', 'revision', 'invalid'])],
'verification_note' => ['nullable', 'string', 'max:1000'],
]);
$document->update([
'verification_status' => $data['verification_status'],
'verification_note' => $data['verification_note'] ?? null,
'verified_by' => auth()->id(),
'verified_at' => now(),
]);
Alert::success('Berhasil', 'Status verifikasi dokumen berhasil diperbarui.');
return redirect()->route('students.show', $student)->with('success', 'Status verifikasi dokumen berhasil diperbarui.');
}
public function download(StudentDocument $document): StreamedResponse
{
abort_unless($document->file_path && Storage::disk('public')->exists($document->file_path), 404);
return Storage::disk('public')->download($document->file_path, $document->file_name);
}
public function publicPreview(StudentDocument $document): Response|View
{
abort_unless($document->file_path && Storage::disk('public')->exists($document->file_path), 404);
$mimeType = $document->mime_type ?: Storage::disk('public')->mimeType($document->file_path);
$url = asset('storage/' . $document->file_path);
$isImage = str_starts_with((string) $mimeType, 'image/');
$isPdf = $mimeType === 'application/pdf';
return view('public.document-preview', [
'document' => $document,
'fileUrl' => $url,
'mimeType' => $mimeType,
'isImage' => $isImage,
'isPdf' => $isPdf,
]);
}
public function publicDownload(StudentDocument $document): StreamedResponse
{
return $this->download($document);
}
protected function validateDocument(Request $request, bool $fileRequired = true): array
{
$documentType = DocumentType::findOrFail($request->input('document_type_id'));
$maxFileSizeKb = $documentType->max_file_size_kb ?: 5120;
$allowedExtensions = $documentType->allowed_extensions
? collect(explode(',', $documentType->allowed_extensions))->map(fn ($item) => trim($item))->filter()->implode(',')
: 'jpg,jpeg,png,pdf,webp';
return $request->validate([
'document_type_id' => ['required', 'exists:document_types,id'],
'document_number' => ['nullable', 'string', 'max:100'],
'issued_date' => ['nullable', 'date'],
'expired_date' => ['nullable', 'date', 'after_or_equal:issued_date'],
'description' => ['nullable', 'string'],
'verification_status' => ['required', Rule::in(array_keys($this->verificationStatuses()))],
'verification_note' => ['nullable', 'string', 'max:1000'],
'document_file' => [$fileRequired ? 'required' : 'nullable', 'file', 'mimes:' . $allowedExtensions, 'max:' . $maxFileSizeKb],
], [
'document_file.max' => 'Ukuran file melebihi batas maksimal untuk jenis dokumen ini (' . number_format($maxFileSizeKb / 1024, 2) . ' MB).',
'document_file.mimes' => 'Format file tidak sesuai. Format yang diizinkan untuk jenis dokumen ini: ' . strtoupper(str_replace(',', ', ', $allowedExtensions)) . '.',
]);
}
protected function verificationStatuses(): array
{
return [
'pending' => 'Pending',
'valid' => 'Valid',
'invalid' => 'Invalid',
'revision' => 'Perlu Revisi',
];
}
protected function ensureAdmin(): void
{
abort_unless(auth()->user()?->isAdmin(), 403, 'Aksi ini hanya boleh dilakukan administrator.');
}
}