dashboards: drive list selection from a ?selected= url param, not a global

the /dashboards list row was a <div onClick> that set SELECTED_DASHBOARD_KEY (a
global) with no url change on-page, so selection wasn't deep-linkable and the row
wasn't a real link. selection now lives in the url as ?selected=<id>, read by the
list, the screen (mobile panel), and the preview. rows are real <Link>s (edit/delete
kept as sibling buttons, not nested in the anchor); the preview + its "open" link are
unchanged. edit-save and delete update/clear the param.

NOT runtime-tested yet (server not restarted) — verify the /dashboards preview,
create/edit/delete, and mobile panel flows on next restart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 05:32:10 +00:00
co-authored by Claude Opus 4.8
parent 04073dc6a0
commit 1b6b980af0
3 changed files with 62 additions and 44 deletions
@@ -1,14 +1,16 @@
import type { LayoutNode } from 'officerdev';
import { WorkspaceView, SELECTED_DASHBOARD_KEY } from 'officerdev';
import { useSearchParams } from 'react-router';
import { WorkspaceView } from 'officerdev';
import { useIsMobile } from 'hooks/useIsMobile';
import { useGlobal } from 'hooks/useGlobal';
import { useDashboardState } from 'state/useDashboardState';
import { defaultLayout } from './defaultLayout';
export const DashboardsScreen = () => {
const workspace = useDashboardState<LayoutNode>('screens/dashboards', defaultLayout);
const isMobile = useIsMobile();
const [selected, setSelected] = useGlobal<string | null>(SELECTED_DASHBOARD_KEY, null);
// Selection lives in the URL (?selected=<id>); on mobile it drives which panel is shown.
const [searchParams, setSearchParams] = useSearchParams();
const selected = searchParams.get('selected');
const mobilePanelId = isMobile && selected ? 'ws-home-right' : undefined;
return (
@@ -18,7 +20,14 @@ export const DashboardsScreen = () => {
locked
mobilePanelId={mobilePanelId}
onMobilePanelChange={(id) => {
if (!id) setSelected(null);
if (!id)
setSearchParams(
(p) => {
p.delete('selected');
return p;
},
{ replace: true },
);
}}
/>
</div>