Files
scheduler/frontend/src/App.jsx
T

250 lines
7.5 KiB
React
Raw Normal View History

2026-06-17 16:02:04 -04:00
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'
2026-06-16 15:07:59 -04:00
2026-06-17 16:02:04 -04:00
// ---------------------------------------------------------------------------
// 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([])
}
// -------------------------------------------------------------------------
2026-06-16 15:07:59 -04:00
return (
2026-06-17 16:02:04 -04:00
<div className="flex flex-col min-h-screen bg-gray-950 text-gray-100">
<Header onOpenSettings={() => setSettingsOpen(true)} />
{error && (
<div className="px-6 py-2 bg-orange-900/60 border-b border-orange-700 text-orange-200 text-sm flex items-center justify-between">
<span>{error}</span>
<button className="ml-3 text-orange-400 hover:text-white" onClick={() => setError(null)}></button>
2026-06-16 15:07:59 -04:00
</div>
2026-06-17 16:02:04 -04:00
)}
<FailedBanner
failedTests={failedTests}
estimatedMinutes={0}
onDecision={handleRerunDecision}
/>
<main className="flex flex-1 gap-4 p-4 overflow-hidden">
<div className="flex-1 min-w-0 flex flex-col">
<Calendar
scheduleData={scheduleData}
daytimeDateKey={daytimeEnabled ? toKey(new Date()) : null}
weekStart={weekStart}
onWeekChange={setWeekStart}
/>
2026-06-16 15:07:59 -04:00
</div>
2026-06-17 16:02:04 -04:00
<RightPanel
completionDate={completionDate}
daytimeEnabled={daytimeEnabled}
onDaytimeEnabledChange={setDaytimeEnabled}
topPriority={topPriority}
onTopPriorityChange={setTopPriority}
lowestPriority={lowestPriority}
onLowestPriorityChange={setLowestPriority}
onRemakeSchedule={handleRemakeSchedule}
loading={loading}
tonightConfigRows={tonightConfigRows}
/>
</main>
<SettingsModal
isOpen={settingsOpen}
onClose={() => setSettingsOpen(false)}
settings={settings}
onSave={handleSaveSettings}
/>
</div>
2026-06-16 15:07:59 -04:00
)
}