krona-front/src/features/operations/WorkOrdersView.tsx

181 lines
8 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 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>
);
};