181 lines
8 KiB
TypeScript
181 lines
8 KiB
TypeScript
import React, { useState } from 'react';
|
||
import { useDemoStore, WorkOrder } from '../../mock/store';
|
||
import { Badge } from '../../shared/components/Badge';
|
||
import { Button } from '../../shared/components/Button';
|
||
import { Modal } from '../../shared/components/Modal';
|
||
import { formatCurrency } from '../../shared/utils/formatters';
|
||
import { Car, Wrench, User, Phone, CheckCircle2, Play, Plus, Clock } from 'lucide-react';
|
||
|
||
export const WorkOrdersView: React.FC = () => {
|
||
const { workOrders, updateWorkOrderStatus, setIsQuickActionsOpen } = useDemoStore();
|
||
const [selectedOrder, setSelectedOrder] = useState<WorkOrder | null>(null);
|
||
|
||
const getStatusBadge = (status: WorkOrder['status']) => {
|
||
switch (status) {
|
||
case 'in_progress':
|
||
return <Badge variant="warning" size="sm">В работе на посту</Badge>;
|
||
case 'ready':
|
||
return <Badge variant="gold" size="sm">Готов к выдаче</Badge>;
|
||
case 'paid':
|
||
return <Badge variant="green" size="sm">Оплачен и выдан</Badge>;
|
||
case 'draft':
|
||
default:
|
||
return <Badge variant="neutral" size="sm">Черновик / Заезд</Badge>;
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-4 animate-fade-in">
|
||
<div className="flex items-center justify-between bg-krona-surface p-3.5 rounded-xl border border-krona-border shadow-krona-soft">
|
||
<div>
|
||
<h2 className="text-sm font-bold text-krona-primary">
|
||
Заказ-наряды автокомплекса и постов
|
||
</h2>
|
||
<p className="text-xs text-krona-secondary">
|
||
Учет автомобилей, выполненных работ, списанных запчастей и нормо-часов
|
||
</p>
|
||
</div>
|
||
|
||
<Button variant="primary" size="sm" onClick={() => setIsQuickActionsOpen(true)}>
|
||
<Plus className="w-3.5 h-3.5 mr-1.5" />
|
||
Новый заказ-наряд
|
||
</Button>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
{workOrders.map((wo) => {
|
||
const totalAmount = wo.partsAmount + wo.laborAmount;
|
||
|
||
return (
|
||
<div
|
||
key={wo.id}
|
||
onClick={() => setSelectedOrder(wo)}
|
||
className="p-4 rounded-xl border border-krona-border bg-krona-surface hover:border-krona-border-strong shadow-krona-soft cursor-pointer transition-all space-y-3"
|
||
>
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-2">
|
||
<div className="p-2 rounded-lg bg-krona-workspace text-krona-info border border-krona-border">
|
||
<Car className="w-4 h-4" />
|
||
</div>
|
||
<div>
|
||
<h4 className="text-sm font-bold text-krona-primary">{wo.vehicle}</h4>
|
||
<span className="text-xs text-krona-secondary font-mono">{wo.id} • {wo.bay}</span>
|
||
</div>
|
||
</div>
|
||
|
||
{getStatusBadge(wo.status)}
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-2 text-xs py-1">
|
||
<div>
|
||
<span className="text-krona-secondary block text-[11px]">Владелец:</span>
|
||
<span className="font-medium text-krona-primary">{wo.ownerName}</span>
|
||
</div>
|
||
<div>
|
||
<span className="text-krona-secondary block text-[11px]">Мастер поста:</span>
|
||
<span className="font-medium text-krona-primary">{wo.masterName}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="p-2.5 rounded-lg bg-krona-workspace text-xs space-y-1">
|
||
<span className="text-[11px] font-semibold text-krona-secondary uppercase">
|
||
Работы и услуги:
|
||
</span>
|
||
<ul className="list-disc list-inside space-y-0.5 text-krona-primary">
|
||
{wo.services.map((srv, i) => (
|
||
<li key={i} className="truncate">{srv}</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
|
||
<div className="pt-2 border-t border-krona-border/60 flex items-center justify-between text-xs">
|
||
<div>
|
||
<span className="text-krona-secondary block text-[10px]">Итоговая стоимость:</span>
|
||
<span className="text-sm font-bold text-krona-green">{formatCurrency(totalAmount)}</span>
|
||
</div>
|
||
|
||
<div className="flex gap-2">
|
||
{wo.status === 'in_progress' && (
|
||
<Button
|
||
variant="primary"
|
||
size="sm"
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
updateWorkOrderStatus(wo.id, 'ready');
|
||
}}
|
||
>
|
||
Готово
|
||
</Button>
|
||
)}
|
||
{wo.status === 'ready' && (
|
||
<Button
|
||
variant="gold"
|
||
size="sm"
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
updateWorkOrderStatus(wo.id, 'paid');
|
||
}}
|
||
>
|
||
Принять оплату
|
||
</Button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
{/* Work Order Detail Modal */}
|
||
{selectedOrder && (
|
||
<Modal
|
||
isOpen={Boolean(selectedOrder)}
|
||
onClose={() => setSelectedOrder(null)}
|
||
title={`Заказ-наряд ${selectedOrder.id}`}
|
||
subtitle={`${selectedOrder.vehicle} • ${selectedOrder.bay}`}
|
||
maxWidth="md"
|
||
footer={
|
||
<Button variant="secondary" onClick={() => setSelectedOrder(null)}>
|
||
Закрыть
|
||
</Button>
|
||
}
|
||
>
|
||
<div className="space-y-4 text-xs">
|
||
<div className="grid grid-cols-2 gap-3 p-3 rounded-lg bg-krona-workspace">
|
||
<div>
|
||
<span className="text-krona-secondary block">Владелец:</span>
|
||
<span className="font-semibold text-krona-primary">{selectedOrder.ownerName}</span>
|
||
<span className="text-krona-secondary block text-[11px]">{selectedOrder.phone}</span>
|
||
</div>
|
||
<div>
|
||
<span className="text-krona-secondary block">Ответственный специалист:</span>
|
||
<span className="font-semibold text-krona-primary">{selectedOrder.masterName}</span>
|
||
<span className="text-krona-secondary block text-[11px]">{selectedOrder.bay}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<span className="font-semibold text-krona-primary block mb-1">Калькуляция:</span>
|
||
<div className="p-3 border border-krona-border rounded-lg space-y-1.5">
|
||
<div className="flex justify-between">
|
||
<span className="text-krona-secondary">Работы мастера:</span>
|
||
<span className="font-medium">{formatCurrency(selectedOrder.laborAmount)}</span>
|
||
</div>
|
||
<div className="flex justify-between">
|
||
<span className="text-krona-secondary">Материалы и запчасти:</span>
|
||
<span className="font-medium">{formatCurrency(selectedOrder.partsAmount)}</span>
|
||
</div>
|
||
<div className="flex justify-between pt-1 border-t border-krona-border font-bold text-sm">
|
||
<span>ИТОГО К ОПЛАТЕ:</span>
|
||
<span className="text-krona-green">
|
||
{formatCurrency(selectedOrder.laborAmount + selectedOrder.partsAmount)}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Modal>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|