253 lines
7.7 KiB
TypeScript
253 lines
7.7 KiB
TypeScript
import { useState } from "react";
|
||
import { Link, Form } from "@remix-run/react";
|
||
import { Button } from "~/components/ui/Button";
|
||
import { Text } from "~/components/ui/Text";
|
||
import { DataTable } from "~/components/ui/DataTable";
|
||
import type { MaintenanceVisitWithRelations } from "~/types/database";
|
||
import { PAYMENT_STATUS_NAMES } from "~/lib/constants";
|
||
import { useSettings } from "~/contexts/SettingsContext";
|
||
|
||
interface MaintenanceVisitListProps {
|
||
visits: MaintenanceVisitWithRelations[];
|
||
onEdit?: (visit: MaintenanceVisitWithRelations) => void;
|
||
onView?: (visit: MaintenanceVisitWithRelations) => void;
|
||
}
|
||
|
||
export function MaintenanceVisitList({
|
||
visits,
|
||
onEdit,
|
||
onView
|
||
}: MaintenanceVisitListProps) {
|
||
const { formatDate, formatCurrency, formatNumber, formatDateTime } = useSettings();
|
||
const [deleteVisitId, setDeleteVisitId] = useState<number | null>(null);
|
||
|
||
const handleDelete = (visitId: number) => {
|
||
setDeleteVisitId(visitId);
|
||
};
|
||
|
||
const confirmDelete = () => {
|
||
if (deleteVisitId) {
|
||
// Submit delete form
|
||
const form = document.createElement('form');
|
||
form.method = 'post';
|
||
form.style.display = 'none';
|
||
|
||
const intentInput = document.createElement('input');
|
||
intentInput.type = 'hidden';
|
||
intentInput.name = 'intent';
|
||
intentInput.value = 'delete';
|
||
|
||
const idInput = document.createElement('input');
|
||
idInput.type = 'hidden';
|
||
idInput.name = 'id';
|
||
idInput.value = deleteVisitId.toString();
|
||
|
||
form.appendChild(intentInput);
|
||
form.appendChild(idInput);
|
||
document.body.appendChild(form);
|
||
form.submit();
|
||
}
|
||
};
|
||
|
||
const getPaymentStatusColor = (status: string) => {
|
||
switch (status) {
|
||
case 'paid':
|
||
return 'text-green-600 bg-green-50';
|
||
case 'pending':
|
||
return 'text-yellow-600 bg-yellow-50';
|
||
case 'partial':
|
||
return 'text-blue-600 bg-blue-50';
|
||
case 'cancelled':
|
||
return 'text-red-600 bg-red-50';
|
||
default:
|
||
return 'text-gray-600 bg-gray-50';
|
||
}
|
||
};
|
||
|
||
const columns = [
|
||
{
|
||
key: 'visitDate',
|
||
header: 'تاريخ الزيارة',
|
||
render: (visit: MaintenanceVisitWithRelations) => (
|
||
<div>
|
||
<Text weight="medium">
|
||
{formatDate(visit.visitDate)}
|
||
</Text>
|
||
<Text size="sm" color="secondary">
|
||
{formatDateTime(visit.visitDate).split(' ')[1]}
|
||
</Text>
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
key: 'vehicle',
|
||
header: 'المركبة',
|
||
render: (visit: MaintenanceVisitWithRelations) => (
|
||
<div>
|
||
<Text weight="medium">{visit.vehicle.plateNumber}</Text>
|
||
<Text size="sm" color="secondary">
|
||
{visit.vehicle.manufacturer} {visit.vehicle.model} ({visit.vehicle.year})
|
||
</Text>
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
key: 'customer',
|
||
header: 'العميل',
|
||
render: (visit: MaintenanceVisitWithRelations) => (
|
||
<div>
|
||
<Text weight="medium">{visit.customer.name}</Text>
|
||
{visit.customer.phone && (
|
||
<Text size="sm" color="secondary">{visit.customer.phone}</Text>
|
||
)}
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
key: 'maintenanceJobs',
|
||
header: 'أعمال الصيانة',
|
||
render: (visit: MaintenanceVisitWithRelations) => {
|
||
try {
|
||
const jobs = JSON.parse(visit.maintenanceJobs);
|
||
return (
|
||
<div>
|
||
<Text weight="medium">
|
||
{jobs.length > 1 ? `${jobs.length} أعمال صيانة` : jobs[0]?.job || 'غير محدد'}
|
||
</Text>
|
||
<Text size="sm" color="secondary" className="line-clamp-2">
|
||
{visit.description}
|
||
</Text>
|
||
</div>
|
||
);
|
||
} catch {
|
||
return (
|
||
<div>
|
||
<Text weight="medium">غير محدد</Text>
|
||
<Text size="sm" color="secondary" className="line-clamp-2">
|
||
{visit.description}
|
||
</Text>
|
||
</div>
|
||
);
|
||
}
|
||
},
|
||
},
|
||
{
|
||
key: 'cost',
|
||
header: 'التكلفة',
|
||
render: (visit: MaintenanceVisitWithRelations) => (
|
||
<Text weight="medium" className="font-mono">
|
||
{formatCurrency(visit.cost)}
|
||
</Text>
|
||
),
|
||
},
|
||
{
|
||
key: 'paymentStatus',
|
||
header: 'حالة الدفع',
|
||
render: (visit: MaintenanceVisitWithRelations) => (
|
||
<span className={`inline-flex px-2 py-1 text-xs font-medium rounded-full ${getPaymentStatusColor(visit.paymentStatus)}`}>
|
||
{PAYMENT_STATUS_NAMES[visit.paymentStatus as keyof typeof PAYMENT_STATUS_NAMES]}
|
||
</span>
|
||
),
|
||
},
|
||
{
|
||
key: 'kilometers',
|
||
header: 'الكيلومترات',
|
||
render: (visit: MaintenanceVisitWithRelations) => (
|
||
<Text className="font-mono">
|
||
{formatNumber(visit.kilometers)} كم
|
||
</Text>
|
||
),
|
||
},
|
||
{
|
||
key: 'actions',
|
||
header: 'الإجراءات',
|
||
render: (visit: MaintenanceVisitWithRelations) => (
|
||
<div className="flex gap-2">
|
||
{onView ? (
|
||
<Button
|
||
size="sm"
|
||
variant="outline"
|
||
onClick={() => onView(visit)}
|
||
>
|
||
عرض
|
||
</Button>
|
||
) : (
|
||
<Link to={`/maintenance-visits/${visit.id}`}>
|
||
<Button size="sm" variant="outline">
|
||
عرض
|
||
</Button>
|
||
</Link>
|
||
)}
|
||
{onEdit && (
|
||
<Button
|
||
size="sm"
|
||
variant="outline"
|
||
onClick={() => onEdit(visit)}
|
||
>
|
||
تعديل
|
||
</Button>
|
||
)}
|
||
<Button
|
||
size="sm"
|
||
variant="outline"
|
||
className="text-red-600 border-red-300 hover:bg-red-50"
|
||
onClick={() => handleDelete(visit.id)}
|
||
>
|
||
حذف
|
||
</Button>
|
||
</div>
|
||
),
|
||
},
|
||
];
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
<div className="bg-white rounded-lg shadow-sm border">
|
||
{visits.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>
|
||
) : (
|
||
<DataTable
|
||
data={visits}
|
||
columns={columns}
|
||
emptyMessage="لم يتم العثور على أي زيارات صيانة"
|
||
/>
|
||
)}
|
||
</div>
|
||
|
||
{/* Delete Confirmation Modal */}
|
||
{deleteVisitId !== null && (
|
||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||
<div className="bg-white rounded-lg p-6 max-w-md w-full mx-4">
|
||
<h3 className="text-lg font-medium text-gray-900 mb-4">تأكيد الحذف</h3>
|
||
<p className="text-gray-600 mb-6">
|
||
هل أنت متأكد من حذف زيارة الصيانة هذه؟ سيتم حذف جميع البيانات المرتبطة بها نهائياً.
|
||
</p>
|
||
<div className="flex gap-3 justify-end">
|
||
<Button
|
||
variant="outline"
|
||
onClick={() => setDeleteVisitId(null)}
|
||
>
|
||
إلغاء
|
||
</Button>
|
||
<Button
|
||
variant="outline"
|
||
className="text-red-600 border-red-300 hover:bg-red-50"
|
||
onClick={confirmDelete}
|
||
>
|
||
حذف
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
} |