frontend changes: change shift order display, added config map

This commit is contained in:
2026-07-13 02:10:25 -04:00
parent 5843ff9081
commit 6bf6fa4867
8 changed files with 155 additions and 105 deletions
+8
View File
@@ -141,10 +141,16 @@ export default function Calendar({
const shifts = scheduleData[key] ?? { shift1: [], shift2: [], shift3: [] }
const isToday = date.getTime() === today.getTime()
const isWeekend = date.getDay() === 0 || date.getDay() === 6
// Get next day's shift1 for display
const nextDate = addDays(date, 1)
const nextKey = toDateKey(nextDate)
const nextDayShift1 = scheduleData[nextKey]?.shift1 ?? []
// Which shifts on this date are part of the active test window?
const activeShifts = new Set()
if (key === activeWindow.shift3Date) activeShifts.add(3)
if (key === activeWindow.shift1Date) activeShifts.add(1)
// Check if next day's shift1 is active
const nextDayShift1Active = nextKey === activeWindow.shift1Date
return (
<DayColumn
key={key}
@@ -152,6 +158,8 @@ export default function Calendar({
isToday={isToday}
activeShifts={activeShifts}
shifts={shifts}
nextDayShift1={nextDayShift1}
nextDayShift1Active={nextDayShift1Active}
showShift2={isWeekend || (daytimeDateKey !== null && key === daytimeDateKey)}
windowLookup={windowLookup}
onWindowSelect={onWindowSelect}
+10 -8
View File
@@ -7,6 +7,8 @@ export default function DayColumn({
isToday,
activeShifts = new Set(),
shifts = {},
nextDayShift1 = [],
nextDayShift1Active = false,
showShift2 = false,
windowLookup = new Map(),
onWindowSelect,
@@ -39,14 +41,6 @@ export default function DayColumn({
{/* Shifts */}
<div className="flex flex-col flex-1 px-0.5 py-1">
<ShiftSlot
label="12AM9AM"
tests={shifts.shift1}
visible={true}
active={activeShifts.has(1)}
windowDetails={getWindowForShift(1)}
onWindowSelect={onWindowSelect}
/>
<ShiftSlot
label="9AM5PM"
tests={shifts.shift2}
@@ -63,6 +57,14 @@ export default function DayColumn({
windowDetails={getWindowForShift(3)}
onWindowSelect={onWindowSelect}
/>
<ShiftSlot
label="12AM9AM (next day)"
tests={nextDayShift1}
visible={true}
active={nextDayShift1Active}
windowDetails={null}
onWindowSelect={onWindowSelect}
/>
</div>
</div>
)
+7 -29
View File
@@ -1,3 +1,5 @@
import cgw453Image from '../assets/CGW453.PNG'
function formatCompletionDate(value) {
if (!value) return null
@@ -28,6 +30,11 @@ export default function RightPanel({
return (
<aside className="flex flex-col gap-4 w-60 shrink-0 pt-8">
{/* Device Image */}
<div className="flex justify-center mb-2">
<img src={cgw453Image} alt="CGW453 Device" className="h-32 object-contain" />
</div>
{/* Estimated Completion */}
<div>
<p className="text-[11px] font-semibold uppercase tracking-wider text-gray-500 mb-1">
@@ -72,35 +79,6 @@ export default function RightPanel({
>
{loading ? 'Working…' : 'Remake Schedule'}
</button>
{/* Config for tonight */}
<div>
<p className="text-[11px] font-semibold uppercase tracking-wider text-gray-500 mb-1.5">
Config for Today
</p>
{tonightConfigRows.length === 0 ? (
<p className="text-xs text-gray-500">No STA placement mapping available for tonight.</p>
) : (
<div className="max-h-52 overflow-y-auto rounded border border-gray-700">
<table className="w-full text-xs">
<thead className="bg-gray-900 sticky top-0">
<tr>
<th className="text-left text-gray-400 font-semibold px-2 py-1.5 border-b border-gray-700">STA</th>
<th className="text-left text-gray-400 font-semibold px-2 py-1.5 border-b border-gray-700">Testpoint</th>
</tr>
</thead>
<tbody>
{tonightConfigRows.map((row) => (
<tr key={row.sta} className="odd:bg-gray-950 even:bg-gray-900/70">
<td className="text-gray-200 font-semibold px-2 py-1.5 border-b border-gray-800">{row.sta}</td>
<td className="text-gray-300 px-2 py-1.5 border-b border-gray-800">{row.testPoints.join(', ')}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</aside>
)
}
@@ -1,4 +1,10 @@
import { useEffect, useMemo, useState } from 'react'
import { getDeviceAccentClass } from './TestCard'
const CONFIG_MAP_IMAGE_MODULES = import.meta.glob('../assets/TC*_map/*.png', {
eager: true,
import: 'default',
})
const STATUS_STYLES = {
pending: 'bg-gray-700 text-gray-200 border-gray-600',
@@ -90,6 +96,16 @@ function formatConfigEntries(config) {
.filter(Boolean)
}
function getConfigMapImages(configKey) {
if (!configKey) return []
const folderToken = `/${configKey.toUpperCase()}_map/`
return Object.entries(CONFIG_MAP_IMAGE_MODULES)
.filter(([path]) => path.includes(folderToken))
.sort(([pathA], [pathB]) => pathA.localeCompare(pathB, undefined, { numeric: true }))
.map(([, assetUrl]) => assetUrl)
}
function TestRow({ test }) {
const [expanded, setExpanded] = useState(false)
const statusStyle = STATUS_STYLES[test.status] ?? STATUS_STYLES.pending
@@ -103,15 +119,18 @@ function TestRow({ test }) {
className="w-full px-4 py-3 text-left hover:bg-gray-800/80 transition-colors"
>
<div className="flex items-start justify-between gap-3">
<div>
<p className="text-sm font-semibold text-white">{test.test_id}</p>
<p className="mt-1 text-xs text-gray-400">{test.device ?? '—'} · {formatShiftLabel(test)}</p>
<div className="flex items-center gap-2">
<span className={`w-2.5 h-2.5 rounded-full shrink-0 ${getDeviceAccentClass(test.device)}`} aria-hidden="true" />
<div>
<p className="text-sm font-semibold text-white">{test.test_id}</p>
<p className="mt-1 text-xs text-gray-400">{test.device ?? '—'} · {formatShiftLabel(test)}</p>
</div>
</div>
<div className="flex items-center gap-2 shrink-0">
<span className={`px-2 py-1 rounded-full border text-[11px] font-semibold uppercase tracking-wide ${statusStyle}`}>
{test.status ?? 'pending'}
</span>
<span className="text-xs text-gray-400">{formatHours(test.estimated_minutes)}</span>
<span className="text-xs text-gray-400">{Number.isFinite(test.estimated_minutes) && test.estimated_minutes > 0 ? `${test.estimated_minutes} min` : '—'}</span>
</div>
</div>
</button>
@@ -127,14 +146,6 @@ function TestRow({ test }) {
<p className="text-gray-500 uppercase tracking-wide text-[11px] mb-1">Rotation</p>
<p>{test.rotation ?? '—'}</p>
</div>
<div>
<p className="text-gray-500 uppercase tracking-wide text-[11px] mb-1">Priority</p>
<p>{test.priority ?? '—'}</p>
</div>
<div>
<p className="text-gray-500 uppercase tracking-wide text-[11px] mb-1">Sequence</p>
<p>{test.sequence_in_shift ?? '—'}</p>
</div>
</div>
<div className="mt-4">
@@ -171,7 +182,15 @@ function TestRow({ test }) {
}
export default function TestWindowDetailsPanel({ isOpen, windowDetails, onClose }) {
const [showConfigMap, setShowConfigMap] = useState(false)
const windowTestConfig = useMemo(() => getWindowTestConfig(windowDetails), [windowDetails])
const windowConfigMapKey = useMemo(
() => (windowTestConfig.length > 0 ? String(windowTestConfig[0]).toUpperCase() : null),
[windowTestConfig],
)
const configMapImages = useMemo(() => getConfigMapImages(windowConfigMapKey), [windowConfigMapKey])
const hasConfigMapImages = configMapImages.length > 0
const showMapPane = showConfigMap && hasConfigMapImages
useEffect(() => {
if (!isOpen) return undefined
@@ -201,11 +220,13 @@ export default function TestWindowDetailsPanel({ isOpen, windowDetails, onClose
/>
<aside
className={`absolute inset-y-0 right-0 w-full max-w-[30rem] border-l border-gray-700 bg-gray-950/96 shadow-2xl transition-transform duration-300 ${
className={`absolute inset-y-0 right-0 border-l border-gray-700 bg-gray-950/96 shadow-2xl transition-[width,transform] duration-300 ease-in-out ${
showMapPane ? 'w-full' : 'w-[30rem]'
} ${
isOpen ? 'translate-x-0' : 'translate-x-full'
}`}
>
<div className="flex h-full flex-col">
<div className="flex h-full min-w-0 flex-col">
<div className="flex items-start justify-between gap-4 border-b border-gray-800 px-5 py-5">
<div>
<p className="text-[11px] font-semibold uppercase tracking-[0.22em] text-cyan-400">Test Window</p>
@@ -226,63 +247,106 @@ export default function TestWindowDetailsPanel({ isOpen, windowDetails, onClose
</div>
{windowDetails && (
<div className="flex-1 overflow-y-auto px-5 py-5">
<div className="grid grid-cols-2 gap-3">
<div className="rounded-2xl border border-gray-800 bg-gray-900/80 px-4 py-3">
<p className="text-[11px] font-semibold uppercase tracking-wide text-gray-500">Available Run Time</p>
<p className="mt-2 text-2xl font-semibold text-white">{formatHours(windowDetails.available_runtime_minutes)}</p>
<div className="flex-1 min-h-0 overflow-y-auto">
<div className="flex min-h-full min-w-0">
<div className={`${showMapPane ? 'w-[30rem] min-w-[30rem] border-r border-gray-800' : 'w-full'} px-5 py-5`}>
<div className="grid grid-cols-2 gap-3">
<div className="rounded-2xl px-4 py-3">
<p className="text-[11px] font-semibold uppercase tracking-wide text-gray-500">Available Run Time</p>
<p className="mt-2 text-2xl font-semibold text-white">{formatHours(windowDetails.available_runtime_minutes)}</p>
</div>
<div className="rounded-2xl px-4 py-3">
<p className="text-[11px] font-semibold uppercase tracking-wide text-gray-500">Estimated Run Time</p>
<p className="mt-2 text-2xl font-semibold text-white">{formatHours(windowDetails.estimated_runtime_minutes)}</p>
</div>
</div>
<div className="rounded-2xl border border-gray-800 bg-gray-900/80 px-4 py-3">
<p className="text-[11px] font-semibold uppercase tracking-wide text-gray-500">Estimated Run Time</p>
<p className="mt-2 text-2xl font-semibold text-white">{formatHours(windowDetails.estimated_runtime_minutes)}</p>
</div>
</div>
<div className="mt-5 rounded-2xl border border-gray-800 bg-gray-900/80 p-4">
<div className="flex items-center justify-between gap-3">
<p className="text-sm font-semibold text-white">Config</p>
<span className="rounded-full border border-cyan-700/60 bg-cyan-900/30 px-2 py-1 text-[11px] font-semibold uppercase tracking-wide text-cyan-200">
{windowTestConfig.length > 0 ? windowTestConfig.join(', ') : 'N/A'}
</span>
</div>
{windowDetails.configRows?.length ? (
<div className="mt-3 overflow-hidden rounded-xl border border-gray-800">
<table className="w-full text-xs">
<thead className="bg-gray-950/90">
<tr>
<th className="px-3 py-2 text-left font-semibold text-gray-400 border-b border-gray-800">STA</th>
<th className="px-3 py-2 text-left font-semibold text-gray-400 border-b border-gray-800">Testpoint</th>
</tr>
</thead>
<tbody>
{windowDetails.configRows.map((row, index) => (
<tr key={`${row.sta}-${index}`} className="odd:bg-gray-950 even:bg-gray-900/60">
<td className="px-3 py-2 border-b border-gray-800 font-semibold text-gray-200">{row.sta}</td>
<td className="px-3 py-2 border-b border-gray-800 text-gray-300">{row.testPoints.join(', ')}</td>
<div className="mt-5 rounded-2xl border border-gray-800 bg-gray-900/80 p-4">
<div className="flex items-center justify-between gap-3">
<p className="text-sm font-semibold text-white">Config</p>
<span className="rounded-full border border-cyan-700/60 bg-cyan-900/30 px-2 py-1 text-[11px] font-semibold uppercase tracking-wide text-cyan-200">
{windowTestConfig.length > 0 ? windowTestConfig.join(', ') : 'N/A'}
</span>
</div>
{windowDetails.configRows?.length ? (
<div className="mt-3 overflow-hidden rounded-xl border border-gray-800">
<table className="w-full text-xs">
<thead className="bg-gray-950/90">
<tr>
<th className="px-3 py-2 text-left font-semibold text-gray-400 border-b border-gray-800">STA</th>
<th className="px-3 py-2 text-left font-semibold text-gray-400 border-b border-gray-800">Testpoint</th>
</tr>
))}
</tbody>
</table>
</div>
) : (
<p className="mt-3 text-xs text-gray-500">No STA placement mapping available for this window.</p>
)}
</div>
</thead>
<tbody>
{windowDetails.configRows.map((row, index) => (
<tr key={`${row.sta}-${index}`} className="odd:bg-gray-950 even:bg-gray-900/60">
<td className="px-3 py-2 border-b border-gray-800 font-semibold text-gray-200">{row.sta}</td>
<td className="px-3 py-2 border-b border-gray-800 text-gray-300">{row.testPoints.join(', ')}</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<p className="mt-3 text-xs text-gray-500">No STA placement mapping available for this window.</p>
)}
<div className="mt-5">
<div className="flex items-center justify-between gap-3 mb-3">
<p className="text-sm font-semibold text-white">Tests Scheduled In This Window</p>
<span className="text-xs text-gray-500">{windowDetails.tests?.length ?? 0} tests</span>
</div>
{windowDetails.tests?.length ? (
<div className="flex flex-col gap-3">
{windowDetails.tests.map((test) => (
<TestRow key={`${test.test_id}-${test.device}-${test.scheduled_date}-${test.shift_index}`} test={test} />
))}
<div className="mt-3">
<button
type="button"
onClick={() => setShowConfigMap((value) => !value)}
disabled={!hasConfigMapImages}
className="inline-flex items-center gap-2 rounded-md border border-gray-700 bg-gray-900 px-3 py-1.5 text-xs font-semibold text-gray-200 hover:bg-gray-800 transition-colors disabled:cursor-not-allowed disabled:opacity-50"
>
<span>{showMapPane ? 'Hide' : 'Show'} Config Map</span>
<span className="text-gray-400">{windowConfigMapKey ?? 'N/A'}</span>
</button>
{!hasConfigMapImages && (
<p className="mt-2 text-xs text-gray-500">No config map images found for this window config.</p>
)}
</div>
) : (
<div className="rounded-2xl border border-dashed border-gray-800 bg-gray-900/50 px-4 py-6 text-sm text-gray-500">
No tests are currently scheduled in this window.
</div>
<div className="mt-5">
<div className="flex items-center justify-between gap-3 mb-3">
<p className="text-sm font-semibold text-white">Tests Scheduled In This Window</p>
<span className="text-xs text-gray-500">{windowDetails.tests?.length ?? 0} tests</span>
</div>
{windowDetails.tests?.length ? (
<div className="flex flex-col gap-3">
{windowDetails.tests.map((test) => (
<TestRow key={`${test.test_id}-${test.device}-${test.scheduled_date}-${test.shift_index}`} test={test} />
))}
</div>
) : (
<div className="rounded-2xl border border-dashed border-gray-800 bg-gray-900/50 px-4 py-6 text-sm text-gray-500">
No tests are currently scheduled in this window.
</div>
)}
</div>
</div>
{showMapPane && (
<div className="min-w-0 flex-1 px-6 py-5">
<div className="mb-4 flex items-center justify-between gap-3">
<p className="text-sm font-semibold text-white">Config Map</p>
<span className="rounded-full border border-cyan-700/60 bg-cyan-900/30 px-2 py-1 text-[11px] font-semibold uppercase tracking-wide text-cyan-200">
{windowConfigMapKey}
</span>
</div>
<div className="flex flex-col gap-5">
{configMapImages.map((imageUrl, index) => (
<div key={`${windowConfigMapKey}-map-${index + 1}`} className="rounded-xl p-2">
<img
src={imageUrl}
alt={`${windowConfigMapKey} config map ${index + 1}`}
className="w-full max-h-[78vh] rounded-lg object-contain"
loading="lazy"
/>
</div>
))}
</div>
</div>
)}
</div>