96 lines
3.6 KiB
TypeScript
96 lines
3.6 KiB
TypeScript
|
|
'use client';
|
|||
|
|
|
|||
|
|
import { useState } from 'react';
|
|||
|
|
import { Check } from 'lucide-react';
|
|||
|
|
import BottomSheet from '@/components/BottomSheet';
|
|||
|
|
import { getItemDisplayQty } from '@/lib/pocketbase';
|
|||
|
|
import type { OrderItem } from '@/lib/types';
|
|||
|
|
import { cn } from '@/lib/utils';
|
|||
|
|
|
|||
|
|
interface CollectPromptProps {
|
|||
|
|
poLabel: string;
|
|||
|
|
/** The PO's items still on the driver's list. */
|
|||
|
|
items: OrderItem[];
|
|||
|
|
onCollect: (items: OrderItem[]) => void;
|
|||
|
|
onClose: () => void;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Shown right after an invoice photo uploads. The photo alone collects
|
|||
|
|
// nothing, and the green invoice button made it look done, so drivers left
|
|||
|
|
// lines "assigned" and off Collection Confirm. Works with or without the AI
|
|||
|
|
// match: it only lists what is still on the list. Every item starts ticked;
|
|||
|
|
// a short or different item is unticked and collected on its own card.
|
|||
|
|
export default function CollectPrompt({ poLabel, items, onCollect, onClose }: CollectPromptProps) {
|
|||
|
|
const [picked, setPicked] = useState<Set<string>>(() => new Set(items.map((i) => i.id)));
|
|||
|
|
|
|||
|
|
const toggle = (id: string) =>
|
|||
|
|
setPicked((prev) => {
|
|||
|
|
const next = new Set(prev);
|
|||
|
|
if (next.has(id)) next.delete(id);
|
|||
|
|
else next.add(id);
|
|||
|
|
return next;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const chosen = items.filter((i) => picked.has(i.id));
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<BottomSheet title="Invoice saved · collect these items?" subtitle={poLabel} onClose={onClose}>
|
|||
|
|
<p className="text-[14px] text-[#26251e] mb-2">
|
|||
|
|
The photo is saved, but the items aren’t collected yet. Tick what you picked up.
|
|||
|
|
</p>
|
|||
|
|
<ul className="divide-y divide-[rgba(38,37,30,0.08)] mb-2">
|
|||
|
|
{items.map((item) => {
|
|||
|
|
const on = picked.has(item.id);
|
|||
|
|
return (
|
|||
|
|
<li key={item.id}>
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
role="checkbox"
|
|||
|
|
aria-checked={on}
|
|||
|
|
onClick={() => toggle(item.id)}
|
|||
|
|
className="w-full py-2.5 flex items-start gap-3 text-left touch-manipulation"
|
|||
|
|
>
|
|||
|
|
<span
|
|||
|
|
className={cn(
|
|||
|
|
'w-6 h-6 rounded-md border flex items-center justify-center flex-shrink-0 mt-0.5',
|
|||
|
|
on ? 'bg-[#1f8a65] border-[#1f8a65]' : 'bg-white border-[rgba(38,37,30,0.3)]',
|
|||
|
|
)}
|
|||
|
|
aria-hidden="true"
|
|||
|
|
>
|
|||
|
|
{on && <Check size={16} className="text-white" />}
|
|||
|
|
</span>
|
|||
|
|
<span className="flex-1 min-w-0 text-[14px] text-[#26251e] leading-snug break-words">
|
|||
|
|
{item.description}
|
|||
|
|
<span className="text-[rgba(38,37,30,0.55)]"> × {getItemDisplayQty(item)}</span>
|
|||
|
|
</span>
|
|||
|
|
</button>
|
|||
|
|
</li>
|
|||
|
|
);
|
|||
|
|
})}
|
|||
|
|
</ul>
|
|||
|
|
<p className="text-[12px] text-[rgba(38,37,30,0.6)] mb-3">
|
|||
|
|
Short or different from the PO? Untick it and use Collect on that item to enter what you got.
|
|||
|
|
</p>
|
|||
|
|
<div className="flex gap-2">
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
onClick={onClose}
|
|||
|
|
className="h-12 flex-1 rounded-xl text-[15px] font-semibold bg-[#efeaf7] text-[#6c5a99] touch-manipulation"
|
|||
|
|
>
|
|||
|
|
Not now
|
|||
|
|
</button>
|
|||
|
|
<button
|
|||
|
|
type="button"
|
|||
|
|
disabled={chosen.length === 0}
|
|||
|
|
onClick={() => onCollect(chosen)}
|
|||
|
|
className="h-12 flex-1 rounded-xl text-[15px] font-semibold bg-[#1f8a65] text-white disabled:opacity-50 touch-manipulation"
|
|||
|
|
>
|
|||
|
|
{chosen.length === items.length && items.length > 1
|
|||
|
|
? `Collect all ${items.length}`
|
|||
|
|
: `Collect ${chosen.length} item${chosen.length === 1 ? '' : 's'}`}
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</BottomSheet>
|
|||
|
|
);
|
|||
|
|
}
|