mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
feat(insights): add inline edit mode to reorder and hide overview cards
This commit is contained in:
1 parent
e3539d7c94
commit
9b13b41f1a
10 files changed
+915
-11
No files matched your search
@@ -0,0 +1,68 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { reorderById } from "@/lib/insights-layout-reorder";
|
||||
|
||||
describe("reorderById", () => {
|
||||
const rows = [
|
||||
{ id: "a", order: 0, visible: true },
|
||||
{ id: "b", order: 1, visible: false },
|
||||
{ id: "c", order: 2, visible: true },
|
||||
];
|
||||
|
||||
it("moves a row forward and renumbers densely", () => {
|
||||
const next = reorderById(rows, "a", "c");
|
||||
expect(next.map((r) => r.id)).toEqual(["b", "c", "a"]);
|
||||
expect(next.map((r) => r.order)).toEqual([0, 1, 2]);
|
||||
});
|
||||
|
||||
it("moves a row backward and renumbers densely", () => {
|
||||
const next = reorderById(rows, "c", "a");
|
||||
expect(next.map((r) => r.id)).toEqual(["c", "a", "b"]);
|
||||
expect(next.map((r) => r.order)).toEqual([0, 1, 2]);
|
||||
});
|
||||
|
||||
it("preserves every non-order field on each row", () => {
|
||||
const next = reorderById(rows, "a", "b");
|
||||
const moved = next.find((r) => r.id === "a");
|
||||
expect(moved?.visible).toBe(true);
|
||||
const hidden = next.find((r) => r.id === "b");
|
||||
expect(hidden?.visible).toBe(false);
|
||||
});
|
||||
|
||||
it("is a no-op (but still densely renumbered) when ids match", () => {
|
||||
const next = reorderById(rows, "b", "b");
|
||||
expect(next.map((r) => r.id)).toEqual(["a", "b", "c"]);
|
||||
expect(next.map((r) => r.order)).toEqual([0, 1, 2]);
|
||||
});
|
||||
|
||||
it("returns a densely renumbered copy when an id is missing", () => {
|
||||
const sparse = [
|
||||
{ id: "a", order: 5 },
|
||||
{ id: "b", order: 9 },
|
||||
];
|
||||
const next = reorderById(sparse, "a", "zzz");
|
||||
expect(next).toEqual([
|
||||
{ id: "a", order: 0 },
|
||||
{ id: "b", order: 1 },
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not mutate the input array or its rows", () => {
|
||||
const input = [
|
||||
{ id: "a", order: 0 },
|
||||
{ id: "b", order: 1 },
|
||||
];
|
||||
const snapshot = JSON.parse(JSON.stringify(input));
|
||||
reorderById(input, "a", "b");
|
||||
expect(input).toEqual(snapshot);
|
||||
});
|
||||
|
||||
it("sorts an unsorted input by order before reordering", () => {
|
||||
const unsorted = [
|
||||
{ id: "c", order: 2 },
|
||||
{ id: "a", order: 0 },
|
||||
{ id: "b", order: 1 },
|
||||
];
|
||||
const next = reorderById(unsorted, "a", "c");
|
||||
expect(next.map((r) => r.id)).toEqual(["b", "c", "a"]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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 }));
|
||||
}
|
||||
Reference in new issue
Block a user