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>
70 lines
1.6 KiB
TypeScript
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 };
|
|
}
|