Fix date selector loading stale week — pass new dates directly to loadAll

`loadAll` captured `weekStart` in a stale closure when called via
`setTimeout`. Selecting a date would show the correct value in the input
but load data for the previously-shown week. Replaced all deferred
`setTimeout(() => loadAll(true), 0)` calls with direct `loadAll(true, newDate)`
calls, passing the updated date as an explicit argument.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-22 12:26:35 +00:00
parent 872251250e
commit 0b63543e1f
10 changed files with 153 additions and 7 deletions

View file

@ -0,0 +1 @@
*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}:root{--navy: #1a1a2e;--navy-dark: #0f0f20;--gold: #c9a84c;--gold-light: #e8c96d;--surface: rgba(255,255,255,.07);--surface-2: rgba(255,255,255,.08);--text: rgba(255,255,255,.88);--text-muted: rgba(255,255,255,.48);--body-bg: #f4f5f7;--card-bg: #ffffff;--card-border: #e4e8ee;--text-dark: #1e293b;--text-mid: #64748b;--shadow-sm: 0 1px 3px rgba(0,0,0,.07), 0 1px 2px rgba(0,0,0,.04);--shadow-md: 0 4px 12px rgba(0,0,0,.08);--danger: #dc2626;--success: #16a34a;--warning: #d97706;--hk-green: #2d6a4f;--radius: 10px;--font: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif}body{background:var(--body-bg);color:var(--text-dark);font-family:var(--font);min-height:100dvh}button{cursor:pointer;font-family:inherit}input,textarea,select{font-family:inherit}.hk-table{width:100%;border-collapse:collapse;font-size:.82rem;table-layout:fixed}.hk-table th{background:var(--navy);color:var(--text);padding:.45rem .5rem;text-align:center;font-weight:600;white-space:nowrap}.hk-table th.col-label{text-align:left;width:140px}.hk-table td{padding:.35rem .5rem;border-bottom:1px solid var(--card-border);text-align:center;vertical-align:middle}.hk-table td.col-label{text-align:left;font-weight:600;color:var(--text-dark);background:var(--card-bg)}.hk-table tfoot td{font-weight:600;background:#f8fafc}.row-depart{background:#fff8f0}.row-stay{background:#f0f7ff}.row-arrive{background:#f0fff5}.row-depart td,.row-stay td,.row-arrive td{border-bottom:none}.row-group-last td{border-bottom:2px solid var(--card-border)!important}.pickup-tag{color:var(--hk-green);font-size:.75rem;font-weight:600}.delta-new{color:var(--success);font-size:.72rem}.delta-canc{color:var(--danger);font-size:.72rem}.pickup-ctrl{display:flex;align-items:center;justify-content:center;gap:3px;margin-top:2px}.pickup-btn{background:none;border:1px solid var(--card-border);border-radius:3px;padding:0 5px;line-height:1.4;font-size:.8rem;color:var(--text-mid)}.pickup-btn:disabled{opacity:.35;cursor:not-allowed}.pickup-num{min-width:16px;text-align:center;font-size:.8rem;font-weight:600}.diff-ok{color:var(--success);font-weight:600}.diff-under{color:var(--danger);font-weight:600}.diff-over{color:var(--warning);font-weight:600}.hk-num-input{width:60px;border:1px solid var(--card-border);border-radius:4px;padding:.3rem .4rem;font-size:.82rem;text-align:center;background:var(--body-bg);color:var(--text-dark)}.hk-num-input:focus{outline:2px solid var(--hk-green);border-color:transparent}.hk-text-input{border:1px solid var(--card-border);border-radius:4px;padding:.3rem .5rem;font-size:.82rem;background:var(--body-bg);color:var(--text-dark);width:100%}.hk-text-input:focus{outline:2px solid var(--hk-green);border-color:transparent}.table-scroll{overflow-x:auto;border-radius:var(--radius);border:1px solid var(--card-border);box-shadow:var(--shadow-sm)}.nav-scroll::-webkit-scrollbar,.sidebar::-webkit-scrollbar,.sidebar-nav::-webkit-scrollbar{width:4px}.nav-scroll::-webkit-scrollbar-track,.sidebar::-webkit-scrollbar-track,.sidebar-nav::-webkit-scrollbar-track{background:transparent}.nav-scroll::-webkit-scrollbar-thumb,.sidebar::-webkit-scrollbar-thumb,.sidebar-nav::-webkit-scrollbar-thumb{background:#c9a84c59;border-radius:2px}.nav-scroll::-webkit-scrollbar-thumb:hover,.sidebar::-webkit-scrollbar-thumb:hover,.sidebar-nav::-webkit-scrollbar-thumb:hover{background:#c9a84ca6}.nav-scroll,.sidebar,.sidebar-nav{scrollbar-width:thin;scrollbar-color:rgba(201,168,76,.35) transparent}

127
frontend/dist/assets/index-pKsveas1.js vendored Normal file

File diff suppressed because one or more lines are too long

BIN
frontend/dist/icons/icon-192.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

BIN
frontend/dist/icons/icon-512.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

14
frontend/dist/index.html vendored Normal file
View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1280" />
<meta name="theme-color" content="#2d6a4f" />
<title>HK Planner</title>
<script type="module" crossorigin src="/hk-planner/assets/index-pKsveas1.js"></script>
<link rel="stylesheet" crossorigin href="/hk-planner/assets/index-BY6qKqNq.css">
<link rel="manifest" href="/hk-planner/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/hk-planner/registerSW.js"></script></head>
<body>
<div id="root"></div>
</body>
</html>

1
frontend/dist/manifest.webmanifest vendored Normal file
View file

@ -0,0 +1 @@
{"name":"HK Planner","short_name":"HK Planner","start_url":"/hk-planner/","display":"standalone","background_color":"#2d6a4f","theme_color":"#2d6a4f","lang":"en","scope":"/hk-planner/","icons":[{"src":"/hk-planner/icons/icon-192.png","sizes":"192x192","type":"image/png"},{"src":"/hk-planner/icons/icon-512.png","sizes":"512x512","type":"image/png"}]}

1
frontend/dist/registerSW.js vendored Normal file
View file

@ -0,0 +1 @@
if('serviceWorker' in navigator) {window.addEventListener('load', () => {navigator.serviceWorker.register('/hk-planner/sw.js', { scope: '/hk-planner/' })})}

1
frontend/dist/sw.js vendored Normal file
View file

@ -0,0 +1 @@
if(!self.define){let e,i={};const n=(n,s)=>(n=new URL(n+".js",s).href,i[n]||new Promise(i=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=i,document.head.appendChild(e)}else e=n,importScripts(n),i()}).then(()=>{let e=i[n];if(!e)throw new Error(`Module ${n} didnt register its module`);return e}));self.define=(s,r)=>{const o=e||("document"in self?document.currentScript.src:"")||location.href;if(i[o])return;let t={};const c=e=>n(e,o),d={module:{uri:o},exports:t,require:c};i[o]=Promise.all(s.map(e=>d[e]||c(e))).then(e=>(r(...e),t))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"a2c395d8c225f1b3ea12388f15189bce"},{url:"index.html",revision:"ed36f64c6584bab6b7811659079d81c2"},{url:"icons/icon-512.png",revision:"c32202b9deed67ef38331f63dec9d1c8"},{url:"icons/icon-192.png",revision:"46ece317d50d10b8f5e225e073b3221d"},{url:"assets/index-pKsveas1.js",revision:null},{url:"assets/index-BY6qKqNq.css",revision:null},{url:"manifest.webmanifest",revision:"c2510de876adb84db0c4b300b71216fa"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/hk-planner/index.html"),{denylist:[/\/api\//]}))});

1
frontend/dist/workbox-9c191d2f.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -162,12 +162,12 @@ export function Planner() {
putLastReviewed(today).catch(() => {}) putLastReviewed(today).catch(() => {})
} }
const loadAll = useCallback(async (force = false) => { const loadAll = useCallback(async (force = false, wsArg?: string | null, lvArg?: string) => {
setLoading(true) setLoading(true)
setError('') setError('')
const today = todayStr() const today = todayStr()
const ws = weekStart || today const ws = wsArg !== undefined ? (wsArg ?? today) : (weekStart || today)
const lv = lastViewed || savedLastReviewed || offsetDate(today, -1) const lv = lvArg !== undefined ? (lvArg || offsetDate(today, -1)) : (lastViewed || savedLastReviewed || offsetDate(today, -1))
try { try {
const [b, cfg] = await Promise.all([getBookings(ws, lv, force), getConfig()]) const [b, cfg] = await Promise.all([getBookings(ws, lv, force), getConfig()])
setBookings(b) setBookings(b)
@ -221,13 +221,13 @@ export function Planner() {
const newStart = offsetDate(weekStart || today, days) const newStart = offsetDate(weekStart || today, days)
setWeekStart(newStart) setWeekStart(newStart)
stampLastReviewed() stampLastReviewed()
setTimeout(() => loadAll(true), 0) loadAll(true, newStart)
} }
function handleWeekToday() { function handleWeekToday() {
setWeekStart(null) setWeekStart(null)
stampLastReviewed() stampLastReviewed()
setTimeout(() => loadAll(true), 0) loadAll(true, null)
} }
function handleRefresh() { function handleRefresh() {
@ -338,7 +338,7 @@ export function Planner() {
<input <input
type="date" type="date"
value={displayWeekStart} value={displayWeekStart}
onChange={e => { if (e.target.value) { setWeekStart(e.target.value); stampLastReviewed(); setTimeout(() => loadAll(true), 0) } }} onChange={e => { if (e.target.value) { setWeekStart(e.target.value); stampLastReviewed(); loadAll(true, e.target.value) } }}
style={{ border: '1px solid var(--card-border)', borderRadius: '6px', padding: '0.35rem 0.5rem', fontSize: '0.82rem', color: 'var(--text-dark)' }} style={{ border: '1px solid var(--card-border)', borderRadius: '6px', padding: '0.35rem 0.5rem', fontSize: '0.82rem', color: 'var(--text-dark)' }}
/> />
<div style={{ width: '1px', height: '24px', background: 'var(--card-border)' }} /> <div style={{ width: '1px', height: '24px', background: 'var(--card-border)' }} />
@ -348,7 +348,7 @@ export function Planner() {
<input <input
type="date" type="date"
value={lastViewed} value={lastViewed}
onChange={e => { if (e.target.value) { setLastViewed(e.target.value); setTimeout(() => loadAll(true), 0) } }} onChange={e => { if (e.target.value) { setLastViewed(e.target.value); loadAll(true, undefined, e.target.value) } }}
style={{ border: '1px solid var(--card-border)', borderRadius: '6px', padding: '0.3rem 0.5rem', fontSize: '0.78rem', color: 'var(--text-dark)' }} style={{ border: '1px solid var(--card-border)', borderRadius: '6px', padding: '0.3rem 0.5rem', fontSize: '0.78rem', color: 'var(--text-dark)' }}
/> />
</label> </label>