mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
- Layout PUT accepts version 1 as well as 2 (live iOS client still PUTs version 1); the serializer normalises to the canonical v2 blob, so a v1 body never 422s. Mirrored in the OpenAPI schema + description; yaml regen. - Add the Weight tile to the inline tile editor so it can be hidden / reordered like every other Vitals grid tile. - Replace the non-transitive Vitals tile-drag comparator with a pure, total-order rebuild helper (reorder the Vitals subset, substitute it back into the full list in original-order slots, densify). - Gated-off section rows keep a disabled eye toggle in the same position with the gate hint as a caption below the title, so the row never jumps. - Cap the disclosed Vitals tile list height (max-h-[60vh] scroll, overscroll-contain) so a tile drag does not fight the page scroll. - Gate edit mode until the layout GET settles so a save can never flush defaults over a real saved layout. - Drop the duplicate top-right "Done" affordance: hide the customize toggle while editing; the in-card Done / Reset own save and reset. - Restore focus on enter (edit-card heading) and on close (customize toggle). Tests: route regression for a version-1 PUT persisting a resolved v2 layout; unit coverage for the new Vitals rebuild helper (order, non-Vitals stability, per-tile visibility, no mutation).
74 lines
3.0 KiB
TypeScript
74 lines
3.0 KiB
TypeScript
/**
|
|
* v1.15.11 W3 — pure reorder helper for the inline Insights edit mode.
|
|
*
|
|
* Mirrors the `reorderWidgets` contract in
|
|
* `src/components/settings/dashboard-layout-section.tsx` (move an item from
|
|
* `fromId` next to `toId`, renumber the whole list to a dense 0-based
|
|
* `order`) but is generic over any `{ id, order }` row so the section list
|
|
* AND the Vitals tile sub-list can both reorder through one tested helper.
|
|
*
|
|
* Kept as a standalone module (not imported from the dashboard component) so
|
|
* the inline-edit surface does not depend on a Settings component, and so the
|
|
* helper is unit-testable without spinning up a `DndContext`. The dashboard's
|
|
* own `reorderWidgets` stays untouched — extracting it there would have
|
|
* touched the stable dashboard surface for no behavioural gain.
|
|
*/
|
|
export interface OrderableRow {
|
|
id: string;
|
|
order: number;
|
|
}
|
|
|
|
/**
|
|
* Move the row identified by `fromId` to the position of `toId`, then
|
|
* renumber every row to a dense 0-based `order`. Returns a NEW array of NEW
|
|
* row objects (every input field is preserved via the spread); the input is
|
|
* never mutated. A no-op move (same id, or either id missing) still returns a
|
|
* densely-renumbered copy so the caller can treat the output as canonical.
|
|
*/
|
|
export function reorderById<T extends OrderableRow>(
|
|
rows: readonly T[],
|
|
fromId: string,
|
|
toId: string,
|
|
): T[] {
|
|
const sorted = [...rows].sort((a, b) => a.order - b.order);
|
|
const fromIdx = sorted.findIndex((r) => r.id === fromId);
|
|
const toIdx = sorted.findIndex((r) => r.id === toId);
|
|
if (fromIdx < 0 || toIdx < 0 || fromIdx === toIdx) {
|
|
return sorted.map((r, i) => ({ ...r, order: i }));
|
|
}
|
|
const next = [...sorted];
|
|
const [removed] = next.splice(fromIdx, 1);
|
|
next.splice(toIdx, 0, removed);
|
|
return next.map((r, i) => ({ ...r, order: i }));
|
|
}
|
|
|
|
/**
|
|
* v1.15.11 QA M2 — rebuild a full tile list after reordering ONLY the Vitals
|
|
* subset, without the non-transitive mixed-key comparator the inline edit mode
|
|
* used to run. Walks the ORIGINAL `order` of every tile: a non-Vitals tile
|
|
* (the routed sub-page strip) keeps its slot verbatim; each Vitals slot, in
|
|
* original-order position, is filled by the next id from `reorderedVitalsIds`.
|
|
* The result is densely renumbered 0-based so the persisted blob stays
|
|
* canonical. Pure + total-order by construction — no `sort` with a key that
|
|
* flips per pair.
|
|
*
|
|
* `tiles` is the full draft list; `isVitals` decides membership; the returned
|
|
* array preserves every non-`order` field on each row.
|
|
*/
|
|
export function rebuildTilesWithReorderedVitals<T extends OrderableRow>(
|
|
tiles: readonly T[],
|
|
reorderedVitalsIds: readonly string[],
|
|
isVitals: (id: string) => boolean,
|
|
): T[] {
|
|
const original = [...tiles].sort((a, b) => a.order - b.order);
|
|
const byId = new Map(tiles.map((t) => [t.id, t]));
|
|
let vIdx = 0;
|
|
return original
|
|
.map((t) => {
|
|
if (!isVitals(t.id)) return t;
|
|
const replacementId = reorderedVitalsIds[vIdx++] ?? t.id;
|
|
return byId.get(replacementId) ?? t;
|
|
})
|
|
.map((t, i) => ({ ...t, order: i }));
|
|
}
|