car_mms/app/components/customers/CustomerList.tsx
2025-09-11 14:22:27 +03:00

282 lines
9.7 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 } from "@remix-run/react";
import { useState, useEffect } from "react";
import { Button } from "~/components/ui/Button";
import { Flex } from "~/components/layout/Flex";
import { useSettings } from "~/contexts/SettingsContext";
import type { CustomerWithVehicles } from "~/types/database";
interface CustomerListProps {
customers: CustomerWithVehicles[];
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
onViewCustomer: (customer: CustomerWithVehicles) => void;
onEditCustomer: (customer: CustomerWithVehicles) => void;
isLoading: boolean;
actionData?: any;
}
export function CustomerList({
customers,
currentPage,
totalPages,
onPageChange,
onViewCustomer,
onEditCustomer,
isLoading,
actionData,
}: CustomerListProps) {
const { formatDate } = useSettings();
const [deletingCustomerId, setDeletingCustomerId] = useState<number | null>(null);
// Reset deleting state when delete action completes
useEffect(() => {
if (actionData?.success && actionData.action === "delete") {
setDeletingCustomerId(null);
}
}, [actionData]);
const columns = [
{
key: "name",
header: "اسم العميل",
render: (customer: CustomerWithVehicles) => (
<div>
<div className="font-medium text-gray-900">{customer.name}</div>
<div className="text-sm text-gray-500">
{/* العميل رقم: {customer.id} */}
</div>
</div>
),
},
{
key: "contact",
header: "معلومات الاتصال",
render: (customer: CustomerWithVehicles) => (
<div className="space-y-1">
{customer.phone && (
<div className="text-sm text-gray-900" dir="ltr">
📞 {customer.phone}
</div>
)}
{customer.email && (
<div className="text-sm text-gray-600" dir="ltr">
{customer.email}
</div>
)}
{!customer.phone && !customer.email && (
<div className="text-sm text-gray-400">
لا توجد معلومات اتصال
</div>
)}
</div>
),
},
{
key: "address",
header: "العنوان",
render: (customer: CustomerWithVehicles) => (
<div className="text-sm text-gray-900">
{customer.address || (
<span className="text-gray-400">غير محدد</span>
)}
</div>
),
},
{
key: "vehicles",
header: "المركبات",
render: (customer: CustomerWithVehicles) => (
<div>
<div className="font-medium text-gray-900">
{customer.vehicles.length} مركبة
</div>
{customer.vehicles.length > 0 && (
<div className="text-sm text-gray-500 mt-1">
{customer.vehicles.slice(0, 2).map((vehicle) => (
<div key={vehicle.id}>
{vehicle.plateNumber} - {vehicle.manufacturer} {vehicle.model}
</div>
))}
{customer.vehicles.length > 2 && (
<div className="text-gray-400">
و {customer.vehicles.length - 2} مركبة أخرى...
</div>
)}
</div>
)}
</div>
),
},
{
key: "visits",
header: "الزيارات",
render: (customer: CustomerWithVehicles) => (
<div>
<div className="font-medium text-gray-900">
{customer.maintenanceVisits.length} زيارة
</div>
{customer.maintenanceVisits.length > 0 && (
<div className="text-sm text-gray-500">
آخر زيارة: {formatDate(customer.maintenanceVisits[0].visitDate)}
</div>
)}
</div>
),
},
{
key: "createdDate",
header: "تاريخ الإنشاء",
render: (customer: CustomerWithVehicles) => (
<div className="text-sm text-gray-600">
{formatDate(customer.createdDate)}
</div>
),
},
{
key: "actions",
header: "الإجراءات",
render: (customer: CustomerWithVehicles) => (
<Flex className="flex-wrap gap-2">
<Button
size="sm"
variant="outline"
className="bg-blue-50 text-blue-600 border-blue-300 hover:bg-blue-100"
disabled={isLoading}
onClick={() => onViewCustomer(customer)}
>
عرض
</Button>
<Button
size="sm"
variant="outline"
onClick={() => onEditCustomer(customer)}
disabled={isLoading}
>
تعديل
</Button>
<Form method="post" className="inline">
<input type="hidden" name="_action" value="delete" />
<input type="hidden" name="id" value={customer.id} />
<Button
type="submit"
size="sm"
variant="outline"
className="text-red-600 border-red-300 hover:bg-red-50"
disabled={isLoading || deletingCustomerId === customer.id}
onClick={(e) => {
e.preventDefault();
if (window.confirm("هل أنت متأكد من حذف هذا العميل؟")) {
setDeletingCustomerId(customer.id);
(e.target as HTMLButtonElement).form?.submit();
}
}}
>
{deletingCustomerId === customer.id ? "جاري الحذف..." : "حذف"}
</Button>
</Form>
</Flex>
),
},
];
return (
<div className="bg-white rounded-lg shadow-sm border">
{customers.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>
) : (
<>
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
{columns.map((column) => (
<th
key={column.key}
className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{column.header}
</th>
))}
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{customers.map((customer) => (
<tr key={customer.id} className="hover:bg-gray-50">
{columns.map((column) => (
<td
key={`${customer.id}-${column.key}`}
className="px-6 py-4 whitespace-nowrap text-sm text-gray-900"
>
{column.render ? column.render(customer) : String(customer[column.key as keyof CustomerWithVehicles] || '')}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
{/* Pagination */}
{totalPages > 1 && (
<div className="px-4 py-3 border-t border-gray-200 bg-gray-50">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2 space-x-reverse">
<button
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage === 1 || isLoading}
className="px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
السابق
</button>
<div className="flex items-center space-x-1 space-x-reverse">
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
const page = i + 1;
return (
<button
key={page}
onClick={() => onPageChange(page)}
disabled={isLoading}
className={`px-3 py-2 text-sm font-medium rounded-md ${
currentPage === page
? 'bg-blue-600 text-white'
: 'text-gray-500 bg-white border border-gray-300 hover:bg-gray-50'
} disabled:opacity-50 disabled:cursor-not-allowed`}
>
{page}
</button>
);
})}
</div>
<button
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage === totalPages || isLoading}
className="px-3 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
التالي
</button>
</div>
<p className="text-sm text-gray-500">
صفحة {currentPage} من {totalPages}
</p>
</div>
</div>
)}
</>
)}
</div>
);
}