This commit is contained in:
2026-01-23 20:35:40 +03:00
parent cf3b0e48ec
commit 66c151653e
137 changed files with 41495 additions and 0 deletions

View File

@@ -0,0 +1,253 @@
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>
);
}