WFDASHDRIVER-APP/components/CollectModal.tsx

179 lines
6.6 KiB
TypeScript
Raw Normal View History

'use client';
import { useState } from 'react';
import { OrderItem } from '@/lib/types';
import { cn, formatCurrency } from '@/lib/utils';
import { X, Minus, Plus, Check } from 'lucide-react';
interface CollectModalProps {
item: OrderItem | null;
isOpen: boolean;
onClose: () => void;
onConfirm: (item: OrderItem, receivedQty: number, notes: string) => void;
}
export default function CollectModal({
item,
isOpen,
onClose,
onConfirm,
}: CollectModalProps) {
const [receivedQty, setReceivedQty] = useState('');
const [notes, setNotes] = useState('');
const [error, setError] = useState('');
if (!isOpen || !item) return null;
const expectedQty = item.order_qty || item.quantity || 0;
const parsedQty = parseFloat(receivedQty) || 0;
const isPartial = parsedQty > 0 && parsedQty < expectedQty;
const isOverQty = parsedQty > expectedQty;
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setError('');
const qty = parseFloat(receivedQty);
if (isNaN(qty) || qty <= 0) {
setError('Please enter a valid quantity');
return;
}
onConfirm(item, qty, notes);
setReceivedQty('');
setNotes('');
setError('');
};
const adjustQty = (delta: number) => {
const current = parseFloat(receivedQty) || 0;
const newQty = Math.max(0, current + delta);
setReceivedQty(newQty.toString());
setError('');
};
return (
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center">
<div className="absolute inset-0 bg-black/50 modal-overlay" onClick={onClose} />
<div className="relative bg-white bottom-sheet w-full max-w-md p-6 animate-slide-up">
{/* Drag indicator */}
<div className="sm:hidden flex justify-center mb-4">
<div className="w-10 h-1 bg-gray-300 rounded-full" />
</div>
<div className="flex items-center justify-between mb-5">
<div className="flex items-center gap-2.5">
<div className="w-8 h-8 bg-green-50 rounded-xl flex items-center justify-center">
<Check size={18} className="text-green-600" />
</div>
<h2 className="text-lg font-bold text-gray-900">Collect Item</h2>
</div>
<button
onClick={onClose}
className="p-2 rounded-xl hover:bg-gray-100 active:bg-gray-200 transition-colors"
>
<X size={18} className="text-gray-400" />
</button>
</div>
{/* Item info */}
<div className="mb-5 p-4 bg-gray-50 rounded-xl">
<p className="text-sm font-semibold text-gray-900 truncate">{item.description}</p>
<div className="flex items-center gap-3 mt-1.5 text-xs text-gray-500">
<span className="font-medium text-gray-700">
{item.expand?.supplier?.suppliername || 'Unknown'}
</span>
<span>Expected: {expectedQty} units</span>
</div>
{item.selling_price && (
<p className="text-xs text-green-600 font-medium mt-1">
Unit price: {formatCurrency(item.selling_price)}
</p>
)}
</div>
<form onSubmit={handleSubmit} className="space-y-5">
{/* Quantity */}
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Received Quantity *
</label>
<div className="flex items-center gap-3">
<button
type="button"
onClick={() => adjustQty(-1)}
className="w-12 h-12 flex items-center justify-center rounded-xl bg-gray-100 active:bg-gray-200 transition-colors touch-manipulation press-feedback-sm"
>
<Minus size={18} className="text-gray-600" />
</button>
<input
type="number"
step="0.01"
inputMode="decimal"
value={receivedQty}
onChange={(e) => { setReceivedQty(e.target.value); setError(''); }}
className="w-20 text-center text-2xl font-bold py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-green-500/30 focus:border-green-500 transition-all"
placeholder="0"
autoFocus
/>
<button
type="button"
onClick={() => adjustQty(1)}
className="w-12 h-12 flex items-center justify-center rounded-xl bg-gray-100 active:bg-gray-200 transition-colors touch-manipulation press-feedback-sm"
>
<Plus size={18} className="text-gray-600" />
</button>
{expectedQty > 0 && (
<button
type="button"
onClick={() => { setReceivedQty(expectedQty.toString()); setError(''); }}
className="px-3 py-2 text-xs font-medium text-green-700 bg-green-50 rounded-lg active:bg-green-100 transition-colors touch-manipulation"
>
Full ({expectedQty})
</button>
)}
</div>
{isPartial && (
<p className="text-xs text-amber-600 mt-2 font-medium">
Partial collection: {parsedQty} of {expectedQty} units
</p>
)}
{isOverQty && (
<p className="text-xs text-blue-600 mt-2 font-medium">
Over collection: {parsedQty} exceeds expected {expectedQty}
</p>
)}
{error && <p className="text-xs text-red-500 mt-2">{error}</p>}
</div>
{/* Notes */}
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Notes (optional)
</label>
<textarea
value={notes}
onChange={(e) => setNotes(e.target.value)}
className="w-full p-3 text-sm bg-gray-50 border border-gray-200 rounded-xl focus:ring-2 focus:ring-green-500/30 focus:border-green-500 focus:bg-white transition-all resize-none selectable"
rows={2}
placeholder="Add any notes about this collection..."
/>
</div>
{/* Submit */}
<button
type="submit"
className={cn(
'w-full py-4 px-4 rounded-xl text-white font-semibold text-[15px]',
'bg-green-500 active:bg-green-600 transition-all shadow-lg shadow-green-500/25',
'touch-manipulation press-feedback',
(!parsedQty || parsedQty < 0) && 'opacity-50'
)}
>
Confirm Collection
</button>
</form>
</div>
</div>
);
}