From 93c5514c2265d9f22d1071f9461b1140a6985cb5 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Tue, 1 Sep 2026 00:03:13 +0700 Subject: [PATCH] Refactor Letter Request Management - Created LetterRequestRequest and LetterRequestStatusRequest for handling form requests. - Removed the old LetterRequestRequest class and updated the authorization logic. - Changed the student relationship to user in the LetterRequest model. - Updated LetterRequestService to reflect changes in the model and request handling. - Modified permission catalog to include new permissions for letter request status updates. - Updated database migrations and seeders to use user_id instead of student_id. - Removed student-specific letter request pages and components, consolidating functionality under admin routes. - Adjusted frontend components to accommodate new data structure and permissions. - Enhanced the letter request columns to support status updates directly from the table. --- .../Services/LetterRequestController.php | 65 ++---- .../LetterRequest/LetterRequestRequest.php | 30 +++ .../LetterRequestStatusRequest.php | 23 ++ .../Admin/Services/LetterRequestRequest.php | 37 --- app/Models/LetterRequest.php | 4 +- .../Admin/Services/LetterRequestService.php | 68 ++---- app/Support/PermissionCatalog.php | 2 +- ...25_000003_create_letter_requests_table.php | 2 +- database/seeders/LetterRequestSeeder.php | 4 +- database/seeders/RolePermissionSeeder.php | 24 +- resources/js/components/app-sidebar.tsx | 7 +- .../services/letter-requests/columns.tsx | 123 +++++----- .../admin/services/letter-requests/index.tsx | 218 ++++++++---------- .../pages/student/letter-requests/columns.tsx | 91 -------- .../pages/student/letter-requests/create.tsx | 79 ------- .../pages/student/letter-requests/index.tsx | 40 ---- .../js/pages/student/letter-requests/show.tsx | 123 ---------- resources/js/types/letter-request.ts | 14 +- routes/admin.php | 8 +- 19 files changed, 290 insertions(+), 672 deletions(-) create mode 100644 app/Http/Requests/Admin/Services/LetterRequest/LetterRequestRequest.php create mode 100644 app/Http/Requests/Admin/Services/LetterRequest/LetterRequestStatusRequest.php delete mode 100644 app/Http/Requests/Admin/Services/LetterRequestRequest.php delete mode 100644 resources/js/pages/student/letter-requests/columns.tsx delete mode 100644 resources/js/pages/student/letter-requests/create.tsx delete mode 100644 resources/js/pages/student/letter-requests/index.tsx delete mode 100644 resources/js/pages/student/letter-requests/show.tsx diff --git a/app/Http/Controllers/Admin/Services/LetterRequestController.php b/app/Http/Controllers/Admin/Services/LetterRequestController.php index 044a878..d405fe2 100644 --- a/app/Http/Controllers/Admin/Services/LetterRequestController.php +++ b/app/Http/Controllers/Admin/Services/LetterRequestController.php @@ -2,15 +2,14 @@ namespace App\Http\Controllers\Admin\Services; +use App\Enums\LetterStatus; use App\Http\Controllers\Controller; -use App\Http\Requests\Admin\Services\LetterRequestRequest; +use App\Http\Requests\Admin\Services\LetterRequest\LetterRequestRequest; +use App\Http\Requests\Admin\Services\LetterRequest\LetterRequestStatusRequest; use App\Http\Requests\PaginatedRequest; use App\Models\LetterRequest; -use App\Models\Student; use App\Services\Admin\Services\LetterRequestService; -use App\Services\Admin\Users\StudentService; use Illuminate\Http\RedirectResponse; -use Illuminate\Http\Request; use Inertia\Inertia; use Inertia\Response; @@ -18,7 +17,6 @@ class LetterRequestController extends Controller { public function __construct( private readonly LetterRequestService $service, - private readonly StudentService $studentService, ) {} public function index(PaginatedRequest $request): Response @@ -28,60 +26,25 @@ public function index(PaginatedRequest $request): Response ...$request->validatedWithDefaults(), status: $request->validated('status'), ), - 'students' => $this->studentService->getAllForSelect(), 'filters' => $request->only(['status']), ]); } - public function mine(Request $request): Response - { - return Inertia::render('student/letter-requests/index', [ - 'letterRequests' => $this->service->mine($this->currentStudent($request)), - ]); - } - - public function create(): Response - { - return Inertia::render('student/letter-requests/create'); - } - - public function show(Request $request, LetterRequest $letterRequest): Response - { - $student = $this->currentStudent($request); - - abort_if($letterRequest->student_id !== $student->id, 403); - - return Inertia::render('student/letter-requests/show', [ - 'letterRequest' => $this->service->withDetails($letterRequest), - ]); - } - public function store(LetterRequestRequest $request): RedirectResponse { - if ($request->routeIs('student.*')) { - $student = $this->currentStudent($request); + $this->service->create([ + 'user_id' => auth()->id(), + ...$request->validated(), + ]); - $letterRequest = $this->service->create([ - 'student_id' => $student->id, - 'letter_type' => $request->validated('letter_type'), - 'purpose' => $request->validated('purpose'), - ], null); - - Inertia::flash('toast', ['type' => 'success', 'message' => 'Surat permohonan berhasil diajukan.']); - - return to_route('student.letter-requests.show', $letterRequest); - } - - $this->service->create($request->validated(), $request->file('result')); - - Inertia::flash('toast', ['type' => 'success', 'message' => 'Surat permohonan berhasil ditambahkan.']); + Inertia::flash('toast', ['type' => 'success', 'message' => 'Surat permohonan berhasil diajukan.']); return to_route('admin.services.letter-requests.index'); } public function update(LetterRequestRequest $request, LetterRequest $letterRequest): RedirectResponse { - $this->service->update($letterRequest, $request->validated(), $request->file('result')); + $this->service->update($letterRequest, $request->validated()); Inertia::flash('toast', ['type' => 'success', 'message' => 'Surat permohonan berhasil diperbarui.']); @@ -90,17 +53,17 @@ public function update(LetterRequestRequest $request, LetterRequest $letterReque public function destroy(LetterRequest $letterRequest): RedirectResponse { + abort_if($letterRequest->status !== LetterStatus::Submitted->value, 403); + $this->service->delete($letterRequest); return Inertia::flash('toast', ['type' => 'success', 'message' => 'Surat permohonan berhasil dihapus.'])->back(); } - private function currentStudent(Request $request): Student + public function updateStatus(LetterRequestStatusRequest $request, LetterRequest $letterRequest): RedirectResponse { - $student = $request->user()->student; + $this->service->updateStatus($letterRequest, $request->validated('status'), $request->file('result')); - abort_if(! $student, 403); - - return $student; + return Inertia::flash('toast', ['type' => 'success', 'message' => 'Status surat permohonan berhasil diperbarui.'])->back(); } } diff --git a/app/Http/Requests/Admin/Services/LetterRequest/LetterRequestRequest.php b/app/Http/Requests/Admin/Services/LetterRequest/LetterRequestRequest.php new file mode 100644 index 0000000..b1a9456 --- /dev/null +++ b/app/Http/Requests/Admin/Services/LetterRequest/LetterRequestRequest.php @@ -0,0 +1,30 @@ +isMethod('post')) { + return $this->user()->can('create-letter-requests'); + } + + $letterRequest = $this->route('letter_request'); + + return $this->user()->can('update-letter-requests') + && $letterRequest->user_id === $this->user()->id + && $letterRequest->status === LetterStatus::Submitted->value; + } + + public function rules(): array + { + return [ + 'letter_type' => ['required', 'string', 'max:100'], + 'purpose' => ['nullable', 'string'], + ]; + } +} diff --git a/app/Http/Requests/Admin/Services/LetterRequest/LetterRequestStatusRequest.php b/app/Http/Requests/Admin/Services/LetterRequest/LetterRequestStatusRequest.php new file mode 100644 index 0000000..a408828 --- /dev/null +++ b/app/Http/Requests/Admin/Services/LetterRequest/LetterRequestStatusRequest.php @@ -0,0 +1,23 @@ +user()->can('update-letter-requests-status'); + } + + public function rules(): array + { + return [ + 'status' => ['nullable', 'required_without:result', Rule::enum(LetterStatus::class)], + 'result' => ['nullable', 'file', 'max:10240', 'mimes:pdf,jpg,jpeg,png,doc,docx'], + ]; + } +} diff --git a/app/Http/Requests/Admin/Services/LetterRequestRequest.php b/app/Http/Requests/Admin/Services/LetterRequestRequest.php deleted file mode 100644 index fecbd3e..0000000 --- a/app/Http/Requests/Admin/Services/LetterRequestRequest.php +++ /dev/null @@ -1,37 +0,0 @@ -routeIs('student.*')) { - return (bool) $this->user()->student; - } - - return $this->user()->can($this->isMethod('post') ? 'create-letter-requests' : 'update-letter-requests'); - } - - public function rules(): array - { - if ($this->routeIs('student.*')) { - return [ - 'letter_type' => ['required', 'string', 'max:100'], - 'purpose' => ['nullable', 'string'], - ]; - } - - return [ - 'student_id' => ['required', 'integer', Rule::exists('students', 'id')], - 'letter_type' => ['required', 'string', 'max:100'], - 'purpose' => ['nullable', 'string'], - 'status' => ['required', Rule::enum(LetterStatus::class)], - 'result' => ['nullable', 'file', 'max:10240', 'mimes:pdf,jpg,jpeg,png,doc,docx'], - ]; - } -} diff --git a/app/Models/LetterRequest.php b/app/Models/LetterRequest.php index 5f4edab..2951b93 100644 --- a/app/Models/LetterRequest.php +++ b/app/Models/LetterRequest.php @@ -32,9 +32,9 @@ public function registerMediaCollections(): void $this->addMediaCollection('letter_result')->singleFile(); } - public function student(): BelongsTo + public function user(): BelongsTo { - return $this->belongsTo(Student::class); + return $this->belongsTo(User::class); } public function processor(): BelongsTo diff --git a/app/Services/Admin/Services/LetterRequestService.php b/app/Services/Admin/Services/LetterRequestService.php index dbea71b..0d1f4e0 100644 --- a/app/Services/Admin/Services/LetterRequestService.php +++ b/app/Services/Admin/Services/LetterRequestService.php @@ -4,71 +4,56 @@ use App\Enums\LetterStatus; use App\Models\LetterRequest; -use App\Models\Student; use Illuminate\Contracts\Pagination\LengthAwarePaginator; -use Illuminate\Database\Eloquent\Collection; use Illuminate\Http\UploadedFile; class LetterRequestService { - /** - * @return Collection - */ - public function mine(Student $student): Collection - { - return LetterRequest::query() - ->where('student_id', $student->id) - ->with('processor.profile') - ->latest() - ->get(); - } - - public function withDetails(LetterRequest $letterRequest): LetterRequest - { - return $letterRequest->load('processor.profile'); - } - public function paginated(int $perPage = 25, string $search = '', ?string $status = null): LengthAwarePaginator { + $user = auth()->user(); + return LetterRequest::query() - ->select(['id', 'student_id', 'letter_type', 'purpose', 'status', 'processed_by', 'submitted_at', 'completed_at']) - ->with(['student.user.profile', 'student.department', 'processor.profile']) + ->select(['id', 'user_id', 'letter_type', 'purpose', 'status', 'processed_by', 'submitted_at', 'completed_at']) + ->with(['user.profile', 'user.student.department', 'processor.profile']) ->when($search, fn ($q) => $q->where('letter_type', 'like', "%{$search}%") - ->orWhereHas('student', fn ($q) => $q->where('student_number', 'like', "%{$search}%") - ->orWhereHas('user.profile', fn ($q) => $q->where('full_name', 'like', "%{$search}%")))) + ->orWhereHas('user', fn ($q) => $q->whereHas('profile', fn ($q) => $q->where('full_name', 'like', "%{$search}%")) + ->orWhereHas('student', fn ($q) => $q->where('student_number', 'like', "%{$search}%")))) ->when($status, fn ($q) => $q->where('status', $status)) + ->when($user->hasRole('mahasiswa'), fn ($q) => $q->where('user_id', $user->id)) ->latest() ->paginate($perPage); } - public function create(array $data, ?UploadedFile $result): LetterRequest + public function create(array $data): LetterRequest { - $status = $data['status'] ?? LetterStatus::Submitted->value; - - $letterRequest = LetterRequest::create([ - 'student_id' => $data['student_id'], + return LetterRequest::create([ + 'user_id' => $data['user_id'], 'letter_type' => $data['letter_type'], 'purpose' => $data['purpose'] ?? null, - 'status' => $status, - 'processed_by' => $status !== LetterStatus::Submitted->value ? auth()->id() : null, + 'status' => LetterStatus::Submitted->value, 'submitted_at' => now(), - 'completed_at' => $status === LetterStatus::Completed->value ? now() : null, ]); + } - if ($result) { - $letterRequest->addMedia($result)->toMediaCollection('letter_result'); - } + public function update(LetterRequest $letterRequest, array $data): LetterRequest + { + $letterRequest->letter_type = $data['letter_type']; + $letterRequest->purpose = $data['purpose'] ?? null; + $letterRequest->update(); return $letterRequest; } - public function update(LetterRequest $letterRequest, array $data, ?UploadedFile $result): LetterRequest + public function delete(LetterRequest $letterRequest): bool { - $status = $data['status'] ?? LetterStatus::Submitted->value; + return $letterRequest->delete(); + } + + public function updateStatus(LetterRequest $letterRequest, ?string $status, ?UploadedFile $result): LetterRequest + { + $status = $result ? LetterStatus::Completed->value : $status; - $letterRequest->student_id = $data['student_id']; - $letterRequest->letter_type = $data['letter_type']; - $letterRequest->purpose = $data['purpose'] ?? null; $letterRequest->status = $status; $letterRequest->processed_by = $status !== LetterStatus::Submitted->value ? auth()->id() : null; $letterRequest->completed_at = $status === LetterStatus::Completed->value @@ -82,9 +67,4 @@ public function update(LetterRequest $letterRequest, array $data, ?UploadedFile return $letterRequest; } - - public function delete(LetterRequest $letterRequest): bool - { - return $letterRequest->delete(); - } } diff --git a/app/Support/PermissionCatalog.php b/app/Support/PermissionCatalog.php index e300ae6..f36c1c0 100644 --- a/app/Support/PermissionCatalog.php +++ b/app/Support/PermissionCatalog.php @@ -33,7 +33,7 @@ class PermissionCatalog ]; public const SERVICES = [ - 'view-letter-requests', 'create-letter-requests', 'update-letter-requests', 'delete-letter-requests', + 'view-letter-requests', 'create-letter-requests', 'update-letter-requests', 'update-letter-requests-status', 'delete-letter-requests', 'view-academic-advising-logs', 'create-academic-advising-logs', 'update-academic-advising-logs', 'delete-academic-advising-logs', ]; diff --git a/database/migrations/2026_08_25_000003_create_letter_requests_table.php b/database/migrations/2026_08_25_000003_create_letter_requests_table.php index 328e22d..8bb7a69 100644 --- a/database/migrations/2026_08_25_000003_create_letter_requests_table.php +++ b/database/migrations/2026_08_25_000003_create_letter_requests_table.php @@ -11,7 +11,7 @@ public function up(): void { Schema::create('letter_requests', function (Blueprint $table) { $table->id(); - $table->foreignId('student_id')->constrained()->cascadeOnDelete(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->string('letter_type', 100); $table->text('purpose')->nullable(); $table->enum('status', LetterStatus::values())->nullable()->default(LetterStatus::Submitted->value); diff --git a/database/seeders/LetterRequestSeeder.php b/database/seeders/LetterRequestSeeder.php index 2b3c2fd..f8e3ec9 100644 --- a/database/seeders/LetterRequestSeeder.php +++ b/database/seeders/LetterRequestSeeder.php @@ -11,7 +11,7 @@ public function run(): void { LetterRequest::insert([ [ - 'student_id' => 1, + 'user_id' => 1, 'letter_type' => 'Surat Aktif Kuliah', 'purpose' => 'Persyaratan pengajuan beasiswa', 'status' => 'completed', @@ -22,7 +22,7 @@ public function run(): void 'updated_at' => '2026-07-02 14:00:00', ], [ - 'student_id' => 2, + 'user_id' => 2, 'letter_type' => 'Surat Keterangan Lulus Sementara', 'purpose' => 'Persyaratan melamar magang', 'status' => 'in_process', diff --git a/database/seeders/RolePermissionSeeder.php b/database/seeders/RolePermissionSeeder.php index b261a77..bc542b5 100644 --- a/database/seeders/RolePermissionSeeder.php +++ b/database/seeders/RolePermissionSeeder.php @@ -22,7 +22,10 @@ public function run(): void $users = PermissionCatalog::USERS; $feedbackSelfService = [ - 'view-feedback', 'create-feedback', 'update-feedback', 'delete-feedback', + 'view-feedback', + 'create-feedback', + 'update-feedback', + 'delete-feedback', ]; $permissionNames = PermissionCatalog::all(); @@ -34,14 +37,27 @@ public function run(): void app()[PermissionRegistrar::class]->forgetCachedPermissions(); $roles = [ - 'mahasiswa' => ['view-dashboard', ...$feedbackSelfService], - 'dosen' => ['view-dashboard', ...$feedbackSelfService], + 'mahasiswa' => [ + 'view-dashboard', + 'view-academic-terms', + 'view-courses', + 'view-letter-requests', + 'create-letter-requests', + 'update-letter-requests', + 'delete-letter-requests', + ...$feedbackSelfService, + ], + 'dosen' => [ + 'view-dashboard', + 'view-courses', + ...$feedbackSelfService, + ], 'staff-admin' => [ 'view-dashboard', ...$master, ...$academicClasses, ...$manage, - ...$services, + ...array_diff($services, ['create-letter-requests', 'update-letter-requests']), ...$users, ...$feedbackSelfService, 'update-feedback-status', diff --git a/resources/js/components/app-sidebar.tsx b/resources/js/components/app-sidebar.tsx index 7d77d39..0e764be 100644 --- a/resources/js/components/app-sidebar.tsx +++ b/resources/js/components/app-sidebar.tsx @@ -56,7 +56,6 @@ import { index as lecturersRoute } from '@/routes/admin/users/lecturers'; import { index as studentsRoute } from '@/routes/admin/users/students'; import { index as lecturerAcademicAdvisingLogsRoute } from '@/routes/lecturer/academic-advising-logs'; import { index as studentCourseRegistrationsRoute } from '@/routes/student/course-registrations'; -import { index as studentLetterRequestsRoute } from '@/routes/student/letter-requests'; import type { Auth } from '@/types/auth'; const STAFF_ROLES = ['developer', 'staff-admin', 'staff-keuangan', 'kaprodi']; @@ -198,13 +197,11 @@ function buildNavMain({ ]; const layananItems: NavItem[] = [ - ...(isMahasiswa || can('view-letter-requests') + ...(can('view-letter-requests') ? [ { name: 'Surat Permohonan', - url: isMahasiswa - ? studentLetterRequestsRoute.url() - : letterRequestsRoute.url(), + url: letterRequestsRoute.url(), icon: Mail, }, ] diff --git a/resources/js/pages/admin/services/letter-requests/columns.tsx b/resources/js/pages/admin/services/letter-requests/columns.tsx index c3d4329..cdcf2a7 100644 --- a/resources/js/pages/admin/services/letter-requests/columns.tsx +++ b/resources/js/pages/admin/services/letter-requests/columns.tsx @@ -2,39 +2,63 @@ import type { ColumnDef } from '@tanstack/react-table'; import { format } from 'date-fns'; import { Paperclip, Pencil, Trash2 } from 'lucide-react'; import { RowActions } from '@/components/row-actions'; -import { Badge } from '@/components/ui/badge'; -import type { LetterRequest, LetterStatus } from '@/types/letter-request'; -import { LetterStatusLabels } from '@/types/letter-request'; +import { StatusBadge } from '@/components/status-badge'; +import type { LetterRequest } from '@/types/letter-request'; +import { LetterStatuses, LetterStatusLabels } from '@/types/letter-request'; export type { LetterRequest } from '@/types/letter-request'; +const LetterStatusVariants: Record< + string, + 'default' | 'secondary' | 'destructive' | 'outline' +> = { + submitted: 'outline', + in_process: 'secondary', + completed: 'default', + rejected: 'destructive', +}; + +const letterStatusOptions = LetterStatuses.map((status) => ({ + value: status, + label: LetterStatusLabels[status], +})); + type CreateColumnsParams = { handleEdit: (letterRequest: LetterRequest) => void; handleDeleteClick: (letterRequest: LetterRequest) => void; + handleStatusChange: (letterRequest: LetterRequest, status: string) => void; canUpdate: boolean; canDelete: boolean; + canProcess: boolean; }; export function createLetterRequestColumns( params: CreateColumnsParams, ): ColumnDef[] { - const { handleEdit, handleDeleteClick, canUpdate, canDelete } = params; + const { + handleEdit, + handleDeleteClick, + handleStatusChange, + canUpdate, + canDelete, + canProcess, + } = params; const columns: ColumnDef[] = [ { - accessorKey: 'student.student_number', + accessorKey: 'user.student.student_number', header: () => Mahasiswa, cell: ({ row }) => { - const student = row.original.student; + const user = row.original.user; return (

- {student?.user?.profile?.full_name ?? 'N/A'} + {user?.profile?.full_name ?? 'N/A'}

- {student?.student_number} ·{' '} - {student?.department?.name ?? '-'} + {user?.student?.student_number} ·{' '} + {user?.student?.department?.name ?? '-'}

); @@ -67,30 +91,19 @@ export function createLetterRequestColumns( className: 'w-[120px] text-center', headerClassName: 'w-[120px] text-center', }, - cell: ({ row }) => { - const status = row.getValue('status') as LetterStatus | null; - - const variant = - status === 'completed' - ? 'default' - : status === 'rejected' - ? 'destructive' - : status === 'in_process' - ? 'secondary' - : 'outline'; - - return ( -
- {status ? ( - - {LetterStatusLabels[status]} - - ) : ( - - - )} -
- ); - }, + cell: ({ row }) => ( +
+ + handleStatusChange(row.original, status) + } + disabled={!canProcess} + /> +
+ ), }, { accessorKey: 'processor.profile.full_name', @@ -142,26 +155,30 @@ export function createLetterRequestColumns( className: 'w-[100px] text-center', headerClassName: 'w-[100px] text-center', }, - cell: ({ row }) => ( - , - show: canUpdate, - onClick: () => handleEdit(row.original), - }, - { - label: 'Hapus', - icon: ( - - ), - show: canDelete, - onClick: () => handleDeleteClick(row.original), - }, - ]} - /> - ), + cell: ({ row }) => { + const isLocked = row.original.status !== 'submitted'; + + return ( + , + show: canUpdate && !isLocked, + onClick: () => handleEdit(row.original), + }, + { + label: 'Hapus', + icon: ( + + ), + show: canDelete && !isLocked, + onClick: () => handleDeleteClick(row.original), + }, + ]} + /> + ); + }, }); } diff --git a/resources/js/pages/admin/services/letter-requests/index.tsx b/resources/js/pages/admin/services/letter-requests/index.tsx index 70c60f6..e7a0968 100644 --- a/resources/js/pages/admin/services/letter-requests/index.tsx +++ b/resources/js/pages/admin/services/letter-requests/index.tsx @@ -1,6 +1,3 @@ -import { Head, router } from '@inertiajs/react'; -import { Plus } from 'lucide-react'; -import { useState } from 'react'; import type { PaginationState } from '@/components/data-table'; import { DataTable } from '@/components/data-table'; import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog'; @@ -10,38 +7,25 @@ import { FilterDialog } from '@/components/filter-dialog'; import { FormDialog } from '@/components/form-dialog'; import InputError from '@/components/input-error'; import { PageHeader } from '@/components/page-header'; +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Button } from '@/components/ui/button'; -import { - Combobox, - ComboboxContent, - ComboboxEmpty, - ComboboxInput, - ComboboxItem, - ComboboxList, -} from '@/components/ui/combobox'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; import { Textarea } from '@/components/ui/textarea'; import { usePermissions } from '@/hooks/use-permissions'; import { useServerTable } from '@/hooks/use-server-table'; import { - index as letterRequestIndex, destroy, + index as letterRequestIndex, store, update, + update_status, } from '@/routes/admin/services/letter-requests'; -import type { - LetterRequest, - LetterRequestStudent, -} from '@/types/letter-request'; +import type { LetterRequest } from '@/types/letter-request'; import { LetterStatuses, LetterStatusLabels } from '@/types/letter-request'; +import { Head, router } from '@inertiajs/react'; +import { Info, Plus } from 'lucide-react'; +import { useState } from 'react'; import { createLetterRequestColumns } from './columns'; type Props = { @@ -52,30 +36,26 @@ type Props = { per_page: number; total: number; }; - students: LetterRequestStudent[]; highlight?: number; filters: { status?: string; }; }; -function studentLabel(student: LetterRequestStudent): string { - return `${student.user?.profile?.full_name ?? 'N/A'} - ${student.student_number}`; -} - export default function LetterRequestIndex({ letterRequests, - students, highlight, filters, }: Props) { const [createOpen, setCreateOpen] = useState(false); const [editing, setEditing] = useState(null); const [deleting, setDeleting] = useState(null); + const [processing, setProcessing] = useState(null); const { hasPermission } = usePermissions(); const canCreate = hasPermission('create-letter-requests'); const canUpdate = hasPermission('update-letter-requests'); const canDelete = hasPermission('delete-letter-requests'); + const canProcess = hasPermission('update-letter-requests-status'); const filterFields: FilterField[] = [ { @@ -117,11 +97,27 @@ export default function LetterRequestIndex({ }); } + function handleStatusChange(letterRequest: LetterRequest, status: string) { + if (status === 'completed') { + setProcessing(letterRequest); + + return; + } + + router.patch( + update_status.url(letterRequest.id), + { status }, + { preserveScroll: true }, + ); + } + const columns = createLetterRequestColumns({ handleEdit: (letterRequest) => setEditing(letterRequest), handleDeleteClick: (letterRequest) => setDeleting(letterRequest), + handleStatusChange, canUpdate, canDelete, + canProcess, }); return ( @@ -146,18 +142,14 @@ export default function LetterRequestIndex({ onClick={() => setCreateOpen(true)} > - Tambah + Ajukan ) } /> - + + { + if (!open) { + setProcessing(null); + } + }} + processing={processing} + /> + + {canProcess && ( + + + Ubah status permohonan + + Klik badge Status pada tabel untuk mengubah status + permohonan secara langsung. + + + )} + - `Apakah Anda yakin ingin menghapus permohonan "${letterRequest.letter_type}" milik "${letterRequest.student?.user?.profile?.full_name ?? 'mahasiswa ini'}"? Tindakan ini tidak dapat dibatalkan.` + `Apakah Anda yakin ingin menghapus permohonan "${letterRequest.letter_type}" milik "${letterRequest.user?.profile?.full_name ?? 'mahasiswa ini'}"? Tindakan ini tidak dapat dibatalkan.` } onConfirm={handleDelete} /> @@ -210,55 +223,12 @@ export default function LetterRequestIndex({ function LetterRequestFields({ errors, editing, - students, - resetKey, }: { errors: Record; editing?: LetterRequest; - students: LetterRequestStudent[]; - resetKey?: string; }) { - const [student, setStudent] = useState( - editing?.student ?? null, - ); - return ( <> -
- - - studentLabel(s)} - isItemEqualToValue={(a, b) => a.id === b.id} - > - - - - Mahasiswa tidak ditemukan. - - - {(s: LetterRequestStudent) => ( - - {studentLabel(s)} - - )} - - - - -
-
- - - - -
- ); } @@ -320,45 +260,75 @@ function LetterRequestFields({ function CreateForm({ open, onOpenChange, - students, }: { open: boolean; onOpenChange: (open: boolean) => void; - students: LetterRequestStudent[]; }) { return ( onOpenChange(false)} > {({ errors }) => (
- +
)}
); } +function ProcessForm({ + open, + onOpenChange, + processing, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + processing: LetterRequest | null; +}) { + return ( + onOpenChange(false)} + > + {({ errors }) => + processing && ( +
+

+ Unggah dokumen hasil untuk menandai surat ini + sebagai selesai. +

+ +
+ ) + } +
+ ); +} + function EditForm({ open, onOpenChange, editing, - students, }: { open: boolean; onOpenChange: (open: boolean) => void; editing: LetterRequest | null; - students: LetterRequestStudent[]; }) { return ( editing && (
- +
) } diff --git a/resources/js/pages/student/letter-requests/columns.tsx b/resources/js/pages/student/letter-requests/columns.tsx deleted file mode 100644 index 674edcb..0000000 --- a/resources/js/pages/student/letter-requests/columns.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import type { ColumnDef } from '@tanstack/react-table'; -import { format } from 'date-fns'; -import { Eye } from 'lucide-react'; -import { RowActions } from '@/components/row-actions'; -import { Badge } from '@/components/ui/badge'; -import { show } from '@/routes/student/letter-requests'; -import type { LetterRequest, LetterStatus } from '@/types/letter-request'; -import { LetterStatusLabels } from '@/types/letter-request'; - -export const letterRequestColumns: ColumnDef[] = [ - { - accessorKey: 'letter_type', - header: () => Jenis Surat, - cell: ({ row }) => { - const letterRequest = row.original; - - return ( -
-

{letterRequest.letter_type}

- {letterRequest.purpose && ( -

- {letterRequest.purpose} -

- )} -
- ); - }, - }, - { - accessorKey: 'status', - header: () => Status, - meta: { - className: 'w-[130px] text-center', - headerClassName: 'w-[130px] text-center', - }, - cell: ({ row }) => { - const status = row.original.status as LetterStatus | null; - - const variant = - status === 'completed' - ? 'default' - : status === 'rejected' - ? 'destructive' - : status === 'in_process' - ? 'secondary' - : 'outline'; - - return ( -
- {status ? ( - - {LetterStatusLabels[status]} - - ) : ( - - - )} -
- ); - }, - }, - { - accessorKey: 'submitted_at', - header: () => Diajukan, - cell: ({ row }) => { - const submittedAt = row.original.submitted_at; - - return submittedAt - ? format(new Date(submittedAt), 'd MMM yyyy, HH:mm') - : '-'; - }, - }, - { - id: 'actions', - header: () => Aksi, - meta: { - className: 'w-[80px] text-center', - headerClassName: 'w-[80px] text-center', - }, - cell: ({ row }) => ( - , - href: show.url(row.original.id), - }, - ]} - /> - ), - }, -]; diff --git a/resources/js/pages/student/letter-requests/create.tsx b/resources/js/pages/student/letter-requests/create.tsx deleted file mode 100644 index d031ec5..0000000 --- a/resources/js/pages/student/letter-requests/create.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { Form, Head, Link } from '@inertiajs/react'; -import { ArrowLeft } from 'lucide-react'; -import InputError from '@/components/input-error'; -import { PageHeader } from '@/components/page-header'; -import { Button } from '@/components/ui/button'; -import { - Card, - CardContent, - CardFooter, - CardHeader, - CardTitle, -} from '@/components/ui/card'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Textarea } from '@/components/ui/textarea'; -import { index, store } from '@/routes/student/letter-requests'; - -export default function LetterRequestCreate() { - return ( - <> - - -
- - - - Kembali - - - } - /> - -
- {({ errors, processing }) => ( - - - Detail Permohonan - - -
- - - -
-
- -