import type { ActionFunctionArgs, LoaderFunctionArgs, MetaFunction } from "@remix-run/node"; import { json, redirect } from "@remix-run/node"; import { Form, useActionData, useLoaderData, useNavigation, useSearchParams } from "@remix-run/react"; import { requireAuthLevel } from "~/utils/auth.server"; import DashboardLayout from "~/components/DashboardLayout"; import { useState, useEffect } from "react"; import { prisma } from "~/utils/db.server"; import Toast from "~/components/Toast"; export const meta: MetaFunction = () => [{ title: "Workers - Phosphat Report" }]; export const loader = async ({ request }: LoaderFunctionArgs) => { const user = await requireAuthLevel(request, 2); // Only supervisors and admins const workers = await prisma.worker.findMany({ orderBy: { name: 'asc' } }); return json({ user, workers }); }; export const action = async ({ request }: ActionFunctionArgs) => { await requireAuthLevel(request, 2); const formData = await request.formData(); const intent = formData.get("intent"); if (intent === "create") { const name = formData.get("name"); if (typeof name !== "string" || !name) { return json({ errors: { name: "Name is required" } }, { status: 400 }); } try { await prisma.worker.create({ data: { name, status: "active" } }); return redirect("/workers?success=Worker created successfully!"); } catch (error: any) { if (error.code === "P2002") { return json({ errors: { name: "A worker with this name already exists" } }, { status: 400 }); } return json({ errors: { form: "Failed to create worker" } }, { status: 400 }); } } if (intent === "update") { const id = formData.get("id"); const name = formData.get("name"); const status = formData.get("status"); if (typeof id !== "string" || !id) { return json({ errors: { form: "Invalid worker ID" } }, { status: 400 }); } if (typeof name !== "string" || !name) { return json({ errors: { name: "Name is required" } }, { status: 400 }); } if (typeof status !== "string" || !["active", "inactive"].includes(status)) { return json({ errors: { status: "Valid status is required" } }, { status: 400 }); } try { await prisma.worker.update({ where: { id: parseInt(id) }, data: { name, status } }); return redirect("/workers?success=Worker updated successfully!"); } catch (error: any) { if (error.code === "P2002") { return json({ errors: { name: "A worker with this name already exists" } }, { status: 400 }); } return json({ errors: { form: "Failed to update worker" } }, { status: 400 }); } } if (intent === "delete") { const id = formData.get("id"); if (typeof id !== "string" || !id) { return json({ errors: { form: "Invalid worker ID" } }, { status: 400 }); } try { await prisma.worker.delete({ where: { id: parseInt(id) } }); return redirect("/workers?success=Worker deleted successfully!"); } catch (error: any) { if (error.code === "P2003") { return redirect("/workers?error=Cannot delete worker: worker is assigned to shifts"); } return redirect("/workers?error=Failed to delete worker"); } } return json({ errors: { form: "Invalid action" } }, { status: 400 }); }; export default function Workers() { const { user, workers } = useLoaderData(); const actionData = useActionData(); const navigation = useNavigation(); const [searchParams, setSearchParams] = useSearchParams(); const [showModal, setShowModal] = useState(false); const [editingWorker, setEditingWorker] = useState(null); const [showDeleteConfirm, setShowDeleteConfirm] = useState(null); const [toast, setToast] = useState<{ message: string; type: "success" | "error" } | null>(null); const [showShiftsModal, setShowShiftsModal] = useState(false); const [selectedWorker, setSelectedWorker] = useState(null); const [workerShifts, setWorkerShifts] = useState([]); const [dateFrom, setDateFrom] = useState(''); const [dateTo, setDateTo] = useState(''); const [isLoadingShifts, setIsLoadingShifts] = useState(false); const isSubmitting = navigation.state === "submitting"; // Handle success messages from URL params useEffect(() => { const successMessage = searchParams.get("success"); const errorMessage = searchParams.get("error"); if (successMessage) { setToast({ message: successMessage, type: "success" }); setSearchParams({}, { replace: true }); // Close modals on success setShowModal(false); setEditingWorker(null); setShowDeleteConfirm(null); } else if (errorMessage) { setToast({ message: errorMessage, type: "error" }); setSearchParams({}, { replace: true }); // Close delete confirm modal on error setShowDeleteConfirm(null); } }, [searchParams, setSearchParams]); // Handle action errors useEffect(() => { if (actionData?.errors) { const errors = actionData.errors as any; const errorMessage = errors.form || errors.name || errors.status || "An error occurred"; setToast({ message: errorMessage, type: "error" }); } }, [actionData]); const handleEdit = (worker: any) => { setEditingWorker(worker); setShowModal(true); }; const handleCloseModal = () => { setShowModal(false); setEditingWorker(null); }; const handleViewShifts = (worker: any) => { setSelectedWorker(worker); setShowShiftsModal(true); setDateFrom(''); setDateTo(''); setWorkerShifts([]); }; const handleCloseShiftsModal = () => { setShowShiftsModal(false); setSelectedWorker(null); setWorkerShifts([]); setDateFrom(''); setDateTo(''); }; const handleFilterShifts = async () => { if (!selectedWorker) return; setIsLoadingShifts(true); try { // Build query parameters const params = new URLSearchParams({ workerId: selectedWorker.id.toString() }); if (dateFrom) params.append('dateFrom', dateFrom); if (dateTo) params.append('dateTo', dateTo); const response = await fetch(`/api/worker-shifts?${params.toString()}`); const data = await response.json(); if (data.shifts) { setWorkerShifts(data.shifts); } } catch (error) { setToast({ message: "Failed to load shifts", type: "error" }); } finally { setIsLoadingShifts(false); } }; return ( {toast && ( setToast(null)} /> )}

Workers Management

Manage laborers and workers

{workers.length === 0 ? ( ) : ( workers.map((worker) => ( )) )}
Name Status Actions
No workers found. Click "Add Worker" to create one.
{worker.name} {worker.status}
{/* Add/Edit Modal */} {showModal && (

{editingWorker ? "Edit Worker" : "Add New Worker"}

{editingWorker && }
{editingWorker && (
)}
)} {/* View Shifts Modal */} {showShiftsModal && selectedWorker && (

Shifts for {selectedWorker.name}

{/* Date Filters */}
setDateFrom(e.target.value)} max={new Date().toISOString().split('T')[0]} className="block w-full text-sm border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500" />
setDateTo(e.target.value)} max={new Date().toISOString().split('T')[0]} className="block w-full text-sm border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500" />
{/* Shifts List */}
{workerShifts.length > 0 ? (
{workerShifts.map((shift: any) => ( ))}
Date Shift Area Dredger Location Employee
{new Date(shift.createdDate).toLocaleDateString('en-GB')} {shift.shift.charAt(0).toUpperCase() + shift.shift.slice(1)} {shift.area.name} {shift.dredgerLocation.name} {shift.employee.name}
) : (

{isLoadingShifts ? 'Loading shifts...' : 'Click "Filter Shifts" to view shifts for this worker'}

)}

{workerShifts.length > 0 && `Showing ${workerShifts.length} shift${workerShifts.length !== 1 ? 's' : ''}`}

)} {/* Delete Confirmation Modal */} {showDeleteConfirm && (

Delete Worker

Are you sure you want to delete this worker? This action cannot be undone.

)}
); }