import { useState, useEffect, useCallback, useMemo } from 'react' import Header from './components/Header' import FailedBanner from './components/FailedBanner' import Calendar from './components/Calendar' import RightPanel from './components/RightPanel' import SettingsModal from './components/SettingsModal' import { api, groupScheduleItems } from './api' // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- function getMondayOfWeek(date) { const d = new Date(date) const day = d.getDay() d.setDate(d.getDate() + (day === 0 ? -6 : 1 - day)) d.setHours(0, 0, 0, 0) return d } function toKey(d) { return d.toISOString().slice(0, 10) } function addDays(date, n) { const d = new Date(date) d.setDate(d.getDate() + n) return d } function getTonightWindowKeys() { const now = new Date() const hour = now.getHours() const today = new Date(now) today.setHours(0, 0, 0, 0) if (hour < 1) { return { shift3Date: toKey(addDays(today, -1)), shift1Date: toKey(today), } } return { shift3Date: toKey(today), shift1Date: toKey(addDays(today, 1)), } } function toTonightConfigRows(scheduleData) { const { shift3Date, shift1Date } = getTonightWindowKeys() const tonightTests = [ ...((scheduleData[shift3Date]?.shift3) ?? []), ...((scheduleData[shift1Date]?.shift1) ?? []), ] const bySta = new Map() for (const test of tonightTests) { const config = test.config ?? {} for (const entry of Object.values(config)) { const staRaw = entry?.sta const testPoint = entry?.test_point if (!staRaw || !testPoint) continue for (const staPart of String(staRaw).split(',')) { const sta = staPart.trim().toUpperCase() if (!sta) continue if (!bySta.has(sta)) { bySta.set(sta, { sta, points: new Set() }) } bySta.get(sta).points.add(testPoint) } } } return Array.from(bySta.values()) .map((row) => ({ sta: row.sta, testPoints: Array.from(row.points).sort(), })) .sort((a, b) => a.sta.localeCompare(b.sta)) } const DEFAULT_SETTINGS = { p2pCoeCsvPath: '', p3pCsvPath: '', refResultDir: '', dutResultDir: '', testExclusion: '', holidays: '', startDateOverride: '', } // --------------------------------------------------------------------------- export default function App() { const [settingsOpen, setSettingsOpen] = useState(false) const [settings, setSettings] = useState(DEFAULT_SETTINGS) const [daytimeEnabled, setDaytimeEnabled] = useState(false) const [topPriority, setTopPriority] = useState('') const [lowestPriority, setLowestPriority] = useState('') const [failedTests, setFailedTests] = useState([]) const [scheduleData, setScheduleData] = useState({}) const [completionDate, setCompletionDate] = useState(null) const [weekStart, setWeekStart] = useState(() => getMondayOfWeek(new Date())) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const tonightConfigRows = useMemo(() => toTonightConfigRows(scheduleData), [scheduleData]) // Fetch schedule for the given weekStart (Monday) const fetchSchedule = useCallback(async (start) => { try { const data = await api.getScheduleWeek(toKey(start)) setScheduleData(groupScheduleItems(data.items)) } catch (e) { console.error('Failed to fetch schedule:', e) } }, []) // On mount: load settings + holidays + schedule for today's week useEffect(() => { async function init() { try { const [saved, holidayData] = await Promise.all([ api.getSettings(), api.getHolidays(), ]) setSettings(prev => ({ ...prev, ...saved, holidays: (holidayData.dates ?? []).join(', '), })) } catch (e) { console.warn('Backend not reachable on load:', e.message) } await fetchSchedule(getMondayOfWeek(new Date())) } init() }, [fetchSchedule]) // Refetch whenever the displayed week changes useEffect(() => { fetchSchedule(weekStart) }, [weekStart, fetchSchedule]) // ------------------------------------------------------------------------- async function handleSaveSettings(newSettings) { setLoading(true) setError(null) try { await api.saveSettings(newSettings) const holidayDates = (newSettings.holidays ?? '') .split(',').map(s => s.trim()).filter(Boolean) await api.saveHolidays(holidayDates) if (newSettings.p2pCoeCsvPath?.trim()) { await api.loadCsv(newSettings.p2pCoeCsvPath.trim()) } if (newSettings.p3pCsvPath?.trim()) { await api.loadCsv(newSettings.p3pCsvPath.trim()) } setSettings(newSettings) } catch (e) { setError(e.message) } finally { setLoading(false) } } async function handleRemakeSchedule() { setLoading(true) setError(null) try { const result = await api.compileSchedule({ start_date: settings.startDateOverride?.trim() || null, daytime_testing_today: daytimeEnabled, top_priority_tests: topPriority.split(',').map(s => s.trim()).filter(Boolean), lowest_priority_tests: lowestPriority.split(',').map(s => s.trim()).filter(Boolean), rule: settings.testExclusion ?? '', }) setCompletionDate(result.completion_date ?? null) await fetchSchedule(weekStart) } catch (e) { setError(e.message) } finally { setLoading(false) } } function handleRerunDecision(rerunDuringDay) { // TODO: POST /api/failed-tests/rerun-decision once backend endpoint exists console.log('Rerun during day:', rerunDuringDay) setFailedTests([]) } // ------------------------------------------------------------------------- return (