Files
GearBox/src/client/lib/impactDeltas.ts
Jean-Luc Makiola 818db73432 feat: add impact delta computation with TDD tests
Implements computeImpactDeltas pure function with 8 TDD tests covering replace/add/none modes and null weight/price handling. Adds useImpactDeltas hook, categoryId to ThreadWithCandidates, and selectedSetupId state to uiStore.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:06:46 +02:00

70 lines
1.6 KiB
TypeScript

export interface CandidateInput {
id: number;
weightGrams: number | null;
priceCents: number | null;
}
export interface SetupItemInput {
categoryId: number;
weightGrams: number | null;
priceCents: number | null;
name: string;
}
export type DeltaMode = "replace" | "add" | "none";
export interface CandidateDelta {
candidateId: number;
mode: DeltaMode;
weightDelta: number | null;
priceDelta: number | null;
replacedItemName: string | null;
}
export interface ImpactDeltas {
mode: DeltaMode;
deltas: Record<number, CandidateDelta>;
}
export function computeImpactDeltas(
candidates: CandidateInput[],
setupItems: SetupItemInput[] | undefined,
threadCategoryId: number,
): ImpactDeltas {
if (!setupItems) return { mode: "none", deltas: {} };
const replacedItem =
setupItems.find((item) => item.categoryId === threadCategoryId) ?? null;
const mode: DeltaMode = replacedItem ? "replace" : "add";
const deltas: Record<number, CandidateDelta> = {};
for (const candidate of candidates) {
let weightDelta: number | null = null;
let priceDelta: number | null = null;
if (candidate.weightGrams != null) {
weightDelta =
replacedItem?.weightGrams != null
? candidate.weightGrams - replacedItem.weightGrams
: candidate.weightGrams;
}
if (candidate.priceCents != null) {
priceDelta =
replacedItem?.priceCents != null
? candidate.priceCents - replacedItem.priceCents
: candidate.priceCents;
}
deltas[candidate.id] = {
candidateId: candidate.id,
mode,
weightDelta,
priceDelta,
replacedItemName: replacedItem?.name ?? null,
};
}
return { mode, deltas };
}