'use client'; import { useCallback, useEffect, useState } from 'react'; import type { AllocRequestHeader, AddAllocCartItemPayload, UpdateCartHeaderPayload, } from '@/types/requests'; import { RequestStepper, type StepConfig } from './request-stepper'; import { ShipToSelector } from './ship-to-selector'; import { CartItemsTable } from './cart-items-table'; import { CartHeaderForm } from './cart-header-form'; import { CartReview } from './cart-review'; import { CoilBrowserDialog } from './coil-browser-dialog'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Button } from '@/components/ui/button'; import { Loader2, ArrowLeft, ArrowRight, Briefcase } from 'lucide-react'; import { useRouter, useSearchParams } from 'next/navigation'; import { useToast } from '@/hooks/use-toast'; const STEPS: StepConfig[] = [ { label: 'Job & Address', description: 'Job# + ship-to' }, { label: 'Items', description: 'Add coils' }, { label: 'Details', description: 'Order info' }, { label: 'Review', description: 'Submit' }, ]; export function AllocationRequestCart() { const router = useRouter(); const searchParams = useSearchParams(); const { toast } = useToast(); const [cart, setCart] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [currentStep, setCurrentStep] = useState(0); const [browserOpen, setBrowserOpen] = useState(false); const [jobNumber, setJobNumber] = useState(searchParams.get('job') || ''); const [jobConfirmed, setJobConfirmed] = useState(false); // Fetch or create cart for a specific job const fetchCart = useCallback(async (job: string) => { setLoading(true); setError(null); try { const res = await fetch( `/api/allocation-requests/cart?jobNumber=${encodeURIComponent(job)}` ); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); setCart(data); setJobConfirmed(true); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load cart'); } finally { setLoading(false); } }, []); // If job number came from URL, fetch cart immediately useEffect(() => { const urlJob = searchParams.get('job'); if (urlJob) { fetchCart(urlJob); } }, [searchParams, fetchCart]); const handleConfirmJob = () => { if (!jobNumber.trim()) return; fetchCart(jobNumber.trim()); }; // Update cart header const updateHeader = async (data: UpdateCartHeaderPayload) => { if (!cart) return; const res = await fetch('/api/allocation-requests/cart', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: cart.id, ...data }), }); if (!res.ok) { const err = await res.json(); throw new Error(err.error || 'Failed to update cart'); } const updated = await res.json(); setCart(updated); }; // Add item const addItem = async (item: AddAllocCartItemPayload) => { if (!cart) return; const res = await fetch('/api/allocation-requests/cart/items', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ request_id: cart.id, ...item }), }); if (!res.ok) { const err = await res.json(); toast({ title: 'Error', description: err.error || 'Failed to add item', variant: 'destructive', }); return; } toast({ title: 'Item added to cart' }); await fetchCart(cart.job_number); }; // Remove item const removeItem = async (detailId: string) => { if (!cart) return; const res = await fetch( `/api/allocation-requests/cart/items?detailId=${detailId}&requestId=${cart.id}`, { method: 'DELETE' } ); if (!res.ok) { const err = await res.json(); toast({ title: 'Error', description: err.error || 'Failed to remove item', variant: 'destructive', }); return; } await fetchCart(cart.job_number); }; // Update item const updateItem = async ( detailId: string, data: { quantity?: number; notes?: string | null } ) => { if (!cart) return; const res = await fetch( `/api/allocation-requests/cart/items/${detailId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ request_id: cart.id, ...data }), } ); if (!res.ok) { const err = await res.json(); toast({ title: 'Error', description: err.error || 'Failed to update item', variant: 'destructive', }); return; } await fetchCart(cart.job_number); }; // Submit const submitCart = async () => { if (!cart) return; const res = await fetch('/api/allocation-requests/cart/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: cart.id }), }); if (!res.ok) { const err = await res.json(); toast({ title: 'Error', description: err.error || 'Failed to submit request', variant: 'destructive', }); return; } toast({ title: 'Allocation request submitted successfully!' }); router.push('/allocation-requests'); }; // Handle ship-to selection const handleSelectAddress = (address: string) => { updateHeader({ ship_to_address: address }); }; // Job number entry step (before cart is loaded) if (!jobConfirmed) { return (
Job Number
setJobNumber(e.target.value)} placeholder="e.g. J12345" onKeyDown={(e) => e.key === 'Enter' && handleConfirmJob()} />
{error && (

{error}

)}
); } if (loading) { return ( ); } if (error || !cart) { return ( Failed to load cart: {error || 'Unknown error'} ); } return (
{/* Job number indicator */}
Job: {cart.job_number}
{/* Step 0: Ship-To Address */} {currentStep === 0 && ( setCurrentStep(1)} /> )} {/* Step 1: Items */} {currentStep === 1 && ( <> setBrowserOpen(true)} />
setBrowserOpen(false)} jobNumber={cart.job_number} onAddItem={addItem} /> )} {/* Step 2: Details */} {currentStep === 2 && ( setCurrentStep(1)} onNext={() => setCurrentStep(3)} /> )} {/* Step 3: Review */} {currentStep === 3 && ( setCurrentStep(2)} /> )}
); }