Settings > Beta switch adds an advisory Check invoice button on POs with an invoice photo. Nothing else in collecting changes. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
140 lines
4.9 KiB
TypeScript
140 lines
4.9 KiB
TypeScript
'use client';
|
|
|
|
import { useRef, useState } from 'react';
|
|
import { Camera, FileText, Loader2, Check } from 'lucide-react';
|
|
import { OrderItem, PurchaseOrder } from '@/lib/types';
|
|
import { buildPoFileUrl, countInvoicePages, invoicePhotoTarget } from '@/lib/poDocs';
|
|
import InvoiceCheck from '@/components/InvoiceCheck';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface PoActionsProps {
|
|
purchaseOrder: PurchaseOrder | null;
|
|
items: OrderItem[];
|
|
/** Uploads one invoice page for this PO. Rejects on failure. */
|
|
onAddInvoicePage: (items: OrderItem[], file: File) => Promise<void>;
|
|
/** False hides the photo button (e.g. the caller can't upload). */
|
|
canPhoto?: boolean;
|
|
/** 'lg' = full-width stacked buttons, for the PO bottom sheet. */
|
|
size?: 'sm' | 'lg';
|
|
className?: string;
|
|
}
|
|
|
|
// The two PO-level buttons on a PO header: open the PO PDF in one tap,
|
|
// and photograph the supplier invoice once for the whole PO (with
|
|
// "Page 2", "Page 3"… for multi-page invoices).
|
|
export default function PoActions({
|
|
purchaseOrder,
|
|
items,
|
|
onAddInvoicePage,
|
|
canPhoto: photoAllowed = true,
|
|
size = 'sm',
|
|
className,
|
|
}: PoActionsProps) {
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const [uploading, setUploading] = useState(false);
|
|
|
|
const pdfUrl = buildPoFileUrl(purchaseOrder);
|
|
// A customer PO's photo goes on one of its lines; a stock order's on
|
|
// the stock purchase. No target (no PO at all) means no photo button.
|
|
const canPhoto = photoAllowed && invoicePhotoTarget(items) !== null;
|
|
const pages = countInvoicePages(items);
|
|
|
|
if (!pdfUrl && !canPhoto) return null;
|
|
|
|
const handleFile = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0];
|
|
// Reset so taking the same shot again still fires onChange.
|
|
e.target.value = '';
|
|
if (!file || uploading) return;
|
|
setUploading(true);
|
|
try {
|
|
await onAddInvoicePage(items, file);
|
|
} catch {
|
|
// The page handler already told the driver; nothing to add here.
|
|
} finally {
|
|
setUploading(false);
|
|
}
|
|
};
|
|
|
|
const poLabel = purchaseOrder?.po_number ? `PO ${purchaseOrder.po_number}` : 'this PO';
|
|
const lg = size === 'lg';
|
|
const btn = lg
|
|
? 'h-12 w-full rounded-xl text-[15px] font-semibold flex items-center justify-center gap-2 transition-colors touch-manipulation'
|
|
: 'h-9 px-3 rounded-lg text-[13px] font-semibold flex items-center gap-1.5 transition-colors touch-manipulation';
|
|
const icon = lg ? 18 : 14;
|
|
|
|
return (
|
|
<div className={cn(lg ? 'flex flex-col gap-2' : 'flex items-center gap-1 flex-shrink-0', className)}>
|
|
{pdfUrl && (
|
|
<a
|
|
href={pdfUrl}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
aria-label={`Open the PDF for ${poLabel}`}
|
|
className={cn(
|
|
btn,
|
|
lg ? 'bg-[#26251e] text-white active:bg-black' : 'bg-[#efeaf7] text-[#6c5a99] active:bg-[#ded5ef]',
|
|
)}
|
|
>
|
|
<FileText size={icon} aria-hidden="true" />
|
|
<span>{lg ? 'Open PO PDF' : 'PO'}</span>
|
|
</a>
|
|
)}
|
|
{canPhoto && (
|
|
<>
|
|
<button
|
|
type="button"
|
|
onClick={() => inputRef.current?.click()}
|
|
disabled={uploading}
|
|
aria-busy={uploading}
|
|
aria-label={
|
|
pages === 0
|
|
? `Take a photo of the invoice for ${poLabel}`
|
|
: `Add page ${pages + 1} of the invoice for ${poLabel}`
|
|
}
|
|
className={cn(
|
|
btn,
|
|
pages > 0
|
|
? 'bg-[#e6f1ec] text-[#125a3e] active:bg-[#cfe6db]'
|
|
: lg
|
|
? 'bg-white text-[#26251e] border border-[rgba(38,37,30,0.2)] active:bg-[#f2f1ed]'
|
|
: 'bg-[#26251e] text-white active:bg-black',
|
|
uploading && 'opacity-60',
|
|
)}
|
|
>
|
|
{uploading ? (
|
|
<Loader2 size={icon} className="animate-spin" aria-hidden="true" />
|
|
) : pages === 0 ? (
|
|
<Camera size={icon} aria-hidden="true" />
|
|
) : (
|
|
<Check size={icon} aria-hidden="true" />
|
|
)}
|
|
<span>
|
|
{uploading
|
|
? 'Uploading…'
|
|
: pages === 0
|
|
? lg ? 'Invoice photo' : 'Invoice'
|
|
: lg
|
|
? `Invoice: ${pages} page${pages === 1 ? '' : 's'} · add page ${pages + 1}`
|
|
: `${pages} pg · add pg ${pages + 1}`}
|
|
</span>
|
|
</button>
|
|
<input
|
|
ref={inputRef}
|
|
type="file"
|
|
accept="image/*"
|
|
capture="environment"
|
|
onChange={handleFile}
|
|
className="hidden"
|
|
aria-hidden="true"
|
|
tabIndex={-1}
|
|
/>
|
|
</>
|
|
)}
|
|
{/* Beta helper: renders nothing unless switched on in Settings. */}
|
|
{purchaseOrder?.po_number && (
|
|
<InvoiceCheck poNumber={purchaseOrder.po_number} pages={pages} size={size} />
|
|
)}
|
|
</div>
|
|
);
|
|
}
|