Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions src/app/stock/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ export default function StockPage() {
const [toppingUp, setToppingUp] = useState<string | null>(null);
/** Which vial is being emptied into a nasal spray bottle. */
const [transferring, setTransferring] = useState<string | null>(null);
/**
* Which row is having its tablet size set.
*
* Needed because a pack can exist before anyone said how big one tablet is:
* added before the compound was marked as tablets, or imported, or added
* while the compound was still described as a powder. Without a way to say it
* afterwards the pack is stuck, countable by nothing, and the form for
* logging a dose can only say that the size is missing.
*/
const [sizing, setSizing] = useState<string | null>(null);

const now = Date.now();
/*
Expand Down Expand Up @@ -357,7 +367,33 @@ export default function StockPage() {
solution it can never be.
*/
onReconstitute={isPack(v) ? undefined : () => setReconstituting(v.id)}
tabletCompound={findPeptide(custom, v.peptideId)?.preparation === "tablet"}
onSetTabletSize={() => setSizing(v.id)}
/>
{sizing === v.id && (
<TabletSizeForm
vial={v}
onCancel={() => setSizing(null)}
onSave={(mgEach, tabletsInPack) => {
/*
Every vial in the group, unlike the buttons beside it,
which act on the oldest one alone. Those change one
vial's state and the group is meant to split. This
changes what the row has always been, so writing it to
one member would break the group into a pack with a
size and a shelf of identical packs without one.
*/
for (const target of group ? group.vials : [v]) {
updateVial(target.id, {
container: "pack",
mgPerTablet: mgEach,
strengthMg: packStrengthMg(mgEach, tabletsInPack),
});
}
setSizing(null);
}}
/>
)}
{reconstituting === v.id && (
<ReconstituteForm
vial={v}
Expand Down Expand Up @@ -430,6 +466,8 @@ function VialRow({
onTopUp,
onTransfer,
onFinish,
tabletCompound,
onSetTabletSize,
}: {
vial: Vial;
/**
Expand Down Expand Up @@ -461,6 +499,9 @@ function VialRow({
/** Only for a made-up vial, and never for a bottle that is already a spray. */
onTransfer?: () => void;
onFinish?: () => void;
/** Whether the library says this compound comes as tablets. */
tabletCompound?: boolean;
onSetTabletSize?: () => void;
}) {
const { t } = useLang();
const st = vialStatus(vial, now);
Expand Down Expand Up @@ -703,6 +744,18 @@ function VialRow({
<SprayCan size={13} /> {t("stock_to_spray")}
</Button>
)}
{/*
Offered for a pack, and also for an ordinary row of a compound the
library calls tablets, which is how a row added before anyone said
so becomes a pack at all. Without the second case the size could
only ever be set at the moment of adding, and a pack added the day
before the compound was marked as tablets was stuck for good.
*/}
{onSetTabletSize && (pack || tabletCompound) && (
<Button onClick={onSetTabletSize} className="px-3 py-1.5 text-[13px]">
{t("stock_set_tablet_size")}
</Button>
)}
{onFinish && (
<Button onClick={onFinish} variant="ghost" className="px-3 py-1.5 text-[13px]">
{t("stock_mark_empty")}
Expand Down Expand Up @@ -1342,6 +1395,91 @@ function TransferToSprayForm({
);
}

/**
* Saying how big one tablet is, on a row that already exists.
*
* The add form asks for this, but only when the compound was already marked as
* tablets. Everything else arrives without it: a pack added before the
* compound was described that way, an import, a row somebody entered as an
* ordinary vial. All of those could be seen and none of them could be counted,
* and the form for logging a dose could only say the size was missing.
*
* Both numbers, not just the size, because `strengthMg` on a pack is the mass
* of the whole pack and is the product of the two. Asking for the size alone
* would leave the mass saying whatever it said before.
*/
function TabletSizeForm({
vial,
onCancel,
onSave,
}: {
vial: Vial;
onSave: (mgEach: number, tabletsInPack: number) => void;
onCancel: () => void;
}) {
const { t } = useLang();
const known = mgPerTablet(vial);
const [mgEach, setMgEach] = useState(known);
/*
* The pack as bought, not what is left in it. A count of what is left would
* quietly write off every tablet already taken, since the mass taken is
* recorded separately and would then be subtracted a second time.
*/
const [tablets, setTablets] = useState(
known > 0 ? Math.round(vial.strengthMg / known) : 0);

const packMg = packStrengthMg(mgEach, tablets);
const ok = mgEach > 0 && tablets > 0;
// What the row will say once this is saved, including any dose already taken.
const left = ok
? tabletsRemaining({ ...vial, strengthMg: packMg, mgPerTablet: mgEach })
: 0;

return (
<Card className="mt-1.5 space-y-4 border-[var(--tangerine)]/35 p-4">
<SectionLabel>{t("stock_set_tablet_size")}</SectionLabel>

<div className="grid gap-4 sm:grid-cols-2">
<Field label={t("stock_mg_per_tablet")}>
<NumberInput
value={mgEach}
min={0}
step={0.5}
suffix="mg"
onChange={(e) => setMgEach(Number(e.target.value))}
/>
</Field>
<Field label={t("stock_tablets_per_pack")}>
<NumberInput
value={tablets}
min={0}
step={1}
onChange={(e) => setTablets(Number(e.target.value))}
/>
</Field>
</div>

{ok && (
<p className="text-[12.5px] leading-relaxed text-[var(--faint)]">
{t("stock_pack_after", {
mg: trim(packMg, 3),
left: t("count_tablets", { n: left }),
})}
</p>
)}

<div className="flex gap-2.5">
<Button variant="ghost" onClick={onCancel}>
{t("cancel")}
</Button>
<Button variant="primary" onClick={() => onSave(mgEach, tablets)} disabled={!ok}>
{t("save")}
</Button>
</div>
</Card>
);
}

function TopUpForm({
vial,
bottles,
Expand Down
14 changes: 11 additions & 3 deletions src/components/LogDoseSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -567,9 +567,17 @@ export function LogDoseSheet({
<Field
label={t("log_tablets")}
hint={
vial && mcgPerTablet(vial) > 0
? t("log_per_tablet_hint", { dose: formatDose(mcgPerTablet(vial)) })
: t("log_no_tablet_size")
/*
Three states, three sentences. The first version said "add
the tablet size to the pack" whether or not there was a pack
to add it to, which reads as an instruction with nowhere to
carry it out.
*/
!vial
? t("log_no_pack_in_stock")
: mcgPerTablet(vial) > 0
? t("log_per_tablet_hint", { dose: formatDose(mcgPerTablet(vial)) })
: t("log_no_tablet_size")
}
>
<NumberInput
Expand Down
20 changes: 16 additions & 4 deletions src/lib/i18n/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,12 +345,15 @@ export const TRANSLATIONS = {
stock_tablets_left: "{n} tablets left",
stock_per_tablet: "{mg} mg a tablet",
stock_no_tablet_size: "No tablet size recorded, so nothing can be counted.",
stock_set_tablet_size: "Set tablet size",
stock_pack_after: "A pack of {mg} mg, {left} in it now.",
ccf_tablet: "Tablets",
ccf_tablet_hint: "Comes as tablets or capsules, counted rather than drawn. Nothing to make up, and no syringe when you log it.",
log_tablets: "Tablets",
log_tablets_suffix: "tablets",
log_per_tablet_hint: "{dose} a tablet.",
log_no_tablet_size: "Add the tablet size to the pack first.",
log_no_tablet_size: "Set the tablet size on this pack in Stock.",
log_no_pack_in_stock: "No pack of this in stock, so there is nothing to count out of.",
log_per_press_hint: "{dose} a press, {ml} mL in total.",
log_na: "n/a",
count_tablets_one: "{n} tablet",
Expand Down Expand Up @@ -1756,12 +1759,15 @@ export const TRANSLATIONS = {
stock_tablets_left: "{n} Tabletten übrig",
stock_per_tablet: "{mg} mg pro Tablette",
stock_no_tablet_size: "Keine Tablettengröße hinterlegt, also lässt sich nichts zählen.",
stock_set_tablet_size: "Tablettengröße eintragen",
stock_pack_after: "Eine Packung mit {mg} mg, darin jetzt {left}.",
ccf_tablet: "Tabletten",
ccf_tablet_hint: "Kommt als Tabletten oder Kapseln, gezählt statt aufgezogen. Nichts anzusetzen, und beim Eintragen keine Spritze.",
log_tablets: "Tabletten",
log_tablets_suffix: "Tabletten",
log_per_tablet_hint: "{dose} pro Tablette.",
log_no_tablet_size: "Trag zuerst die Tablettengröße der Packung ein.",
log_no_tablet_size: "Trag die Tablettengröße dieser Packung unter Vorrat ein.",
log_no_pack_in_stock: "Keine Packung davon im Vorrat, also gibt es nichts abzuzählen.",
log_per_press_hint: "{dose} pro Sprühstoß, {ml} mL insgesamt.",
log_na: "k.A.",
count_tablets_one: "{n} Tablette",
Expand Down Expand Up @@ -3182,12 +3188,15 @@ export const TRANSLATIONS = {
stock_tablets_left: "ostalo {n} tablet",
stock_per_tablet: "{mg} mg na tableto",
stock_no_tablet_size: "Velikost tablete ni vpisana, zato ni česa šteti.",
stock_set_tablet_size: "Vpiši velikost tablete",
stock_pack_after: "Škatlica s {mg} mg, v njej je zdaj {left}.",
ccf_tablet: "Tablete",
ccf_tablet_hint: "Pride v tabletah ali kapsulah, ki se štejejo in ne vlečejo. Ničesar ni treba pripraviti, ob beleženju pa ni brizge.",
log_tablets: "Tablete",
log_tablets_suffix: "tablet",
log_per_tablet_hint: "{dose} na tableto.",
log_no_tablet_size: "Najprej vpiši velikost tablete pri škatlici.",
log_no_tablet_size: "Velikost tablete za to škatlico vpiši v razdelku Zaloga.",
log_no_pack_in_stock: "Te škatlice ni v zalogi, zato ni iz česa šteti.",
log_per_press_hint: "{dose} na pritisk, skupaj {ml} mL.",
log_na: "ni podatka",
count_tablets_one: "{n} tableta",
Expand Down Expand Up @@ -4678,12 +4687,15 @@ export const TRANSLATIONS = {
stock_tablets_left: "zostało {n} tabletek",
stock_per_tablet: "{mg} mg na tabletkę",
stock_no_tablet_size: "Nie podano wielkości tabletki, więc nie ma czego liczyć.",
stock_set_tablet_size: "Podaj wielkość tabletki",
stock_pack_after: "Opakowanie {mg} mg, jest w nim teraz {left}.",
ccf_tablet: "Tabletki",
ccf_tablet_hint: "Występuje w tabletkach albo kapsułkach, liczonych zamiast nabieranych. Nic nie trzeba rozpuszczać, a przy zapisie nie ma strzykawki.",
log_tablets: "Tabletki",
log_tablets_suffix: "tabletek",
log_per_tablet_hint: "{dose} na tabletkę.",
log_no_tablet_size: "Najpierw podaj wielkość tabletki przy opakowaniu.",
log_no_tablet_size: "Wielkość tabletki dla tego opakowania podaj w sekcji Zapas.",
log_no_pack_in_stock: "Nie ma tego opakowania w zapasach, więc nie ma z czego liczyć.",
log_per_press_hint: "{dose} na psiknięcie, łącznie {ml} mL.",
log_na: "brak",
count_tablets_one: "{n} tabletka",
Expand Down
Loading