mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 13:22:14 +00:00
The arrange card sent the tile list it copied on mount, with a token read fresh from the cache, so a pill-order save made beside it was overwritten without a conflict. Send the current server tiles, and let an untouched section draft follow the server copy so the pill manager's ECG toggle no longer reads as a pending edit.
82 lines
2.6 KiB
TypeScript
82 lines
2.6 KiB
TypeScript
/**
|
|
* Pure draft logic for the Insights overview arrange editor.
|
|
*
|
|
* The editor owns `sections` (order + visibility) only. The pill manager on
|
|
* Settings → Insights writes the same `/api/insights/layout` blob, so a save
|
|
* here must carry the CURRENT server tiles rather than a copy taken when the
|
|
* editor mounted: the base token is read from the shared cache at save time,
|
|
* so a stale tile copy would never 409 and would silently undo a pill save.
|
|
*
|
|
* For the same reason the section draft follows the server copy whenever the
|
|
* user has not touched it — the pill manager also flips the `"ecg"` section's
|
|
* visibility, and an untouched draft must not turn that into a pending edit.
|
|
*/
|
|
import type {
|
|
InsightsLayout,
|
|
InsightsSectionConfig,
|
|
InsightsTileConfig,
|
|
} from "@/lib/insights-layout";
|
|
|
|
export function sortByOrder<T extends { order: number }>(
|
|
rows: readonly T[],
|
|
): T[] {
|
|
return [...rows].sort((a, b) => a.order - b.order);
|
|
}
|
|
|
|
/** Order + visibility fingerprint of a section list. */
|
|
export function arrangementSignature(
|
|
rows: readonly InsightsSectionConfig[],
|
|
): string {
|
|
return sortByOrder(rows)
|
|
.map((r) => `${r.id}:${r.visible ? 1 : 0}`)
|
|
.join(",");
|
|
}
|
|
|
|
export interface ArrangeDraftState {
|
|
/** Server signature the draft was last seeded from. */
|
|
seeded: string;
|
|
sections: InsightsSectionConfig[];
|
|
}
|
|
|
|
export function seedArrangeDraft(
|
|
serverSections: readonly InsightsSectionConfig[],
|
|
): ArrangeDraftState {
|
|
return {
|
|
seeded: arrangementSignature(serverSections),
|
|
sections: sortByOrder(serverSections),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Follow a server-side change to the sections. Returns `null` when nothing
|
|
* needs to change. An untouched draft re-seeds; a draft carrying the user's
|
|
* own edits is kept, only the baseline advances.
|
|
*/
|
|
export function reconcileArrangeDraft(
|
|
state: ArrangeDraftState,
|
|
serverSections: readonly InsightsSectionConfig[],
|
|
): ArrangeDraftState | null {
|
|
const server = arrangementSignature(serverSections);
|
|
if (server === state.seeded) return null;
|
|
const untouched = arrangementSignature(state.sections) === state.seeded;
|
|
return untouched
|
|
? seedArrangeDraft(serverSections)
|
|
: { seeded: server, sections: state.sections };
|
|
}
|
|
|
|
/** The PUT body: the draft's sections over the current server tiles. */
|
|
export function arrangeSavePayload(
|
|
draftSections: readonly InsightsSectionConfig[],
|
|
current: Pick<InsightsLayout, "tiles">,
|
|
): {
|
|
version: 2;
|
|
sections: InsightsSectionConfig[];
|
|
tiles: InsightsTileConfig[];
|
|
} {
|
|
return {
|
|
version: 2,
|
|
sections: [...draftSections],
|
|
tiles: [...current.tiles],
|
|
};
|
|
}
|