Dual device on weekend feature
This commit is contained in:
+45
-10
@@ -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}
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user