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>
95 lines
3.6 KiB
TypeScript
95 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>
|
||
);
|
||
}
|