showing previous version schedule as faded
This commit is contained in:
+113
-1
@@ -119,6 +119,87 @@ function toTonightConfigRows(scheduleData) {
|
||||
.sort((a, b) => a.sta.localeCompare(b.sta))
|
||||
}
|
||||
|
||||
const SHIFT_KEYS = ['shift1', 'shift2', 'shift3']
|
||||
const SHIFT_INDEX_BY_KEY = {
|
||||
shift1: 1,
|
||||
shift2: 2,
|
||||
shift3: 3,
|
||||
}
|
||||
|
||||
function createEmptyScheduleDay() {
|
||||
return { shift1: [], shift2: [], shift3: [] }
|
||||
}
|
||||
|
||||
function getShiftTests(scheduleData, dateKey, shiftKey) {
|
||||
return scheduleData[dateKey]?.[shiftKey] ?? []
|
||||
}
|
||||
|
||||
function isOnOrAfterScheduleStart(dateKey, shiftKey, scheduleStartShift) {
|
||||
if (!scheduleStartShift?.date || !scheduleStartShift?.shiftIndex) return false
|
||||
|
||||
if (dateKey > scheduleStartShift.date) return true
|
||||
if (dateKey < scheduleStartShift.date) return false
|
||||
|
||||
const currentShiftIndex = SHIFT_INDEX_BY_KEY[shiftKey] ?? 99
|
||||
return currentShiftIndex >= scheduleStartShift.shiftIndex
|
||||
}
|
||||
|
||||
function collectSnapshotShiftKeys(currentScheduleData = {}, previousScheduleData = {}, scheduleStartShift = null) {
|
||||
const snapshotShiftKeys = new Set()
|
||||
const dateKeys = new Set([
|
||||
...Object.keys(currentScheduleData),
|
||||
...Object.keys(previousScheduleData),
|
||||
])
|
||||
|
||||
for (const dateKey of dateKeys) {
|
||||
for (const shiftKey of SHIFT_KEYS) {
|
||||
if (isOnOrAfterScheduleStart(dateKey, shiftKey, scheduleStartShift)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const currentTests = getShiftTests(currentScheduleData, dateKey, shiftKey)
|
||||
const previousTests = getShiftTests(previousScheduleData, dateKey, shiftKey)
|
||||
|
||||
// Show historical snapshot only when this shift disappeared in the latest schedule.
|
||||
if (currentTests.length === 0 && previousTests.length > 0) {
|
||||
snapshotShiftKeys.add(`${dateKey}::${shiftKey}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return snapshotShiftKeys
|
||||
}
|
||||
|
||||
function markHistoricalSnapshot(tests = []) {
|
||||
return tests.map((test) => ({
|
||||
...test,
|
||||
isPreviousVersionSnapshot: true,
|
||||
}))
|
||||
}
|
||||
|
||||
function buildCalendarScheduleData(currentScheduleData = {}, previousScheduleData = {}, snapshotShiftKeys = new Set()) {
|
||||
const calendarScheduleData = {}
|
||||
const dateKeys = new Set([
|
||||
...Object.keys(currentScheduleData),
|
||||
...Object.keys(previousScheduleData),
|
||||
])
|
||||
|
||||
for (const dateKey of dateKeys) {
|
||||
const mergedDay = createEmptyScheduleDay()
|
||||
|
||||
for (const shiftKey of SHIFT_KEYS) {
|
||||
const shiftSnapshotKey = `${dateKey}::${shiftKey}`
|
||||
mergedDay[shiftKey] = snapshotShiftKeys.has(shiftSnapshotKey)
|
||||
? markHistoricalSnapshot(getShiftTests(previousScheduleData, dateKey, shiftKey))
|
||||
: getShiftTests(currentScheduleData, dateKey, shiftKey)
|
||||
}
|
||||
|
||||
calendarScheduleData[dateKey] = mergedDay
|
||||
}
|
||||
|
||||
return calendarScheduleData
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS = {
|
||||
p2pCoeCsvPath: '',
|
||||
p3pCsvPath: '',
|
||||
@@ -171,6 +252,8 @@ export default function App() {
|
||||
const [startDateOverride, setStartDateOverride] = useState('')
|
||||
const [failedTests, setFailedTests] = useState([])
|
||||
const [scheduleData, setScheduleData] = useState({})
|
||||
const [previousScheduleData, setPreviousScheduleData] = useState({})
|
||||
const [scheduleStartShift, setScheduleStartShift] = useState(null)
|
||||
const [scheduleWindows, setScheduleWindows] = useState([])
|
||||
const [selectedWindowId, setSelectedWindowId] = useState(null)
|
||||
const [completionDate, setCompletionDate] = useState(null)
|
||||
@@ -178,6 +261,14 @@ export default function App() {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState(null)
|
||||
const tonightConfigRows = useMemo(() => toTonightConfigRows(scheduleData), [scheduleData])
|
||||
const snapshotShiftKeys = useMemo(
|
||||
() => collectSnapshotShiftKeys(scheduleData, previousScheduleData, scheduleStartShift),
|
||||
[scheduleData, previousScheduleData, scheduleStartShift],
|
||||
)
|
||||
const calendarScheduleData = useMemo(
|
||||
() => buildCalendarScheduleData(scheduleData, previousScheduleData, snapshotShiftKeys),
|
||||
[scheduleData, previousScheduleData, snapshotShiftKeys],
|
||||
)
|
||||
const windowLookup = useMemo(() => {
|
||||
const lookup = new Map()
|
||||
|
||||
@@ -205,7 +296,27 @@ export default function App() {
|
||||
const fetchSchedule = useCallback(async (start) => {
|
||||
try {
|
||||
const data = await api.getScheduleWeek(toKey(start))
|
||||
let previousVersionScheduleData = {}
|
||||
|
||||
if ((data.schedule_version ?? 0) > 1) {
|
||||
try {
|
||||
const previousData = await api.getScheduleWeek(toKey(start), data.schedule_version - 1)
|
||||
previousVersionScheduleData = groupScheduleItems(previousData.items)
|
||||
} catch (previousError) {
|
||||
console.warn('Failed to fetch previous schedule version:', previousError)
|
||||
}
|
||||
}
|
||||
|
||||
setScheduleData(groupScheduleItems(data.items))
|
||||
setPreviousScheduleData(previousVersionScheduleData)
|
||||
if (data.schedule_start_date && data.schedule_start_shift_index) {
|
||||
setScheduleStartShift({
|
||||
date: data.schedule_start_date,
|
||||
shiftIndex: data.schedule_start_shift_index,
|
||||
})
|
||||
} else {
|
||||
setScheduleStartShift(null)
|
||||
}
|
||||
setScheduleWindows(data.windows ?? [])
|
||||
setCompletionDate(data.completion_date ?? null)
|
||||
} catch (e) {
|
||||
@@ -386,7 +497,7 @@ export default function App() {
|
||||
<main className="relative flex flex-1 gap-4 p-4 overflow-hidden">
|
||||
<div className="flex-1 min-w-0 flex flex-col">
|
||||
<Calendar
|
||||
scheduleData={scheduleData}
|
||||
scheduleData={calendarScheduleData}
|
||||
daytimeDateKey={null}
|
||||
weekStart={weekStart}
|
||||
onWeekChange={setWeekStart}
|
||||
@@ -398,6 +509,7 @@ export default function App() {
|
||||
}))
|
||||
}}
|
||||
windowLookup={windowLookup}
|
||||
snapshotShiftKeys={snapshotShiftKeys}
|
||||
onWindowSelect={handleWindowSelect}
|
||||
/>
|
||||
</div>
|
||||
|
||||
+7
-1
@@ -30,7 +30,13 @@ export const api = {
|
||||
|
||||
// Schedule
|
||||
compileSchedule: (opts) => request('POST', '/schedule/compile', opts),
|
||||
getScheduleWeek: (start) => request('GET', `/schedule/week?start=${start}`),
|
||||
getScheduleWeek: (start, version = null) => {
|
||||
const params = new URLSearchParams({ start })
|
||||
if (version !== null && version !== undefined) {
|
||||
params.set('version', String(version))
|
||||
}
|
||||
return request('GET', `/schedule/week?${params.toString()}`)
|
||||
},
|
||||
getRerunTests: () => request('GET', '/tests/rerun'),
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ export default function Calendar({
|
||||
dualDeviceWeekendWeekEnabled = false,
|
||||
onDualDeviceWeekendWeekEnabledChange,
|
||||
windowLookup = new Map(),
|
||||
snapshotShiftKeys = new Set(),
|
||||
onWindowSelect,
|
||||
}) {
|
||||
const DEFAULT_SHIFT_MIN_ROWS = 3
|
||||
@@ -226,6 +227,7 @@ export default function Calendar({
|
||||
slotHeights={slotHeights}
|
||||
showShift2={isWeekend || (daytimeDateKey !== null && key === daytimeDateKey)}
|
||||
windowLookup={windowLookup}
|
||||
snapshotShiftKeys={snapshotShiftKeys}
|
||||
onWindowSelect={onWindowSelect}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ export default function DayColumn({
|
||||
shiftMinRows = { shift2: 1, shift3: 1, nextDayShift1: 1 },
|
||||
slotHeights = { shift2: 0, shift3: 0, nextDayShift1: 0 },
|
||||
windowLookup = new Map(),
|
||||
snapshotShiftKeys = new Set(),
|
||||
onWindowSelect,
|
||||
}) {
|
||||
const dayName = DAY_NAMES[date.getDay()]
|
||||
@@ -20,11 +21,17 @@ export default function DayColumn({
|
||||
const dateKey = date.toISOString().slice(0, 10)
|
||||
const nextDateKey = new Date(date.getTime() + (24 * 60 * 60 * 1000)).toISOString().slice(0, 10)
|
||||
|
||||
function isSnapshotShift(snapshotDateKey, shiftIndex) {
|
||||
return snapshotShiftKeys.has(`${snapshotDateKey}::shift${shiftIndex}`)
|
||||
}
|
||||
|
||||
function getWindowForShift(shiftIndex) {
|
||||
if (isSnapshotShift(dateKey, shiftIndex)) return null
|
||||
return windowLookup.get(`${dateKey}::shift${shiftIndex}`) ?? null
|
||||
}
|
||||
|
||||
function getNextDayShift1Window() {
|
||||
if (isSnapshotShift(nextDateKey, 1)) return null
|
||||
return windowLookup.get(`${nextDateKey}::shift1`) ?? null
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ export default function TestCard({ test }) {
|
||||
const normalizedStatus = String(test?.status ?? 'pending').trim().toLowerCase()
|
||||
const style = STATUS_STYLES[normalizedStatus] ?? STATUS_STYLES.pending
|
||||
const deviceAccentClass = getDeviceAccentClass(test.device)
|
||||
const isPreviousVersionSnapshot = Boolean(test?.isPreviousVersionSnapshot)
|
||||
const cardRef = useRef(null)
|
||||
const [tooltipPos, setTooltipPos] = useState({ top: '0px', left: '0px' })
|
||||
const [isHovered, setIsHovered] = useState(false)
|
||||
@@ -92,7 +93,7 @@ export default function TestCard({ test }) {
|
||||
<div ref={cardRef} className="relative">
|
||||
<div
|
||||
className={`relative px-1.5 py-0.5 pl-3 rounded border text-xs font-mono truncate cursor-default select-none ${style}`}
|
||||
style={{ maxWidth: '100%' }}
|
||||
style={{ maxWidth: '100%', opacity: isPreviousVersionSnapshot ? 0.45 : 1 }}
|
||||
>
|
||||
<span className={`absolute inset-y-0 left-0 w-1 rounded-l ${deviceAccentClass}`} aria-hidden="true" />
|
||||
{test.test_id}
|
||||
|
||||
Reference in New Issue
Block a user