Add checkout/custody feature for tracking items in personal possession
Build and push image / build (push) Successful in 1m8s
Build and push image / build (push) Successful in 1m8s
Items can now be checked out of their bin into "my custody" and later checked back in or marked consumed. Adds checkout/checkin API endpoints, a My Custody sidebar page, CheckoutFlow and CheckinFlow modals, and updates ProductDetail, Inventory, ConsumeFlow, and MarkGoneFlow to handle the new checked-out status. Bulk items prompt for remaining weight on check-in. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,8 @@ export function ProductDetail({
|
||||
onMarkGone,
|
||||
onAudit,
|
||||
onEdit,
|
||||
onCheckout,
|
||||
onCheckin,
|
||||
}: {
|
||||
item: Item;
|
||||
data: Bootstrap;
|
||||
@@ -23,6 +25,8 @@ export function ProductDetail({
|
||||
onMarkGone: (i: Item) => void;
|
||||
onAudit: (i: Item) => void;
|
||||
onEdit: (i: Item) => void;
|
||||
onCheckout: (i: Item) => void;
|
||||
onCheckin: (i: Item) => void;
|
||||
}) {
|
||||
const bin = data.bins.find((b) => b.id === item.binId);
|
||||
const cfg = TYPES.find((t) => t.id === item.type);
|
||||
@@ -34,6 +38,7 @@ export function ProductDetail({
|
||||
const sinceCheck = helpers.daysSinceCheck(item, TODAY_STR);
|
||||
|
||||
const isActive = item.status === "active";
|
||||
const isCheckedOut = item.status === "checked-out";
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
@@ -58,7 +63,7 @@ export function ProductDetail({
|
||||
["Shop", helpers.shopName(data, item.shopId)],
|
||||
["Total cannabinoids", `${item.totalCannabinoids.toFixed(1)}%`],
|
||||
["Purchase date", fmt.date(item.purchaseDate)],
|
||||
["Bin", bin ? bin.name : <span style={{ color: "var(--ink-3)" }}>—</span>],
|
||||
["Bin", isCheckedOut ? "In your custody" : bin ? bin.name : <span style={{ color: "var(--ink-3)" }}>—</span>],
|
||||
["Audit cadence", `Every ${cfg?.cadenceDays ?? "—"} days · ${cfg?.auditMode ?? "—"}`],
|
||||
[
|
||||
"Cost per gram",
|
||||
@@ -69,6 +74,9 @@ export function ProductDetail({
|
||||
: "—",
|
||||
],
|
||||
];
|
||||
if (item.status === "checked-out") {
|
||||
detailRows.push(["Checked out", fmt.date(item.checkoutDate)]);
|
||||
}
|
||||
if (item.status === "consumed") {
|
||||
detailRows.push(
|
||||
["Date finished", fmt.date(item.consumedDate)],
|
||||
@@ -130,17 +138,27 @@ export function ProductDetail({
|
||||
Inventory · <span className="mono">{item.assetId}</span>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: 6, flexWrap: "wrap", justifyContent: "flex-end" }}>
|
||||
{isActive && (
|
||||
<Btn variant="ghost" icon="pocket" onClick={() => onCheckout(item)}>
|
||||
Check out
|
||||
</Btn>
|
||||
)}
|
||||
{isActive && (
|
||||
<Btn variant="ghost" icon="check" onClick={() => onAudit(item)}>
|
||||
Audit
|
||||
</Btn>
|
||||
)}
|
||||
{isActive && (
|
||||
{isCheckedOut && (
|
||||
<Btn variant="sage" icon="check" onClick={() => onCheckin(item)}>
|
||||
Check in
|
||||
</Btn>
|
||||
)}
|
||||
{(isActive || isCheckedOut) && (
|
||||
<Btn variant="secondary" icon="check" onClick={() => onConsume(item)}>
|
||||
Mark consumed
|
||||
</Btn>
|
||||
)}
|
||||
{isActive && (
|
||||
{(isActive || isCheckedOut) && (
|
||||
<Btn variant="ghost" icon="bin" onClick={() => onMarkGone(item)} />
|
||||
)}
|
||||
<Btn variant="ghost" icon="edit" onClick={() => onEdit(item)} />
|
||||
@@ -159,6 +177,9 @@ export function ProductDetail({
|
||||
{item.status === "gone" && (
|
||||
<Pill tone="amber">Gone · {fmt.daysAgo(item.goneDate)}</Pill>
|
||||
)}
|
||||
{isCheckedOut && (
|
||||
<Pill tone="outline">Checked out · {fmt.daysAgo(item.checkoutDate)}</Pill>
|
||||
)}
|
||||
{isActive && overdue && <Pill tone="amber">Audit overdue · {sinceCheck}d</Pill>}
|
||||
</div>
|
||||
<h1
|
||||
@@ -214,7 +235,7 @@ export function ProductDetail({
|
||||
))}
|
||||
</div>
|
||||
|
||||
{isActive && (
|
||||
{(isActive || isCheckedOut) && (
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<div
|
||||
style={{
|
||||
|
||||
Reference in New Issue
Block a user