feat: replace hardcoded package assumptions with profile abstraction (#379)

This commit is contained in:
Daniel Volz
2026-03-04 21:15:05 +01:00
committed by GitHub
parent 6672fb78c9
commit 4936929849
23 changed files with 440 additions and 289 deletions
+49 -56
View File
@@ -15,7 +15,14 @@ import { useTranslation } from "react-i18next";
import { Lightbox, MedicationAvatar } from "../components";
import { useEscapeKey } from "../hooks";
import type { Coverage, Medication, RefillEntry, StockThresholds } from "../types";
import { getMedDisplayName, getMedTotal, getPackageSize } from "../types";
import {
getMedDisplayName,
getMedTotal,
getPackageSize,
isAmountBasedPackageType,
isLiquidContainerPackageType,
isTubePackageType,
} from "../types";
import { formatNumber, generateICS, getExpiryClass, getSystemLocale } from "../utils";
import { getStockStatus } from "../utils/schedule";
import { splitCurrentBlisterStock } from "../utils/stock";
@@ -170,7 +177,7 @@ export function MedDetailModal({
}, [showEditStockModal]);
const remainingPrescriptionRefills = Math.max(0, Number(selectedMed?.prescriptionRemainingRefills) || 0);
const prescriptionPackCapEnabled = selectedMed?.packageType === "blister" && usePrescriptionRefill;
const prescriptionPackCapEnabled = !isAmountBasedPackageType(selectedMed?.packageType) && usePrescriptionRefill;
const cappedRefillPacks = prescriptionPackCapEnabled
? Math.min(refillPacks, remainingPrescriptionRefills)
: refillPacks;
@@ -179,7 +186,7 @@ export function MedDetailModal({
useEffect(() => {
if (!selectedMed) return;
if (!showRefillModal) return;
if (selectedMed.packageType !== "blister" || !usePrescriptionRefill) return;
if (isAmountBasedPackageType(selectedMed.packageType) || !usePrescriptionRefill) return;
if (refillPacks <= remainingPrescriptionRefills) return;
onRefillPacksChange(remainingPrescriptionRefills);
}, [
@@ -192,9 +199,10 @@ export function MedDetailModal({
]);
if (!selectedMed) return null;
const isAmountPackage = selectedMed.packageType === "tube" || selectedMed.packageType === "liquid_container";
const isAmountPackage =
isTubePackageType(selectedMed.packageType) || isLiquidContainerPackageType(selectedMed.packageType);
const amountUnitLabel =
selectedMed.packageType === "liquid_container" || selectedMed.medicationForm === "liquid"
isLiquidContainerPackageType(selectedMed.packageType) || selectedMed.medicationForm === "liquid"
? t("form.packageAmountUnitMl")
: t("form.packageAmountUnitG");
const stockUnitLabel = isAmountPackage ? amountUnitLabel : null;
@@ -202,12 +210,9 @@ export function MedDetailModal({
const medCoverage = coverage.all.find((c) => c.name === getMedDisplayName(selectedMed));
const packageSize = getPackageSize(selectedMed);
// Structural max = sealed package capacity only (excludes pre-existing looseTablets).
const structuralMax =
selectedMed.packageType === "bottle" ||
selectedMed.packageType === "tube" ||
selectedMed.packageType === "liquid_container"
? (selectedMed.totalPills ?? packageSize)
: selectedMed.packCount * selectedMed.blistersPerPack * selectedMed.pillsPerBlister;
const structuralMax = isAmountBasedPackageType(selectedMed.packageType)
? (selectedMed.totalPills ?? packageSize)
: selectedMed.packCount * selectedMed.blistersPerPack * selectedMed.pillsPerBlister;
const currentStock = medCoverage ? Math.round(medCoverage.medsLeft) : getMedTotal(selectedMed);
const status = medCoverage ? getStockStatus(medCoverage.daysLeft, medCoverage.medsLeft, settings) : null;
const fallbackTextClass = status?.className === "warning" ? "warning-text" : "success-text";
@@ -216,12 +221,9 @@ export function MedDetailModal({
const currentFullBlisters = Math.max(0, stock.fullBlisters);
const currentPartialPills = Math.max(0, stock.openBlisterPills);
const currentLoosePills = Math.max(0, stock.loosePills);
const stockDisplayTotal =
selectedMed.packageType === "bottle" ||
selectedMed.packageType === "tube" ||
selectedMed.packageType === "liquid_container"
? (selectedMed.totalPills ?? packageSize)
: Math.max(0, structuralMax);
const stockDisplayTotal = isAmountBasedPackageType(selectedMed.packageType)
? (selectedMed.totalPills ?? packageSize)
: Math.max(0, structuralMax);
const packageCount = Math.max(1, Number(selectedMed.packCount) || 1);
const amountPerPackage = (() => {
const configured = Number(selectedMed.packageAmountValue ?? 0);
@@ -244,7 +246,7 @@ export function MedDetailModal({
const decrementLabel = t("editStock.decreaseValue");
const incrementLabel = t("editStock.increaseValue");
const getScheduleUsageLabel = (usage: number, intakeUnit?: "ml" | "tsp" | "tbsp" | null) => {
if (selectedMed.packageType === "liquid_container") {
if (isLiquidContainerPackageType(selectedMed.packageType)) {
if (intakeUnit === "tsp") {
return `${usage} ${t("form.blisters.teaspoons", { count: Math.abs(usage) })}`;
}
@@ -253,7 +255,7 @@ export function MedDetailModal({
}
return `${usage} ${t("form.packageAmountUnitMl")}`;
}
if (selectedMed.packageType === "tube") {
if (isTubePackageType(selectedMed.packageType)) {
return `${usage} ${t("form.blisters.applications", { count: Math.abs(usage) })}`;
}
return `${usage} ${usage !== 1 ? t("common.pills") : t("common.pill")}`;
@@ -400,7 +402,7 @@ export function MedDetailModal({
const renderEditStockModal = () => {
if (!showEditStockModal) return null;
const isLiquidPackage = selectedMed.packageType === "liquid_container";
const isLiquidPackage = isLiquidContainerPackageType(selectedMed.packageType);
const liquidBottleCount = Math.max(1, editStockFullBlisters);
const liquidAmountPerBottle = Math.max(1, Number.isFinite(amountPerPackage) ? amountPerPackage : 1);
const liquidCapacity = Math.max(1, Math.round(liquidBottleCount * liquidAmountPerBottle));
@@ -439,7 +441,7 @@ export function MedDetailModal({
<h2>{t("editStock.title")}</h2>
<p className="edit-stock-med-name">{getMedDisplayName(selectedMed)}</p>
<p className="edit-stock-hint">{t("editStock.hint")}</p>
{selectedMed.packageType === "blister" && (
{!isAmountBasedPackageType(selectedMed.packageType) && (
<p className="edit-stock-cap-info edit-stock-live-breakdown">
{t("editStock.currentComposition", {
fullBlisters: currentFullBlisters,
@@ -449,10 +451,10 @@ export function MedDetailModal({
})}
</p>
)}
{selectedMed.packageType === "bottle" && (
{isAmountBasedPackageType(selectedMed.packageType) && !isTubePackageType(selectedMed.packageType) && (
<p className="edit-stock-cap-info">{t("editStock.packageSize", { count: structuralMax })}</p>
)}
{(selectedMed.packageType === "tube" || selectedMed.packageType === "liquid_container") && (
{(isTubePackageType(selectedMed.packageType) || isLiquidContainerPackageType(selectedMed.packageType)) && (
<p className="edit-stock-cap-info">
{t("form.totalAmount")}: {formatNumber(isLiquidPackage ? liquidCapacity : structuralMax)}{" "}
{amountUnitLabel}
@@ -465,10 +467,7 @@ export function MedDetailModal({
{(() => {
const dbTotal = getMedTotal(selectedMed);
const currentTotal = medCoverage ? Math.round(medCoverage.medsLeft) : dbTotal;
const isBottle =
selectedMed.packageType === "bottle" ||
selectedMed.packageType === "tube" ||
selectedMed.packageType === "liquid_container";
const isBottle = isAmountBasedPackageType(selectedMed.packageType);
const enteredTotal = isLiquidPackage
? Math.min(liquidCapacity, editStockPartialBlisterPills)
: isBottle
@@ -813,7 +812,7 @@ export function MedDetailModal({
<div className="med-detail-section">
<h3>{t("modal.stockInfo")}</h3>
<div className="med-detail-grid">
{selectedMed.packageType === "blister" && (
{!isAmountBasedPackageType(selectedMed.packageType) && (
<>
<div className="med-detail-item">
<span className="med-detail-label">{t("table.fullBlisters")}</span>
@@ -832,7 +831,7 @@ export function MedDetailModal({
</div>
</>
)}
<div className={`med-detail-item ${selectedMed.packageType === "bottle" ? "full-width" : "full-width"}`}>
<div className="med-detail-item full-width">
<span className="med-detail-label">
{isAmountPackage ? t("form.currentAmount") : t("modal.currentStock")}
</span>
@@ -858,27 +857,27 @@ export function MedDetailModal({
<div className="med-detail-section">
<h3>
{t("modal.packageDetails")} (
{selectedMed.packageType === "bottle"
? t("form.packageTypeBottle")
: selectedMed.packageType === "tube"
? t("form.packageTypeTube")
: selectedMed.packageType === "liquid_container"
? t("form.packageTypeLiquidContainer")
{isTubePackageType(selectedMed.packageType)
? t("form.packageTypeTube")
: isLiquidContainerPackageType(selectedMed.packageType)
? t("form.packageTypeLiquidContainer")
: isAmountBasedPackageType(selectedMed.packageType)
? t("form.packageTypeBottle")
: t("form.packageTypeBlister")}
)
{selectedMed.packageType === "tube" && (
{isTubePackageType(selectedMed.packageType) && (
<span className="info-tooltip small" data-tooltip={t("modal.packageTypeTubeHint")}>
</span>
)}
{selectedMed.packageType === "liquid_container" && (
{isLiquidContainerPackageType(selectedMed.packageType) && (
<span className="info-tooltip small" data-tooltip={t("modal.packageTypeLiquidHint")}>
</span>
)}
</h3>
<div className="med-detail-grid">
{selectedMed.packageType === "blister" ? (
{!isAmountBasedPackageType(selectedMed.packageType) ? (
<>
<div className="med-detail-item">
<span className="med-detail-label">{t("modal.packs")}</span>
@@ -893,7 +892,7 @@ export function MedDetailModal({
<span className="med-detail-value">{selectedMed.pillsPerBlister}</span>
</div>
</>
) : selectedMed.packageType === "liquid_container" ? (
) : isLiquidContainerPackageType(selectedMed.packageType) ? (
<>
<div className="med-detail-item">
<span className="med-detail-label">{t("form.bottles")}</span>
@@ -912,7 +911,7 @@ export function MedDetailModal({
</span>
</div>
</>
) : selectedMed.packageType === "tube" ? (
) : isTubePackageType(selectedMed.packageType) ? (
<>
<div className="med-detail-item">
<span className="med-detail-label">{t("form.tubes")}</span>
@@ -1115,13 +1114,10 @@ export function MedDetailModal({
</span>
<span className="refill-amount">
{(() => {
const total =
selectedMed.packageType === "bottle" ||
selectedMed.packageType === "tube" ||
selectedMed.packageType === "liquid_container"
? entry.loosePillsAdded
: entry.packsAdded * selectedMed.blistersPerPack * selectedMed.pillsPerBlister +
entry.loosePillsAdded;
const total = isAmountBasedPackageType(selectedMed.packageType)
? entry.loosePillsAdded
: entry.packsAdded * selectedMed.blistersPerPack * selectedMed.pillsPerBlister +
entry.loosePillsAdded;
return `+${total}${isAmountPackage ? ` ${stockUnitLabel}` : ` ${total === 1 ? t("common.pill") : t("common.pills")}`}`;
})()}
{entry.usedPrescription && (
@@ -1221,7 +1217,7 @@ export function MedDetailModal({
<p className="refill-med-name">{getMedDisplayName(selectedMed)}</p>
<div className="refill-form">
{selectedMed.packageType === "blister" ? (
{!isAmountBasedPackageType(selectedMed.packageType) ? (
<>
<label>
{t("refill.packs")}
@@ -1265,7 +1261,7 @@ export function MedDetailModal({
onUsePrescriptionRefillChange(checked);
if (
checked &&
selectedMed.packageType === "blister" &&
!isAmountBasedPackageType(selectedMed.packageType) &&
refillPacks > remainingPrescriptionRefills
) {
onRefillPacksChange(remainingPrescriptionRefills);
@@ -1291,9 +1287,7 @@ export function MedDetailModal({
className="success"
onClick={() => onSubmitRefill(selectedMed.id, usePrescriptionRefill)}
disabled={
(selectedMed.packageType === "bottle" ||
selectedMed.packageType === "tube" ||
selectedMed.packageType === "liquid_container"
(isAmountBasedPackageType(selectedMed.packageType)
? refillLoose < 1
: cappedRefillPacks < 1 && refillLoose < 1) ||
exceedsPrescriptionPackLimit ||
@@ -1303,10 +1297,9 @@ export function MedDetailModal({
{refillSaving ? t("common.saving") : t("refill.button")}
</button>
{(() => {
const totalRefill =
selectedMed.packageType === "blister"
? cappedRefillPacks * selectedMed.blistersPerPack * selectedMed.pillsPerBlister + refillLoose
: refillLoose;
const totalRefill = !isAmountBasedPackageType(selectedMed.packageType)
? cappedRefillPacks * selectedMed.blistersPerPack * selectedMed.pillsPerBlister + refillLoose
: refillLoose;
return totalRefill > 0 ? (
<span className="refill-preview">
+{totalRefill}
+28 -20
View File
@@ -10,7 +10,14 @@ import { useTranslation } from "react-i18next";
import { useEscapeKey } from "../hooks/useEscapeKey";
import { useScrollLock } from "../hooks/useScrollLock";
import type { DoseUnit, FieldErrors, FormBlister, FormIntake, FormState, Medication } from "../types";
import { DOSE_UNITS } from "../types";
import {
allowsPillFormSelection,
DOSE_UNITS,
isAmountBasedPackageType,
isLiquidContainerPackageType,
isTubePackageType,
PACKAGE_PROFILES,
} from "../types";
import { deriveTotal } from "../utils";
import { DateInput } from "./DateInput";
import { FormNumberStepper } from "./FormNumberStepper";
@@ -68,7 +75,7 @@ export interface MobileEditModalProps {
/** Calculate total pills from form state */
function deriveTotalFromForm(form: FormState) {
if (form.packageType === "bottle" || form.packageType === "tube" || form.packageType === "liquid_container") {
if (isAmountBasedPackageType(form.packageType)) {
// For bottle type, looseTablets is the current stock
return Number(form.looseTablets) || 0;
}
@@ -126,19 +133,19 @@ export function MobileEditModal({
const activeTabIndexRef = useRef(0);
const allowFractionalIntake = useMemo(() => {
if (form.packageType === "liquid_container") return true;
if (form.packageType === "tube") return form.medicationForm === "liquid";
if (isLiquidContainerPackageType(form.packageType)) return true;
if (isTubePackageType(form.packageType)) return form.medicationForm === "liquid";
return form.pillForm === "tablet";
}, [form.packageType, form.medicationForm, form.pillForm]);
const getUsageLabel = useCallback(
(intake: (typeof form.intakes)[number]) => {
if (form.packageType === "liquid_container") {
if (isLiquidContainerPackageType(form.packageType)) {
if (intake.intakeUnit === "tsp") return t("form.blisters.usageTsp");
if (intake.intakeUnit === "tbsp") return t("form.blisters.usageTbsp");
return t("form.blisters.usageMl");
}
if (form.packageType === "tube") {
if (isTubePackageType(form.packageType)) {
return form.medicationForm === "liquid" ? t("form.blisters.usageMl") : t("form.blisters.usageApplication");
}
if (form.pillForm === "capsule") return t("form.blisters.usageCapsules");
@@ -147,7 +154,7 @@ export function MobileEditModal({
[form.packageType, form.medicationForm, form.pillForm, t]
);
const usesAmountLabels = form.packageType === "tube" || form.packageType === "liquid_container";
const usesAmountLabels = isTubePackageType(form.packageType) || isLiquidContainerPackageType(form.packageType);
const totalCapacityLabel = usesAmountLabels ? t("form.totalAmount") : t("form.totalCapacity");
const currentStockLabel = usesAmountLabels ? t("form.currentAmount") : t("form.currentPills");
const totalLabel = usesAmountLabels ? t("form.totalAmountLabel") : t("form.total");
@@ -432,10 +439,11 @@ export function MobileEditModal({
value={form.packageType}
onChange={(e) => onHandleValueChange("packageType", e.target.value as FormState["packageType"])}
>
<option value="blister">{t("form.packageTypeBlister")}</option>
<option value="bottle">{t("form.packageTypeBottle")}</option>
<option value="tube">{t("form.packageTypeTube")}</option>
<option value="liquid_container">{t("form.packageTypeLiquidContainer")}</option>
{PACKAGE_PROFILES.map((profile) => (
<option key={profile.value} value={profile.value}>
{t(profile.labelKey)}
</option>
))}
</select>
</label>
<label className="full">
@@ -446,7 +454,7 @@ export function MobileEditModal({
placeholder={t("common.optional")}
/>
</label>
{form.packageType !== "tube" && form.packageType !== "liquid_container" && (
{allowsPillFormSelection(form.packageType) && (
<label className="full">
{t("form.pillForm")}
<select
@@ -458,7 +466,7 @@ export function MobileEditModal({
</select>
</label>
)}
{form.packageType === "tube" && (
{isTubePackageType(form.packageType) && (
<label className="full">
{t("form.medicationForm")}
<select value={"topical"} onChange={() => onHandleValueChange("medicationForm", "topical")}>
@@ -466,7 +474,7 @@ export function MobileEditModal({
</select>
</label>
)}
{form.packageType === "liquid_container" && (
{isLiquidContainerPackageType(form.packageType) && (
<label className="full">
{t("form.medicationForm")}
<select value={"liquid"} onChange={() => onHandleValueChange("medicationForm", "liquid")}>
@@ -560,7 +568,7 @@ export function MobileEditModal({
<div className="full form-category">
<h4 className="form-category-title">{t("form.sections.stock")}</h4>
{(() => {
if (form.packageType === "blister") {
if (!isAmountBasedPackageType(form.packageType)) {
return (
<>
<label>
@@ -601,7 +609,7 @@ export function MobileEditModal({
);
}
if (form.packageType === "tube") {
if (isTubePackageType(form.packageType)) {
return (
<>
<label>
@@ -640,7 +648,7 @@ export function MobileEditModal({
);
}
if (form.packageType === "liquid_container") {
if (isLiquidContainerPackageType(form.packageType)) {
return (
<>
<label>
@@ -710,7 +718,7 @@ export function MobileEditModal({
</>
);
})()}
{form.packageType === "bottle" && (
{isAmountBasedPackageType(form.packageType) && !isTubePackageType(form.packageType) && (
<div className="full stock-total-row">
<div className="stock-total-field">
<p className="sub">
@@ -720,7 +728,7 @@ export function MobileEditModal({
</div>
</div>
)}
{form.packageType !== "tube" && form.packageType !== "liquid_container" && (
{allowsPillFormSelection(form.packageType) && (
<label className="full">
{t("form.pillWeight")} ({form.doseUnit})
<div className="dose-input-group">
@@ -837,7 +845,7 @@ export function MobileEditModal({
onChange={(e) => onSetIntakeValue(idx, "startTime", e.target.value)}
/>
</label>
{form.packageType === "liquid_container" && (
{isLiquidContainerPackageType(form.packageType) && (
<label className="compact full-row">
<span>{t("form.blisters.intakeUnit")}</span>
<select
+20 -14
View File
@@ -3,7 +3,13 @@ import { useTranslation } from "react-i18next";
import { useEscapeKey } from "../hooks/useEscapeKey";
import { useScrollLock } from "../hooks/useScrollLock";
import type { Medication } from "../types";
import { getMedDisplayName, getPackageSize } from "../types";
import {
getMedDisplayName,
getPackageSize,
isAmountBasedPackageType,
isLiquidContainerPackageType,
isTubePackageType,
} from "../types";
import { MedicationAvatar } from "./MedicationAvatar";
type ReportFormat = "txt" | "md" | "pdf";
@@ -299,35 +305,35 @@ function fmtDateTime(iso: string | null | undefined): string {
}
function getTubeUnitKey(med: Medication): "form.ml" | "blisters.applications" {
if (med.packageType === "liquid_container") return "form.ml";
if (isLiquidContainerPackageType(med.packageType)) return "form.ml";
return med.medicationForm === "liquid" ? "form.ml" : "blisters.applications";
}
function getUsageText(med: Medication, usage: number, t: TFn): string {
if (med.packageType === "tube" || med.packageType === "liquid_container") {
if (isTubePackageType(med.packageType) || isLiquidContainerPackageType(med.packageType)) {
return `${usage} ${t(getTubeUnitKey(med))}`;
}
return `${usage} ${usage === 1 ? t("common.pill") : t("common.pills")}`;
}
function getTotalCapacityLabel(med: Medication, t: TFn): string {
if (med.packageType === "tube" || med.packageType === "liquid_container") {
if (isTubePackageType(med.packageType) || isLiquidContainerPackageType(med.packageType)) {
return t("form.totalAmountLabel", { unit: t(getTubeUnitKey(med)) });
}
return t("report.docTotalCapacity");
}
function getCurrentStockText(med: Medication, t: TFn): string {
if (med.packageType === "tube" || med.packageType === "liquid_container") {
if (isTubePackageType(med.packageType) || isLiquidContainerPackageType(med.packageType)) {
return `${getPackageSize(med)} ${t(getTubeUnitKey(med))}`;
}
return `${getPackageSize(med)} ${t("common.pills")}`;
}
function getReportPackageTypeLabel(med: Medication, t: TFn): string {
if (med.packageType === "bottle") return t("report.docBottle");
if (med.packageType === "tube") return t("report.docTube");
if (med.packageType === "liquid_container") return t("form.packageTypeLiquidContainer");
if (isTubePackageType(med.packageType)) return t("report.docTube");
if (isLiquidContainerPackageType(med.packageType)) return t("form.packageTypeLiquidContainer");
if (isAmountBasedPackageType(med.packageType)) return t("report.docBottle");
return t("report.docBlister");
}
@@ -374,7 +380,7 @@ function generateTextReport(
// Package / Stock
lines.push(h3(t("report.docPackage")));
lines.push(item(t("report.docPackageType"), getReportPackageTypeLabel(med, t)));
if (med.packageType === "blister") {
if (!isAmountBasedPackageType(med.packageType)) {
lines.push(item(t("report.docPacks"), String(med.packCount)));
lines.push(item(t("report.docBlistersPerPack"), String(med.blistersPerPack)));
lines.push(item(t("report.docPillsPerBlister"), String(med.pillsPerBlister)));
@@ -383,7 +389,7 @@ function generateTextReport(
lines.push(item(getTotalCapacityLabel(med, t), String(med.totalPills ?? med.looseTablets)));
}
lines.push(item(t("report.docCurrentStock"), getCurrentStockText(med, t)));
if (med.packageType !== "tube" && med.packageType !== "liquid_container" && med.pillWeightMg)
if (!isTubePackageType(med.packageType) && !isLiquidContainerPackageType(med.packageType) && med.pillWeightMg)
lines.push(item(t("report.docDosePerPill"), `${med.pillWeightMg} ${med.doseUnit ?? "mg"}`));
if (med.expiryDate) lines.push(item(t("report.docExpiryDate"), fmtDate(med.expiryDate)));
if (med.notes) lines.push(item(t("report.docNotes"), med.notes));
@@ -439,7 +445,7 @@ function generateTextReport(
if (data.refills.length > 0) {
lines.push(h3(t("report.docRefillHistory")));
for (const r of data.refills) {
let entry = `${fmtDate(r.refillDate)}: +${r.packsAdded} ${t("report.docPacks")}, +${r.loosePillsAdded} ${med.packageType === "tube" || med.packageType === "liquid_container" ? t(getTubeUnitKey(med)) : t("common.pills")}`;
let entry = `${fmtDate(r.refillDate)}: +${r.packsAdded} ${t("report.docPacks")}, +${r.loosePillsAdded} ${isTubePackageType(med.packageType) || isLiquidContainerPackageType(med.packageType) ? t(getTubeUnitKey(med)) : t("common.pills")}`;
if (r.usedPrescription) entry += ` ${t("report.docRefillPrescription")}`;
lines.push(fmt === "md" ? `- ${entry}` : `${entry}`);
}
@@ -572,7 +578,7 @@ function buildPrintHtml(
s += `<h3>${escHtml(t("report.docPackage"))}</h3>`;
s += `<table><tbody>`;
s += `<tr><td class="label">${escHtml(t("report.docPackageType"))}</td><td>${escHtml(getReportPackageTypeLabel(med, t))}</td></tr>`;
if (med.packageType === "blister") {
if (!isAmountBasedPackageType(med.packageType)) {
s += `<tr><td class="label">${escHtml(t("report.docPacks"))}</td><td>${med.packCount}</td></tr>`;
s += `<tr><td class="label">${escHtml(t("report.docBlistersPerPack"))}</td><td>${med.blistersPerPack}</td></tr>`;
s += `<tr><td class="label">${escHtml(t("report.docPillsPerBlister"))}</td><td>${med.pillsPerBlister}</td></tr>`;
@@ -582,7 +588,7 @@ function buildPrintHtml(
s += `<tr><td class="label">${escHtml(getTotalCapacityLabel(med, t))}</td><td>${med.totalPills ?? med.looseTablets}</td></tr>`;
}
s += `<tr><td class="label">${escHtml(t("report.docCurrentStock"))}</td><td>${escHtml(getCurrentStockText(med, t))}</td></tr>`;
if (med.packageType !== "tube" && med.packageType !== "liquid_container" && med.pillWeightMg)
if (!isTubePackageType(med.packageType) && !isLiquidContainerPackageType(med.packageType) && med.pillWeightMg)
s += `<tr><td class="label">${escHtml(t("report.docDosePerPill"))}</td><td>${med.pillWeightMg} ${escHtml(med.doseUnit ?? "mg")}</td></tr>`;
if (med.expiryDate)
s += `<tr><td class="label">${escHtml(t("report.docExpiryDate"))}</td><td>${fmtDate(med.expiryDate)}</td></tr>`;
@@ -646,7 +652,7 @@ function buildPrintHtml(
s += `<h3>${escHtml(t("report.docRefillHistory"))}</h3>`;
s += `<ul>`;
for (const r of data.refills) {
let entry = `${fmtDate(r.refillDate)}: +${r.packsAdded} ${escHtml(t("report.docPacks"))}, +${r.loosePillsAdded} ${escHtml(med.packageType === "tube" || med.packageType === "liquid_container" ? t(getTubeUnitKey(med)) : t("common.pills"))}`;
let entry = `${fmtDate(r.refillDate)}: +${r.packsAdded} ${escHtml(t("report.docPacks"))}, +${r.loosePillsAdded} ${escHtml(isTubePackageType(med.packageType) || isLiquidContainerPackageType(med.packageType) ? t(getTubeUnitKey(med)) : t("common.pills"))}`;
if (r.usedPrescription) entry += ` <em>${escHtml(t("report.docRefillPrescription"))}</em>`;
s += `<li>${entry}</li>`;
}
+6 -6
View File
@@ -9,7 +9,7 @@ import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom";
import { useEscapeKey } from "../hooks";
import type { ExpiredLinkData, SharedScheduleData } from "../types";
import { getMedDisplayName, getMedTotal } from "../types";
import { getMedDisplayName, getMedTotal, isLiquidContainerPackageType, isTubePackageType } from "../types";
import { getSystemLocale } from "../utils/formatters";
import { isDoseDismissed } from "../utils/schedule";
import { loadCollapsedDaysFromStorage } from "../utils/storage";
@@ -24,10 +24,10 @@ function getStockStatus(
thresholds: { lowStockDays: number; normalStockDays: number; highStockDays: number; criticalStockDays: number },
packageType?: string
) {
if (packageType === "tube") return { className: "success", label: "status.noSchedule" };
if (isTubePackageType(packageType)) return { className: "success", label: "status.noSchedule" };
if (medsLeft <= 0 || daysLeft === 0) return { className: "danger", label: "status.outOfStock" };
if (daysLeft === null) return { className: "success", label: "status.noSchedule" };
if (packageType === "liquid_container") {
if (isLiquidContainerPackageType(packageType)) {
const lowDays = Math.max(1, Math.floor(thresholds.criticalStockDays));
const criticalDays = Math.max(1, Math.ceil(lowDays / 2));
if (daysLeft <= criticalDays) return { className: "danger", label: "status.criticalStock" };
@@ -54,7 +54,7 @@ export function SharedSchedule() {
const [showFutureDays, setShowFutureDays] = useState(false);
const isLiquidContainerMed = (med: SharedScheduleData["medications"][number] | undefined) =>
med?.packageType === "liquid_container";
isLiquidContainerPackageType(med?.packageType);
const convertLiquidUsageToMl = (usage: number, unit: "ml" | "tsp" | "tbsp" | null | undefined): number => {
if (unit === "tsp") return usage * 5;
@@ -67,7 +67,7 @@ export function SharedSchedule() {
med: SharedScheduleData["medications"][number] | undefined,
unit: "ml" | "tsp" | "tbsp" | null | undefined
): number => {
if (med?.packageType === "tube") return 0;
if (isTubePackageType(med?.packageType)) return 0;
if (!isLiquidContainerMed(med)) return usage;
return convertLiquidUsageToMl(usage, unit);
};
@@ -140,7 +140,7 @@ export function SharedSchedule() {
const shouldHideNoScheduleStatusForTube = (
med: SharedScheduleData["medications"][number] | undefined,
status: { className: string; label: string } | null
) => med?.packageType === "tube" && status?.label === "status.noSchedule";
) => isTubePackageType(med?.packageType) && status?.label === "status.noSchedule";
const getVisibleStockStatus = (
med: SharedScheduleData["medications"][number] | undefined,