All files / src/features/items item-panel-fields.ts

96% Statements 24/25
88.88% Branches 16/18
100% Functions 5/5
94.73% Lines 18/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47    4x 4x 4x                 144x 12x                 4x 256x             6x 6x 41x 7x 7x 1x   6x   5x 20x   5x 5x    
import type { Item } from "@/types";
 
export const NAME_NEEDED = "Give it a name to save";
const TEXT = ["name", "brand", "style", "color", "material", "category", "notes", "story"] as const;
const MEASURES = ["height", "width", "depth", "weight"] as const;
type TextField = (typeof TEXT)[number];
type Measure = (typeof MEASURES)[number];
export type Unit = "imperial" | "metric";
export type PanelFields = Record<TextField | Measure, string> & { unit: Unit };
export type ItemPatch = { name?: string; unit?: Unit } & Partial<Record<Exclude<TextField, "name">, string | null>> &
  Partial<Record<Measure, number | null>>;
 
export function fieldsFromItem(item: Item): PanelFields {
  const s = (v: string | null) => v ?? "";
  return {
    name: s(item.name), brand: s(item.brand), style: s(item.style), color: s(item.color),
    material: s(item.material), category: s(item.category), notes: s(item.notes), story: s(item.story),
    height: s(item.height), width: s(item.width), depth: s(item.depth), weight: s(item.weight),
    // Legacy rows hold "in"; anything that isn't metric reads as imperial.
    unit: item.unit === "metric" ? "metric" : "imperial",
  };
}
 
export const isDirty = (a: PanelFields, b: PanelFields) =>
  (Object.keys(a) as (keyof PanelFields)[]).some((k) => a[k] !== b[k]);
 
/** Only the fields that changed since the last save — never the whole record. */
export function buildItemPatch(
  saved: PanelFields,
  current: PanelFields,
): { ok: true; patch: ItemPatch } | { ok: false; reason: "blank-name" } {
  const patch: ItemPatch = {};
  for (const f of TEXT) {
    if (current[f] === saved[f]) continue;
    const v = current[f].trim();
    if (f === "name") {
      Eif (!v) return { ok: false, reason: "blank-name" };
      patch.name = v;
    } else patch[f] = v === "" ? null : v;
  }
  for (const f of MEASURES) {
    if (current[f] !== saved[f]) patch[f] = current[f].trim() === "" ? null : Number(current[f]);
  }
  if (current.unit !== saved.unit) patch.unit = current.unit;
  return { ok: true, patch };
}