Files
car_mms/app/components/vehicles/VehicleList.tsx
2026-03-08 14:27:16 +03:00

400 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Form, Link } from "@remix-run/react";
import { useState, useEffect } from "react";
import { DataTable, Pagination } from "~/components/ui/DataTable";
import { Button } from "~/components/ui/Button";
import { Flex } from "~/components/layout/Flex";
import { useSettings } from "~/contexts/SettingsContext";
import { TRANSMISSION_TYPES, FUEL_TYPES, USE_TYPES, BODY_TYPES } from "~/lib/constants";
import type { VehicleWithOwner } from "~/types/database";
interface VehicleListProps {
vehicles: VehicleWithOwner[];
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
onEditVehicle: (vehicle: VehicleWithOwner) => void;
onViewVehicle?: (vehicle: VehicleWithOwner) => void;
isLoading: boolean;
actionData?: any;
}
export function VehicleList({
vehicles,
currentPage,
totalPages,
onPageChange,
onEditVehicle,
onViewVehicle,
isLoading,
actionData,
}: VehicleListProps) {
const { formatDate } = useSettings();
const [deletingVehicleId, setDeletingVehicleId] = useState<number | null>(null);
// Reset deleting state when delete action completes
useEffect(() => {
if (actionData?.success && actionData.action === "delete") {
setDeletingVehicleId(null);
}
}, [actionData]);
// Helper functions to get display labels
const getTransmissionLabel = (value: string) => {
return TRANSMISSION_TYPES.find(t => t.value === value)?.label || value;
};
const getFuelLabel = (value: string) => {
return FUEL_TYPES.find(f => f.value === value)?.label || value;
};
const getUseTypeLabel = (value: string) => {
return USE_TYPES.find(u => u.value === value)?.label || value;
};
const getBodyTypeLabel = (value: string) => {
return BODY_TYPES.find(b => b.value === value)?.label || value;
};
const columns = [
{
key: "plateNumber",
header: "رقم اللوحة",
render: (vehicle: VehicleWithOwner) => (
<div>
<Link
to={`/vehicles/${vehicle.id}`}
className="font-mono text-lg font-medium text-blue-600 hover:text-blue-800"
>
{vehicle.plateNumber}
</Link>
<div className="text-sm text-gray-500">
المركبة رقم: {vehicle.id}
</div>
</div>
),
},
{
key: "vehicle",
header: "تفاصيل المركبة",
render: (vehicle: VehicleWithOwner) => (
<div>
<div className="font-medium text-gray-900">
{vehicle.manufacturer} {vehicle.model}
</div>
<div className="text-sm text-gray-600">
{vehicle.year} {getBodyTypeLabel(vehicle.bodyType)}
</div>
{vehicle.trim && (
<div className="text-sm text-gray-500">
فئة: {vehicle.trim}
</div>
)}
</div>
),
},
{
key: "specifications",
header: "المواصفات",
render: (vehicle: VehicleWithOwner) => (
<div className="space-y-1">
<div className="text-sm text-gray-900">
{getTransmissionLabel(vehicle.transmission)}
</div>
<div className="text-sm text-gray-600">
{getFuelLabel(vehicle.fuel)}
</div>
{vehicle.cylinders && (
<div className="text-sm text-gray-500">
{vehicle.cylinders} أسطوانة
</div>
)}
{vehicle.engineDisplacement && (
<div className="text-sm text-gray-500">
{vehicle.engineDisplacement}L
</div>
)}
</div>
),
},
{
key: "owner",
header: "المالك",
render: (vehicle: VehicleWithOwner) => (
<div>
<Link
to={`/customers/${vehicle.owner.id}`}
className="font-medium text-blue-600 hover:text-blue-800"
>
{vehicle.owner.name}
</Link>
{vehicle.owner.phone && (
<div className="text-sm text-gray-500" dir="ltr">
{vehicle.owner.phone}
</div>
)}
</div>
),
},
{
key: "useType",
header: "نوع الاستخدام",
render: (vehicle: VehicleWithOwner) => (
<div className="text-sm text-gray-900">
{getUseTypeLabel(vehicle.useType)}
</div>
),
},
{
key: "maintenance",
header: "الصيانة",
render: (vehicle: VehicleWithOwner) => (
<div className="space-y-1">
{vehicle.lastVisitDate ? (
<div className="text-sm text-gray-900">
آخر زيارة: {formatDate(vehicle.lastVisitDate)}
</div>
) : (
<div className="text-sm text-gray-400">
لا توجد زيارات
</div>
)}
{vehicle.suggestedNextVisitDate && (
<div className="text-sm text-orange-600">
الزيارة التالية: {formatDate(vehicle.suggestedNextVisitDate)}
</div>
)}
</div>
),
},
{
key: "createdDate",
header: "تاريخ التسجيل",
render: (vehicle: VehicleWithOwner) => (
<div className="text-sm text-gray-600">
{formatDate(vehicle.createdDate)}
</div>
),
},
{
key: "actions",
header: "الإجراءات",
render: (vehicle: VehicleWithOwner) => (
<Flex className="flex-wrap gap-2">
{onViewVehicle ? (
<Button
size="sm"
variant="outline"
onClick={() => onViewVehicle(vehicle)}
disabled={isLoading}
>
عرض
</Button>
) : (
<Link to={`/vehicles/${vehicle.id}`}>
<Button
size="sm"
variant="outline"
disabled={isLoading}
>
عرض
</Button>
</Link>
)}
<Button
size="sm"
variant="outline"
onClick={() => onEditVehicle(vehicle)}
disabled={isLoading}
>
تعديل
</Button>
<Form method="post" className="inline">
<input type="hidden" name="_action" value="delete" />
<input type="hidden" name="id" value={vehicle.id} />
<Button
type="submit"
size="sm"
variant="outline"
className="text-red-600 border-red-300 hover:bg-red-50"
disabled={isLoading || deletingVehicleId === vehicle.id}
onClick={(e) => {
e.preventDefault();
if (window.confirm("هل أنت متأكد من حذف هذه المركبة؟")) {
setDeletingVehicleId(vehicle.id);
(e.target as HTMLButtonElement).form?.submit();
}
}}
>
{deletingVehicleId === vehicle.id ? "جاري الحذف..." : "حذف"}
</Button>
</Form>
</Flex>
),
},
];
return (
<div className="space-y-4">
<div className="bg-white rounded-lg shadow-sm border">
{vehicles.length === 0 ? (
<div className="text-center py-12">
<div className="text-gray-400 text-lg mb-2">🚗</div>
<h3 className="text-lg font-medium text-gray-900 mb-2">
لا توجد مركبات
</h3>
<p className="text-gray-500">
لم يتم العثور على أي مركبات. قم بإضافة مركبة جديدة للبدء.
</p>
</div>
) : (
<>
{/* Desktop Table View */}
<div className="hidden lg:block">
<DataTable
data={vehicles}
columns={columns}
loading={isLoading}
emptyMessage="لم يتم العثور على أي مركبات"
/>
</div>
{/* Mobile Card View */}
<div className="lg:hidden divide-y divide-gray-200">
{vehicles.map((vehicle) => (
<div key={vehicle.id} className="p-4 hover:bg-gray-50">
<div className="space-y-3">
<div className="flex justify-between items-start">
<div className="flex-1">
<Link
to={`/vehicles/${vehicle.id}`}
className="font-mono text-lg font-medium text-blue-600 hover:text-blue-800"
>
{vehicle.plateNumber}
</Link>
<div className="text-sm font-medium text-gray-900 mt-1">
{vehicle.manufacturer} {vehicle.model} ({vehicle.year})
</div>
{vehicle.trim && (
<div className="text-sm text-gray-500">
فئة: {vehicle.trim}
</div>
)}
</div>
</div>
<div className="grid grid-cols-2 gap-2 text-sm">
<div>
<span className="text-gray-600">النوع: </span>
<span className="text-gray-900">{getBodyTypeLabel(vehicle.bodyType)}</span>
</div>
<div>
<span className="text-gray-600">الوقود: </span>
<span className="text-gray-900">{getFuelLabel(vehicle.fuel)}</span>
</div>
<div>
<span className="text-gray-600">ناقل الحركة: </span>
<span className="text-gray-900">{getTransmissionLabel(vehicle.transmission)}</span>
</div>
<div>
<span className="text-gray-600">الاستخدام: </span>
<span className="text-gray-900">{getUseTypeLabel(vehicle.useType)}</span>
</div>
</div>
<div className="text-sm">
<Link
to={`/customers/${vehicle.owner.id}`}
className="font-medium text-blue-600 hover:text-blue-800"
>
المالك: {vehicle.owner.name}
</Link>
{vehicle.owner.phone && (
<div className="text-gray-500 mt-1" dir="ltr">
{vehicle.owner.phone}
</div>
)}
</div>
{vehicle.lastVisitDate && (
<div className="text-sm text-gray-600">
آخر زيارة: {formatDate(vehicle.lastVisitDate)}
</div>
)}
<Flex className="flex-wrap gap-2 pt-2">
{onViewVehicle ? (
<Button
size="sm"
variant="outline"
onClick={() => onViewVehicle(vehicle)}
disabled={isLoading}
className="flex-1 min-w-[80px]"
>
عرض
</Button>
) : (
<Link to={`/vehicles/${vehicle.id}`} className="flex-1 min-w-[80px]">
<Button
size="sm"
variant="outline"
disabled={isLoading}
className="w-full"
>
عرض
</Button>
</Link>
)}
<Button
size="sm"
variant="outline"
onClick={() => onEditVehicle(vehicle)}
disabled={isLoading}
className="flex-1 min-w-[80px]"
>
تعديل
</Button>
<Form method="post" className="flex-1 min-w-[80px]">
<input type="hidden" name="_action" value="delete" />
<input type="hidden" name="id" value={vehicle.id} />
<Button
type="submit"
size="sm"
variant="outline"
className="text-red-600 border-red-300 hover:bg-red-50 w-full"
disabled={isLoading || deletingVehicleId === vehicle.id}
onClick={(e) => {
e.preventDefault();
if (window.confirm("هل أنت متأكد من حذف هذه المركبة؟")) {
setDeletingVehicleId(vehicle.id);
(e.target as HTMLButtonElement).form?.submit();
}
}}
>
{deletingVehicleId === vehicle.id ? "جاري الحذف..." : "حذف"}
</Button>
</Form>
</Flex>
</div>
</div>
))}
</div>
</>
)}
</div>
{totalPages > 1 && (
<div className="flex justify-center">
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={onPageChange}
/>
</div>
)}
</div>
);
}