uup
This commit is contained in:
253
app/components/maintenance-visits/MaintenanceVisitList.tsx
Normal file
253
app/components/maintenance-visits/MaintenanceVisitList.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user