Fix student profile dates not saving

Native date pickers blur/refocus the window, which triggered a React Query
refetch mid-edit and wiped the in-progress date. Disable refetchOnWindowFocus,
and make profile/academics edits work on a snapshot with functional state
updates so a background refetch can't clobber input.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-19 17:50:53 -04:00
co-authored by Claude Opus 4.8
parent 8880d7a805
commit eb94c7ec34
2 changed files with 24 additions and 12 deletions
+8 -1
View File
@@ -3,7 +3,14 @@ import { createRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen"; import { routeTree } from "./routeTree.gen";
export const getRouter = () => { export const getRouter = () => {
const queryClient = new QueryClient(); const queryClient = new QueryClient({
defaultOptions: {
// Don't refetch when the window regains focus — opening a native date
// picker blurs/refocuses the window, which would refetch mid-edit and
// wipe in-progress form input.
queries: { refetchOnWindowFocus: false },
},
});
const router = createRouter({ const router = createRouter({
routeTree, routeTree,
+16 -11
View File
@@ -159,9 +159,13 @@ function ProfileTab({ studentId, canEdit, isAdmin }: { studentId: string; canEdi
queryKey: ["classes"], queryKey: ["classes"],
queryFn: async () => (await supabase.from("classes").select("id, name").order("name")).data ?? [], queryFn: async () => (await supabase.from("classes").select("id, name").order("name")).data ?? [],
}); });
const [form, setForm] = useState<Record<string, unknown> | null>(null); // In edit mode we work on a snapshot (`form`) taken when Edit is pressed, so a
const c = (form ?? s) as Record<string, unknown> | null; // background refetch can never clobber in-progress input. Functional updates
const upd = (k: string, v: unknown) => setForm({ ...(c as Record<string, unknown>), [k]: v }); // avoid stale-closure races.
const [form, setForm] = useState<Record<string, unknown>>({});
const startEdit = () => { setForm({ ...(s as Record<string, unknown>) }); setEditing(true); };
const c = (editing ? form : s) as Record<string, unknown> | null;
const upd = (k: string, v: unknown) => setForm((f) => ({ ...f, [k]: v }));
const dismissal = (c?.dismissal_methods as string[]) ?? []; const dismissal = (c?.dismissal_methods as string[]) ?? [];
const toggleDismissal = (v: string) => upd("dismissal_methods", dismissal.includes(v) ? dismissal.filter((x) => x !== v) : [...dismissal, v]); const toggleDismissal = (v: string) => upd("dismissal_methods", dismissal.includes(v) ? dismissal.filter((x) => x !== v) : [...dismissal, v]);
@@ -180,7 +184,7 @@ function ProfileTab({ studentId, canEdit, isAdmin }: { studentId: string; canEdi
const { error } = await supabase.from("students").update(payload as never).eq("id", studentId); const { error } = await supabase.from("students").update(payload as never).eq("id", studentId);
if (error) throw error; if (error) throw error;
}, },
onSuccess: () => { toast.success("Profile saved"); qc.invalidateQueries({ queryKey: ["student", studentId] }); setForm(null); setEditing(false); }, onSuccess: () => { toast.success("Profile saved"); qc.invalidateQueries({ queryKey: ["student", studentId] }); setEditing(false); },
onError: (e: Error) => toast.error(e.message), onError: (e: Error) => toast.error(e.message),
}); });
@@ -192,7 +196,7 @@ function ProfileTab({ studentId, canEdit, isAdmin }: { studentId: string; canEdi
if (!editing) { if (!editing) {
return ( return (
<div className="bg-card border rounded-lg p-6 space-y-6 mt-4"> <div className="bg-card border rounded-lg p-6 space-y-6 mt-4">
<div className="flex justify-end"><EditToggle editing={editing} setEditing={setEditing} canEdit={canEdit} /></div> <div className="flex justify-end">{canEdit && <Button size="sm" variant="outline" onClick={startEdit}><Pencil className="h-4 w-4 mr-1" /> Edit</Button>}</div>
<Section title="Student"> <Section title="Student">
<ViewRow label="First name" value={val("first_name")} /> <ViewRow label="First name" value={val("first_name")} />
<ViewRow label="Last name" value={val("last_name")} /> <ViewRow label="Last name" value={val("last_name")} />
@@ -281,7 +285,7 @@ function ProfileTab({ studentId, canEdit, isAdmin }: { studentId: string; canEdi
</Section> </Section>
<div className="flex gap-2"> <div className="flex gap-2">
<Button onClick={() => save.mutate()} disabled={save.isPending}>{save.isPending ? "Saving…" : "Save profile"}</Button> <Button onClick={() => save.mutate()} disabled={save.isPending}>{save.isPending ? "Saving…" : "Save profile"}</Button>
<Button variant="ghost" onClick={() => { setForm(null); setEditing(false); }}>Cancel</Button> <Button variant="ghost" onClick={() => setEditing(false)}>Cancel</Button>
</div> </div>
</div> </div>
); );
@@ -513,9 +517,10 @@ function AcademicsTab({ studentId, canEdit }: { studentId: string; canEdit: bool
queryKey: ["student", studentId], queryKey: ["student", studentId],
queryFn: async () => (await supabase.from("students").select("*").eq("id", studentId).single()).data, queryFn: async () => (await supabase.from("students").select("*").eq("id", studentId).single()).data,
}); });
const [form, setForm] = useState<Record<string, unknown> | null>(null); const [form, setForm] = useState<Record<string, unknown>>({});
const c = (form ?? s) as Record<string, unknown> | null; const startEdit = () => { setForm({ ...(s as Record<string, unknown>) }); setEditing(true); };
const upd = (k: string, v: unknown) => setForm({ ...(c as Record<string, unknown>), [k]: v }); const c = (editing ? form : s) as Record<string, unknown> | null;
const upd = (k: string, v: unknown) => setForm((f) => ({ ...f, [k]: v }));
const val = (k: string) => (c?.[k] as string) ?? ""; const val = (k: string) => (c?.[k] as string) ?? "";
const { data: logins } = useQuery({ const { data: logins } = useQuery({
queryKey: ["curriculum", studentId], queryKey: ["curriculum", studentId],
@@ -530,7 +535,7 @@ function AcademicsTab({ studentId, canEdit }: { studentId: string; canEdit: bool
}).eq("id", studentId); }).eq("id", studentId);
if (error) throw error; if (error) throw error;
}, },
onSuccess: () => { toast.success("Saved"); qc.invalidateQueries({ queryKey: ["student", studentId] }); setForm(null); }, onSuccess: () => { toast.success("Saved"); qc.invalidateQueries({ queryKey: ["student", studentId] }); },
onError: (e: Error) => toast.error(e.message), onError: (e: Error) => toast.error(e.message),
}); });
const addLogin = useMutation({ const addLogin = useMutation({
@@ -543,7 +548,7 @@ function AcademicsTab({ studentId, canEdit }: { studentId: string; canEdit: bool
return ( return (
<div className="space-y-6 mt-4"> <div className="space-y-6 mt-4">
<div className="flex justify-end"><EditToggle editing={editing} setEditing={setEditing} canEdit={canEdit} /></div> <div className="flex justify-end"><EditToggle editing={editing} setEditing={(v) => (v ? startEdit() : setEditing(false))} canEdit={canEdit} /></div>
<div className="bg-card border rounded-lg p-6 space-y-4"> <div className="bg-card border rounded-lg p-6 space-y-4">
{!editing ? ( {!editing ? (