/** * Календарь ТО — визуализация плановых работ, дедлайнов AD, сроков ПК. * ФАП-148 п.3; EASA Part-M.A.302; ICAO Annex 6 Part I 8.3 */ 'use client'; import { useState, useEffect, useMemo } from 'react'; import { PageLayout } from '@/components/ui'; interface CalEvent { id: string; title: string; date: string; type: string; } const MO = ['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь']; const DW = ['Пн','Вт','Ср','Чт','Пт','Сб','Вс']; const TC: Record = { scheduled:'bg-blue-500', ad_compliance:'bg-red-500', sb_compliance:'bg-orange-400', defect_rectification:'bg-yellow-500', unscheduled:'bg-purple-500', qualification_due:'bg-pink-500', life_limit:'bg-red-700', }; export default function CalendarPage() { const [events, setEvents] = useState([]); const [cur, setCur] = useState(new Date()); const [loading, setLoading] = useState(true); useEffect(() => { setLoading(true); Promise.all([ fetch('/api/v1/work-orders/').then(r => r.json()).catch(() => ({ items: [] })), fetch('/api/v1/personnel-plg/compliance-report').then(r => r.json()).catch(() => ({ expiring_soon: [] })), fetch('/api/v1/airworthiness-core/life-limits').then(r => r.json()).catch(() => ({ items: [] })), ]).then(([wos, pers, lls]) => { const ev: CalEvent[] = []; (wos.items || []).forEach((w: any) => { const d = w.planned_start || w.created_at; if (d) ev.push({ id: w.id, title: `${w.wo_number}: ${(w.title||'').slice(0,25)}`, date: d.slice(0,10), type: w.wo_type }); }); (pers.expiring_soon || []).forEach((p: any) => { if (p.due) ev.push({ id: p.specialist+p.due, title: `ПК: ${(p.specialist||'').slice(0,20)}`, date: p.due.slice(0,10), type: 'qualification_due' }); }); (lls.items || []).filter((l: any) => l.remaining?.days > 0 && l.remaining.days < 90).forEach((l: any) => { const dd = new Date(); dd.setDate(dd.getDate() + l.remaining.days); ev.push({ id: l.id, title: `Ресурс: ${(l.component_name||'').slice(0,20)}`, date: dd.toISOString().slice(0,10), type: 'life_limit' }); }); setEvents(ev); setLoading(false); }); }, []); const y = cur.getFullYear(), m = cur.getMonth(); const sd = (new Date(y,m,1).getDay()+6)%7; const dim = new Date(y,m+1,0).getDate(); const days = useMemo(() => { const a: (number|null)[] = []; for (let i=0;i { const ds = `${y}-${String(m+1).padStart(2,'0')}-${String(d).padStart(2,'0')}`; return events.filter(e => e.date === ds); }; const td = new Date(); return ( {loading &&
⏳ Загрузка...
}

{MO[m]} {y}

{[['scheduled','Плановое ТО'],['ad_compliance','ДЛГ'],['sb_compliance','SB'],['defect_rectification','Дефект'],['qualification_due','ПК'],['life_limit','Ресурс']].map(([k,l]) => (
{l}
))}
{DW.map(d =>
{d}
)} {days.map((day, i) => { if (!day) return
; const de = evFor(day); const isT = day===td.getDate() && m===td.getMonth() && y===td.getFullYear(); return (
{day}
{de.slice(0,3).map((e,j) =>
{e.title}
)} {de.length > 3 &&
+{de.length-3}
}
); })}
); }