282 lines
9.7 KiB
TypeScript
282 lines
9.7 KiB
TypeScript
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>
|
||
);
|
||
} |