Files
HealthLog/src/components/medications/sections/inventory-shared.ts
T
Marc-André Bombeck 832a269ab3 refactor(medications): split the supply tab at the dialog boundary
The Bestand tab was 1,139 lines of one client file holding two concerns that
only meet at the row shape: the list of containers a person holds, and the
three dialogs that change it. Registration, per-item correction, packaging
economics, the summary and the row rendering all shared a scope, so reading
any one of them meant scrolling past the rest.

Move-only. The dialogs go to `inventory-dialogs.tsx` with the day-input
conversions their fields produce; the row shape and the cache-invalidation
they both need go to `inventory-shared.ts`; the section keeps the summary and
the list at 458 lines. Every export the tab offered is still offered here, so
no caller changes. No copy changes, no new keys.

Two suites pin these contracts by reading the file's own source — the register
dialog's unit conversion and the correction dialog's sparse patch. Their
`readFileSync` now names the file the code moved to. The assertions are
unchanged, byte for byte; only the path they read is.
2026-07-27 14:47:55 +02:00

64 lines
2.1 KiB
TypeScript

/**
* The facts the supply tab's two halves both need.
*
* The tab is a list of containers on one side and three write dialogs on the
* other, and they meet at exactly this much: the row shape the API returns,
* the container kinds it can carry, and the cache keys a write has to drop.
* Keeping that here is what lets the two halves live in their own files
* without either importing the other's rendering.
*/
import type { QueryClient } from "@tanstack/react-query";
import { queryKeys } from "@/lib/query-keys";
/**
* Invalidate every read key whose payload reflects a medication's supply
* after a container write (register / adjust / delete).
*
* The per-medication inventory read (`medicationInventory`) is the supply
* tab's own list. The medications LIST read (`medications`) carries the
* dose-derived stock (`stockUnitsRemaining` / `stockDosesRemaining`) that
* the card and table render — without invalidating it, the card kept
* showing the pre-write stock until an unrelated refetch landed (the
* supply-staleness bug). Both keys must drop together.
*/
export async function invalidateSupplyQueries(
queryClient: QueryClient,
medicationId: string,
): Promise<void> {
await Promise.all([
queryClient.invalidateQueries({
queryKey: queryKeys.medicationInventory(medicationId),
}),
queryClient.invalidateQueries({
queryKey: queryKeys.medications(),
}),
]);
}
export type InventoryState = "ACTIVE" | "IN_USE" | "EXPIRED" | "USED_UP";
export const CONTAINER_TYPES = [
"PEN",
"AMPOULE",
"BLISTER",
"INHALER",
"BOTTLE",
"OTHER",
] as const;
export type ContainerType = (typeof CONTAINER_TYPES)[number];
export interface InventoryItem {
id: string;
state: InventoryState;
containerType: ContainerType;
// v1.18.3 (iOS#31) — null = unknown unit count (corrupt / legacy row);
// the UI renders "—" instead of a fabricated 0.
unitsTotal: number | null;
unitsRemaining: number | null;
/** ISO instant. The carton-printed expiry; null when none was recorded. */
printedExpiry: string | null;
/** ISO instant. When the container was opened; null while sealed. */
firstUseAt: string | null;
}