WFDASHDRIVER-APP/components/CollectPrompt.tsx
Dave c2faca98bd feat: ask to collect a PO's items after its invoice photo uploads
An invoice photo collects nothing, and the green invoice button made it
look done, so lines stayed "assigned" and never reached Collection
Confirm (EWSS and G-Man on 2026-10-26). After an upload the driver now
gets "Collect these items?" with every item ticked. Without the AI match
it opens straight away; with it, once the match sheet closes, listing
only what the match left.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 10:54:09 +02:00

95 lines
3.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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>
);
}