Files
HealthLog/src/lib/onboarding/dashboard-seed.ts
T
Marc-André Bombeck fea1d1e0b5 chore: strip planning tags from comments and enable the rule
Output of `node scripts/strip-planning-tags.mjs --write`: 3,157 comment
lines in 1,308 files lose the work-item identifier at their head. Release
versions and issue references stay; nothing outside comment text
changes, which the script checks per file against a comment-free
printout. healthlog/no-planning-tags is enabled at error level with this
commit, so the tree it lands on is clean.
2026-10-20 09:08:40 +02:00

101 lines
3.7 KiB
TypeScript

/**
* v1.39 — the dashboard tile order the setup answers imply.
*
* "Enables the matching modules and orders the dashboard" (design spec §The
* questions, Q2): the module half lives in the registry's derivation, this
* is the ordering half. Each area names the tiles it is about; the seed
* promotes them to the top of the default layout and forces them visible on
* both surfaces, and leaves every other tile exactly where the default puts
* it. A daily medication schedule promotes the medication tile the same way.
*
* ONE-TIME, and the caller gates it: the completion route writes the seed
* only while `User.dashboardWidgetsJson` is still unset, so a person who
* already arranged their tiles is never clobbered, and a concurrent layout
* save that lands first wins. Not a clinical target store — targets are
* derived elsewhere; this only decides what is on top.
*
* Replaces the six goal slugs of the five-step wizard, whose only durable
* effect was this same seed.
*/
import {
DEFAULT_DASHBOARD_LAYOUT,
serializeDashboardLayout,
type DashboardLayout,
type DashboardWidgetId,
} from "@/lib/dashboard-layout";
import type { OnboardingAreaKey } from "@/lib/modules/registry";
import type { OnboardingNeeds } from "./needs";
/**
* Area → the tiles to promote. An area whose surface is not a dashboard tile
* (cycle, labs, illness) maps to nothing; its module is still switched on.
* Ids must be members of `DASHBOARD_WIDGET_IDS`; the seed never invents one.
*/
export const AREA_WIDGET_SEED_MAP: Readonly<
Record<OnboardingAreaKey, readonly DashboardWidgetId[]>
> = Object.freeze({
"blood-pressure": ["bp", "bpInTarget", "pulse"],
"weight-body": ["weight", "bodyFat"],
glucose: ["glucose"],
sleep: ["sleep"],
mood: ["mood"],
cycle: [],
activity: ["steps"],
labs: [],
illness: [],
});
/** Q3 "yes" or "sometimes" promotes the medication tile. */
const MEDICATION_WIDGET_SEED: readonly DashboardWidgetId[] = ["medications"];
/**
* The tiles the answers promote, in the default layout's order. Empty when
* the answers speak to no tile, which is the "leave the column unset" case.
*/
export function promotedWidgetsFor(
needs: Pick<OnboardingNeeds, "areas" | "medication">,
): DashboardWidgetId[] {
const promoted = new Set<DashboardWidgetId>();
for (const area of needs.areas) {
for (const id of AREA_WIDGET_SEED_MAP[area] ?? []) promoted.add(id);
}
if (needs.medication === "yes" || needs.medication === "sometimes") {
for (const id of MEDICATION_WIDGET_SEED) promoted.add(id);
}
return DEFAULT_DASHBOARD_LAYOUT.widgets
.map((w) => w.id as DashboardWidgetId)
.filter((id) => promoted.has(id));
}
/**
* Build the seeded layout, or `null` when there is nothing to promote so the
* caller can skip the write entirely and leave the column unset (the default
* layout). Every tile keeps its default config; the promoted ones move to the
* front, in their default relative order, and are forced visible on both
* surfaces. The result is dense: `order` is 0..n-1 with no tile dropped.
*/
export function buildNeedsSeededDashboardLayout(
needs: Pick<OnboardingNeeds, "areas" | "medication">,
): DashboardLayout | null {
const promoted = new Set(promotedWidgetsFor(needs));
if (promoted.size === 0) return null;
const base = DEFAULT_DASHBOARD_LAYOUT.widgets;
const front = base.filter((w) => promoted.has(w.id as DashboardWidgetId));
const rest = base.filter((w) => !promoted.has(w.id as DashboardWidgetId));
const ordered = [...front, ...rest].map((w, index) => ({
...w,
...(promoted.has(w.id as DashboardWidgetId)
? { visible: true, tileVisible: true }
: {}),
order: index,
}));
return serializeDashboardLayout({
...DEFAULT_DASHBOARD_LAYOUT,
widgets: ordered,
});
}