import { useState, useEffect } from "react"; import type { LoaderFunctionArgs, ActionFunctionArgs, MetaFunction } from "@remix-run/node"; import { useDebounce } from "~/hooks/useDebounce"; import { json, redirect } from "@remix-run/node"; import { useLoaderData, useActionData, useSearchParams, useNavigation } from "@remix-run/react"; import { useSettings } from "~/contexts/SettingsContext"; import { protectMaintenanceRoute } from "~/lib/auth-middleware.server"; import { DashboardLayout } from "~/components/layout/DashboardLayout"; import { Text } from "~/components/ui/Text"; import { Button } from "~/components/ui/Button"; import { Modal } from "~/components/ui/Modal"; import { Input } from "~/components/ui/Input"; import { Select } from "~/components/ui/Select"; import { Flex } from "~/components/layout/Flex"; import { MaintenanceVisitList } from "~/components/maintenance-visits/MaintenanceVisitList"; import { MaintenanceVisitForm } from "~/components/maintenance-visits/MaintenanceVisitForm"; import { MaintenanceVisitDetailsView } from "~/components/maintenance-visits/MaintenanceVisitDetailsView"; import { getMaintenanceVisits, createMaintenanceVisit, updateMaintenanceVisit, deleteMaintenanceVisit, getMaintenanceVisitById } from "~/lib/maintenance-visit-management.server"; import { getCustomers } from "~/lib/customer-management.server"; import { getVehicles } from "~/lib/vehicle-management.server"; import { getMaintenanceTypesForSelect } from "~/lib/maintenance-type-management.server"; import { validateMaintenanceVisit } from "~/lib/validation"; import type { MaintenanceVisitWithRelations } from "~/types/database"; import { PAGINATION } from "~/lib/constants"; export const meta: MetaFunction = () => { return [ { title: "زيارات الصيانة - نظام إدارة صيانة السيارات" }, { name: "description", content: "إدارة زيارات الصيانة وتسجيل الأعمال المنجزة" }, ]; }; export async function loader({ request }: LoaderFunctionArgs) { const user = await protectMaintenanceRoute(request); const url = new URL(request.url); const searchQuery = url.searchParams.get("search") || ""; const paymentStatusFilter = url.searchParams.get("paymentStatus") || ""; const customerId = url.searchParams.get("customerId") ? parseInt(url.searchParams.get("customerId")!) : undefined; const vehicleId = url.searchParams.get("vehicleId") ? parseInt(url.searchParams.get("vehicleId")!) : undefined; const page = parseInt(url.searchParams.get("page") || "1"); const limit = parseInt(url.searchParams.get("limit") || PAGINATION.DEFAULT_PAGE_SIZE.toString()); // Get maintenance visits with filters const { visits, total, totalPages } = await getMaintenanceVisits( searchQuery, page, limit, vehicleId, customerId ); // Get customers, vehicles, and maintenance types for the form const { customers } = await getCustomers("", 1, 1000); // Get all customers const { vehicles } = await getVehicles("", 1, 1000); // Get all vehicles const maintenanceTypes = await getMaintenanceTypesForSelect(); // Get all maintenance types return json({ user, visits, customers, vehicles, maintenanceTypes, pagination: { page, limit, total, totalPages, }, searchQuery, paymentStatusFilter, customerId, vehicleId, }); } export async function action({ request }: ActionFunctionArgs) { const user = await protectMaintenanceRoute(request); const formData = await request.formData(); const intent = formData.get("intent") as string; try { switch (intent) { case "create": { // Debug: Log all form data console.log("Form data received:"); for (const [key, value] of formData.entries()) { console.log(`${key}: ${value}`); } // Check if the required fields are missing from form data if (!formData.has("customerId")) { console.error("customerId field is missing from form data!"); return json({ success: false, errors: { customerId: "العميل مطلوب" } }, { status: 400 }); } if (!formData.has("vehicleId")) { console.error("vehicleId field is missing from form data!"); return json({ success: false, errors: { vehicleId: "المركبة مطلوبة" } }, { status: 400 }); } if (!formData.has("description")) { console.error("description field is missing from form data!"); return json({ success: false, errors: { description: "وصف الصيانة مطلوب" } }, { status: 400 }); } if (!formData.has("cost")) { console.error("cost field is missing from form data!"); return json({ success: false, errors: { cost: "التكلفة مطلوبة" } }, { status: 400 }); } if (!formData.has("kilometers")) { console.error("kilometers field is missing from form data!"); return json({ success: false, errors: { kilometers: "عدد الكيلومترات مطلوب" } }, { status: 400 }); } const vehicleIdRaw = formData.get("vehicleId") as string; const customerIdRaw = formData.get("customerId") as string; const maintenanceJobsRaw = formData.get("maintenanceJobsData") as string; const costRaw = formData.get("cost") as string; const kilometersRaw = formData.get("kilometers") as string; const nextVisitDelayRaw = formData.get("nextVisitDelay") as string; console.log("Raw values:", { vehicleIdRaw, customerIdRaw, maintenanceJobsRaw, costRaw, kilometersRaw, nextVisitDelayRaw }); // Parse maintenance jobs let maintenanceJobs; try { maintenanceJobs = JSON.parse(maintenanceJobsRaw || "[]"); // Jobs are already in the correct format from the form } catch { maintenanceJobs = []; } // Check for empty strings and convert them to undefined for proper validation const data = { vehicleId: vehicleIdRaw && vehicleIdRaw.trim() !== "" ? parseInt(vehicleIdRaw) : undefined, customerId: customerIdRaw && customerIdRaw.trim() !== "" ? parseInt(customerIdRaw) : undefined, maintenanceJobs, description: formData.get("description") as string, cost: costRaw && costRaw.trim() !== "" ? parseFloat(costRaw) : undefined, paymentStatus: formData.get("paymentStatus") as string, kilometers: kilometersRaw && kilometersRaw.trim() !== "" ? parseInt(kilometersRaw) : undefined, nextVisitDelay: nextVisitDelayRaw && nextVisitDelayRaw.trim() !== "" ? parseInt(nextVisitDelayRaw) : undefined, visitDate: formData.get("visitDate") ? new Date(formData.get("visitDate") as string) : new Date(), }; console.log("Parsed data:", data); const validation = validateMaintenanceVisit(data); console.log("Validation result:", validation); if (!validation.isValid) { return json({ success: false, errors: validation.errors }, { status: 400 }); } await createMaintenanceVisit(data); return json({ success: true, message: "تم إنشاء زيارة الصيانة بنجاح" }); } case "update": { const id = parseInt(formData.get("id") as string); const maintenanceJobsRaw = formData.get("maintenanceJobsData") as string; // Parse maintenance jobs let maintenanceJobs; try { maintenanceJobs = JSON.parse(maintenanceJobsRaw || "[]"); // Jobs are already in the correct format from the form } catch { maintenanceJobs = []; } const data = { maintenanceJobs, description: formData.get("description") as string, cost: parseFloat(formData.get("cost") as string), paymentStatus: formData.get("paymentStatus") as string, kilometers: parseInt(formData.get("kilometers") as string), nextVisitDelay: parseInt(formData.get("nextVisitDelay") as string), visitDate: formData.get("visitDate") ? new Date(formData.get("visitDate") as string) : undefined, }; const validation = validateMaintenanceVisit(data); if (!validation.isValid) { return json({ success: false, errors: validation.errors }, { status: 400 }); } await updateMaintenanceVisit(id, data); return json({ success: true, message: "تم تحديث زيارة الصيانة بنجاح" }); } case "delete": { const id = parseInt(formData.get("id") as string); await deleteMaintenanceVisit(id); return json({ success: true, message: "تم حذف زيارة الصيانة بنجاح" }); } default: return json({ success: false, error: "إجراء غير صحيح" }, { status: 400 }); } } catch (error) { console.error("Maintenance visit action error:", error); return json( { success: false, error: error instanceof Error ? error.message : "حدث خطأ غير متوقع" }, { status: 500 } ); } } export default function MaintenanceVisits() { const { user, visits, customers, vehicles, maintenanceTypes, pagination, searchQuery, paymentStatusFilter, customerId, vehicleId } = useLoaderData(); const actionData = useActionData(); const navigation = useNavigation(); const [searchParams, setSearchParams] = useSearchParams(); const [showForm, setShowForm] = useState(false); const [showViewModal, setShowViewModal] = useState(false); const [editingVisit, setEditingVisit] = useState(null); const [viewingVisit, setViewingVisit] = useState(null); const [searchValue, setSearchValue] = useState(searchQuery); const [selectedPaymentStatus, setSelectedPaymentStatus] = useState(paymentStatusFilter); const [justOpenedForm, setJustOpenedForm] = useState(false); // Debounce search values to avoid too many requests const debouncedSearchValue = useDebounce(searchValue, 300); const debouncedPaymentStatus = useDebounce(selectedPaymentStatus, 300); const handleEdit = (visit: MaintenanceVisitWithRelations) => { console.log("Opening edit form for visit:", visit.id); setEditingVisit(visit); setJustOpenedForm(true); setShowForm(true); }; const handleView = (visit: MaintenanceVisitWithRelations) => { setViewingVisit(visit); setShowViewModal(true); }; const handleCloseForm = () => { setShowForm(false); setEditingVisit(null); }; const handleOpenCreateForm = () => { console.log("Opening create form"); setEditingVisit(null); setJustOpenedForm(true); setShowForm(true); }; const handleCloseViewModal = () => { setShowViewModal(false); setViewingVisit(null); }; // Handle search automatically when debounced values change useEffect(() => { if (debouncedSearchValue !== searchQuery || debouncedPaymentStatus !== paymentStatusFilter) { const newSearchParams = new URLSearchParams(searchParams); if (debouncedSearchValue) { newSearchParams.set("search", debouncedSearchValue); } else { newSearchParams.delete("search"); } if (debouncedPaymentStatus) { newSearchParams.set("paymentStatus", debouncedPaymentStatus); } else { newSearchParams.delete("paymentStatus"); } newSearchParams.set("page", "1"); // Reset to first page setSearchParams(newSearchParams); } }, [debouncedSearchValue, debouncedPaymentStatus, searchQuery, paymentStatusFilter, searchParams, setSearchParams]); // Clear search function const clearSearch = () => { setSearchValue(""); setSelectedPaymentStatus(""); }; // Handle pagination const handlePageChange = (page: number) => { const newSearchParams = new URLSearchParams(searchParams); newSearchParams.set("page", page.toString()); setSearchParams(newSearchParams); }; // Track when we've just completed a form submission const [wasSubmitting, setWasSubmitting] = useState(false); // Track navigation state changes useEffect(() => { if (navigation.state === "submitting") { setWasSubmitting(true); } else if (navigation.state === "idle" && wasSubmitting) { // We just finished submitting setWasSubmitting(false); // Close form only if the submission was successful if (actionData?.success && showForm) { console.log("Closing form after successful submission"); setShowForm(false); setEditingVisit(null); } } }, [navigation.state, wasSubmitting, actionData?.success, showForm]); // Reset the justOpenedForm flag after a short delay useEffect(() => { if (justOpenedForm) { console.log("Setting timer to reset justOpenedForm flag"); const timer = setTimeout(() => { console.log("Resetting justOpenedForm flag"); setJustOpenedForm(false); }, 500); return () => clearTimeout(timer); } }, [justOpenedForm]); return (
{/* Header */}
زيارات الصيانة
إدارة زيارات الصيانة وتسجيل الأعمال المنجزة {customerId && ( مفلترة حسب العميل )} {vehicleId && ( مفلترة حسب المركبة )}
{/* Success/Error Messages */} {actionData?.success && (
{actionData.message}
)} {actionData?.error && (
{actionData.error}
)} {/* Search and Filters */}
setSearchValue(e.target.value)} startIcon={ } endIcon={ searchValue && ( ) } />