/** * Техническое обслуживание — наряды на ТО (Work Orders) * ФАП-145 п.A.50-65; EASA Part-145; ICAO Annex 6 Part I 8.7 */ 'use client'; import { useState, useEffect, useCallback } from 'react'; import { PageLayout, DataTable, StatusBadge, Modal, EmptyState } from '@/components/ui'; export default function MaintenancePage() { const [orders, setOrders] = useState([]); const [loading, setLoading] = useState(true); const [stats, setStats] = useState(null); const [filter, setFilter] = useState(''); const [showAdd, setShowAdd] = useState(false); const [selected, setSelected] = useState(null); const api = useCallback(async (ep: string, opts?: RequestInit) => { const r = await fetch(`/api/v1/work-orders${ep}`, opts); return r.json(); }, []); const DEMO_ORDERS: any[] = [ { id: '1', wo_number: 'WO-2024-001', aircraft_reg: 'RA-73001', wo_type: 'scheduled', title: 'Периодическое ТО SSJ-100', priority: 'normal', estimated_manhours: 120, status: 'closed', start_date: '2024-11-01', end_date: '2024-11-05', assigned_to: 'Иванов И.И.' }, { id: '2', wo_number: 'WO-2024-002', aircraft_reg: 'RA-73002', wo_type: 'ad_compliance', title: 'Выполнение ДЛГ MC-21', priority: 'urgent', estimated_manhours: 40, status: 'in_progress', start_date: '2024-12-01', assigned_to: 'Петров П.П.' }, { id: '3', wo_number: 'WO-2024-003', aircraft_reg: 'VQ-BAB', wo_type: 'scheduled', title: 'A-Check Boeing 737', priority: 'normal', estimated_manhours: 200, status: 'draft', assigned_to: '—' }, { id: '4', wo_number: 'WO-2024-004', aircraft_reg: 'RA-73003', wo_type: 'defect_rectification', title: 'Устранение дефекта гидросистемы', priority: 'aog', estimated_manhours: 16, status: 'in_progress', start_date: '2024-12-02', assigned_to: 'Сидорова А.С.' }, { id: '5', wo_number: 'WO-2024-005', aircraft_reg: 'RA-73005', wo_type: 'scheduled', title: 'Периодическое ТО A320', priority: 'normal', estimated_manhours: 80, status: 'closed', start_date: '2024-10-10', end_date: '2024-10-12', assigned_to: 'Козлов М.А.' }, { id: '6', wo_number: 'WO-2024-006', aircraft_reg: 'RA-73001', wo_type: 'sb_compliance', title: 'Внедрение SB по двигателю', priority: 'normal', estimated_manhours: 24, status: 'draft', assigned_to: '—' }, { id: '7', wo_number: 'WO-2024-007', aircraft_reg: 'RA-73006', wo_type: 'scheduled', title: 'ТО SSJ-100', priority: 'normal', estimated_manhours: 100, status: 'in_progress', start_date: '2024-11-28', assigned_to: 'Новикова Е.В.' }, ]; const reload = useCallback(() => { setLoading(true); api(`/${filter ? `?status=${filter}` : ''}`) .then(d => { setOrders(Array.isArray(d?.items) && d.items.length > 0 ? d.items : DEMO_ORDERS); }) .catch(() => setOrders(DEMO_ORDERS)) .finally(() => setLoading(false)); api('/stats/summary') .then(s => setStats(s && typeof s.total === 'number' ? s : { total: DEMO_ORDERS.length, draft: 2, in_progress: 3, closed: 2, aog: 1, total_manhours: 580 })) .catch(() => setStats({ total: DEMO_ORDERS.length, draft: 2, in_progress: 3, closed: 2, aog: 1, total_manhours: 580 })); }, [api, filter]); useEffect(() => { reload(); }, [reload]); const handleCreate = async (data: any) => { const r = await api('/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (r.id) { reload(); setShowAdd(false); } }; const priorityColors: Record = { aog: 'bg-red-600', urgent: 'bg-orange-500', normal: 'bg-blue-500', deferred: 'bg-gray-400' }; const statusColors: Record = { draft: 'bg-gray-400', in_progress: 'bg-blue-500', closed: 'bg-green-500', cancelled: 'bg-red-400' }; const statusLabels: Record = { draft: 'Черновик', in_progress: 'В работе', closed: 'Закрыт', cancelled: 'Отменён' }; const typeLabels: Record = { scheduled: 'Плановое', unscheduled: 'Внеплановое', ad_compliance: 'Выполн. ДЛГ', sb_compliance: 'Выполн. SB', defect_rectification: 'Устранение дефекта', modification: 'Модификация' }; return ( <> {loading &&
⏳ Загрузка...
} setShowAdd(true)} className="btn-primary text-sm px-4 py-2 rounded">+ Создать наряд}> {/* Stats */} {stats && (
{stats.total}
Всего
{stats.draft}
Черновик
{stats.in_progress}
В работе
{stats.closed}
Закрыто
{stats.aog}
AOG
{stats.total_manhours}
Человеко-часов
)} {/* Filters */}
{['', 'draft', 'in_progress', 'closed', 'cancelled'].map(s => ( ))}
{/* Table */} {orders.length > 0 ? ( {typeLabels[v] || v} }, { key: 'title', label: 'Наименование' }, { key: 'priority', label: 'Приоритет', render: (v: string) => }, { key: 'estimated_manhours', label: 'План. ч/ч' }, { key: 'status', label: 'Статус', render: (v: string) => }, ]} data={orders} onRowClick={setSelected} /> ) : } {/* Detail modal */} setSelected(null)} title={selected ? `WO ${selected.wo_number}` : ''} size="lg"> {selected && (
Борт: {selected.aircraft_reg}
Тип: {typeLabels[selected.wo_type] || selected.wo_type}
Приоритет:
Статус:
План. ч/ч: {selected.estimated_manhours}
Факт. ч/ч: {selected.actual_manhours || '—'}
{selected.description &&
{selected.description}
} {selected.crs_signed_by && (
✅ CRS подписан: {selected.crs_signed_by} ({selected.crs_date ? new Date(selected.crs_date).toLocaleDateString('ru-RU') : ''})
)} {selected.findings &&
Замечания: {selected.findings}
}
{selected.status === 'draft' && ( )} {selected.status === 'in_progress' && ( )}
)}
{/* Create modal */} setShowAdd(false)} title="Создать наряд на ТО"> setShowAdd(false)} />
); } function WOForm({ onSubmit, onCancel }: { onSubmit: (d: any) => void; onCancel: () => void }) { const [f, setF] = useState({ wo_number: `WO-${Date.now().toString(36).toUpperCase()}`, aircraft_reg: '', wo_type: 'scheduled', title: '', description: '', priority: 'normal', estimated_manhours: 0, }); return (
setF(p => ({ ...p, wo_number: e.target.value }))} />
setF(p => ({ ...p, aircraft_reg: e.target.value }))} />
setF(p => ({ ...p, title: e.target.value }))} />