car_mms/app/components/maintenance-visits/MaintenanceVisitList.tsx
2025-09-11 14:22:27 +03:00

253 lines
7.7 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 { 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>
);
}