Dual device on weekend feature

This commit is contained in:
2026-07-12 18:34:28 -04:00
parent 9e100d60d0
commit 31caa13c3d
6 changed files with 214 additions and 39 deletions
+45 -10
View File
@@ -37,6 +37,33 @@ function addDays(date, n) {
return d
}
function parseHolidayDates(raw = '') {
return new Set(
String(raw)
.split(',')
.map((token) => token.trim())
.filter(Boolean),
)
}
function isOffDay(date, holidaySet) {
const weekday = date.getDay()
if (weekday === 0 || weekday === 6) return true
return holidaySet.has(toKey(date))
}
function getWeekendStartDatesForWeek(weekStart, holidaySet) {
const dates = []
for (let offset = 0; offset < 7; offset += 1) {
const current = addDays(weekStart, offset)
const nextDay = addDays(current, 1)
if (!isOffDay(current, holidaySet) && isOffDay(nextDay, holidaySet)) {
dates.push(toKey(current))
}
}
return dates
}
function getTonightWindowKeys() {
const now = new Date()
const hour = now.getHours()
@@ -120,7 +147,7 @@ function sanitizeSettings(saved = {}) {
export default function App() {
const [settingsOpen, setSettingsOpen] = useState(false)
const [settings, setSettings] = useState(DEFAULT_SETTINGS)
const [daytimeEnabled, setDaytimeEnabled] = useState(false)
const [dualDeviceWeekendWeekSelections, setDualDeviceWeekendWeekSelections] = useState({})
const [topPriority, setTopPriority] = useState('')
const [lowestPriority, setLowestPriority] = useState('')
const [startDateOverride, setStartDateOverride] = useState('')
@@ -148,6 +175,13 @@ export default function App() {
() => scheduleWindows.find((windowDetails) => windowDetails.window_id === selectedWindowId) ?? null,
[scheduleWindows, selectedWindowId],
)
const holidaySet = useMemo(() => parseHolidayDates(settings.holidays), [settings.holidays])
const activeWeekKey = useMemo(() => toKey(weekStart), [weekStart])
const dualDeviceWeekendWeekEnabled = Boolean(dualDeviceWeekendWeekSelections[activeWeekKey])
const dualDeviceWindowStartDates = useMemo(() => {
if (!dualDeviceWeekendWeekEnabled) return []
return getWeekendStartDatesForWeek(weekStart, holidaySet)
}, [dualDeviceWeekendWeekEnabled, weekStart, holidaySet])
// Fetch schedule for the given weekStart (Monday)
const fetchSchedule = useCallback(async (start) => {
@@ -245,7 +279,7 @@ export default function App() {
const effectiveStartDate = startDateOverride.trim()
const result = await api.compileSchedule({
start_date: effectiveStartDate || null,
daytime_testing_today: daytimeEnabled,
dual_device_weekend_start_dates: dualDeviceWindowStartDates,
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 ?? '',
@@ -273,17 +307,13 @@ export default function App() {
}
}
function handleRerunDecision(rerunDuringDay) {
const rerunIds = failedTests.map(t => t.test_id).join(', ')
function handleRerunDecision(_rerunDuringDay) {
setTopPriority(prev => {
const existing = prev.split(',').map(s => s.trim()).filter(Boolean)
const incoming = failedTests.map(t => t.test_id)
const merged = [...new Set([...existing, ...incoming])]
return merged.join(', ')
})
if (rerunDuringDay) {
setDaytimeEnabled(true)
}
setFailedTests([])
}
@@ -323,9 +353,16 @@ export default function App() {
<div className="flex-1 min-w-0 flex flex-col">
<Calendar
scheduleData={scheduleData}
daytimeDateKey={daytimeEnabled ? toKey(new Date()) : null}
daytimeDateKey={null}
weekStart={weekStart}
onWeekChange={setWeekStart}
dualDeviceWeekendWeekEnabled={dualDeviceWeekendWeekEnabled}
onDualDeviceWeekendWeekEnabledChange={(enabled) => {
setDualDeviceWeekendWeekSelections((prev) => ({
...prev,
[activeWeekKey]: enabled,
}))
}}
windowLookup={windowLookup}
onWindowSelect={handleWindowSelect}
/>
@@ -335,8 +372,6 @@ export default function App() {
completionDate={completionDate}
startDateOverride={startDateOverride}
onStartDateOverrideChange={setStartDateOverride}
daytimeEnabled={daytimeEnabled}
onDaytimeEnabledChange={setDaytimeEnabled}
topPriority={topPriority}
onTopPriorityChange={setTopPriority}
lowestPriority={lowestPriority}
+21
View File
@@ -53,6 +53,8 @@ export default function Calendar({
daytimeDateKey = null,
weekStart,
onWeekChange,
dualDeviceWeekendWeekEnabled = false,
onDualDeviceWeekendWeekEnabledChange,
windowLookup = new Map(),
onWindowSelect,
}) {
@@ -111,6 +113,25 @@ export default function Calendar({
>
Today
</button>
<div className="ml-auto flex items-center gap-2 text-xs text-gray-300 select-none">
<span>Run both devices on weekend this week</span>
<button
type="button"
role="switch"
aria-checked={dualDeviceWeekendWeekEnabled}
onClick={() => onDualDeviceWeekendWeekEnabledChange?.(!dualDeviceWeekendWeekEnabled)}
className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500/70 ${
dualDeviceWeekendWeekEnabled ? 'bg-blue-600' : 'bg-gray-600'
}`}
title="Toggle dual-device weekend-start scheduling for this week"
>
<span
className={`pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow transform transition-transform ${
dualDeviceWeekendWeekEnabled ? 'translate-x-4' : 'translate-x-0'
}`}
/>
</button>
</div>
</div>
{/* 7-day grid */}
-25
View File
@@ -16,8 +16,6 @@ export default function RightPanel({
completionDate,
startDateOverride,
onStartDateOverrideChange,
daytimeEnabled,
onDaytimeEnabledChange,
topPriority,
onTopPriorityChange,
lowestPriority,
@@ -40,29 +38,6 @@ export default function RightPanel({
</p>
</div>
{/* Daytime testing toggle */}
<div>
<div className="flex items-center justify-between">
<p className="text-[11px] font-semibold uppercase tracking-wider text-gray-500">
Day Time Testing (Today)
</p>
<button
role="switch"
aria-checked={daytimeEnabled}
onClick={() => onDaytimeEnabledChange(!daytimeEnabled)}
className={`relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors ${
daytimeEnabled ? 'bg-blue-600' : 'bg-gray-600'
}`}
>
<span
className={`pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow transform transition-transform ${
daytimeEnabled ? 'translate-x-4' : 'translate-x-0'
}`}
/>
</button>
</div>
</div>
{/* Top priority */}
<div>
<label className="block text-[11px] font-semibold uppercase tracking-wider text-gray-500 mb-1.5">