import { useState, useEffect } from 'react' import Layout from '@/components/layout/Layout' import { Card, EmptyState, Btn, Modal, Field, Input, Textarea, showToast, Tag } from '@/components/ui' import { useApp } from '@/lib/context' import { SHIPMENT_SEND_ROLES } from '@/lib/auth' const TYPE_TAG: Record = { FORM_DATA: 'purple', NCR_FIX: 'green', AUDIT: 'amber', OTHER: 'gray' } const TYPE_LABEL: Record = { FORM_DATA: 'Form data', NCR_FIX: 'NCR fix', AUDIT: 'Audit', OTHER: 'Other' } export default function ShipmentsPage() { const { user } = useApp() const [shipments, setShipments] = useState([]) const [loading, setLoading] = useState(true) const [openId, setOpenId] = useState(null) const [items, setItems] = useState>({}) // New shipment modal const [newOpen, setNewOpen] = useState(false) const [newForm, setNewForm] = useState({ product: '', batch: '', client: '', clientEmail: '', shippedAt: '' }) const [suggested, setSuggested] = useState([]) const [creating, setCreating] = useState(false) // Compose modal const [composeShipment, setComposeShipment] = useState(null) const [composeForm, setComposeForm] = useState({ email: '', subject: '', message: '' }) const [sending, setSending] = useState(false) const canSend = user && (SHIPMENT_SEND_ROLES as readonly string[]).includes(user.role) useEffect(() => { load() }, []) async function load() { setLoading(true) const res = await fetch('/api/shipments') if (res.ok) { const { data } = await res.json(); setShipments(data || []) } setLoading(false) } async function suggestItems(product: string) { if (!product) { setSuggested([]); return } const res = await fetch(`/api/shipments/suggest?product=${encodeURIComponent(product)}`) if (res.ok) { const { data } = await res.json(); setSuggested(data || []) } } async function createShipment() { if (!newForm.product || !newForm.batch || !newForm.client || !newForm.shippedAt) { showToast('Product, batch, client, and ship date required', 'error'); return } setCreating(true) const res = await fetch('/api/shipments', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...newForm, items: suggested }), }) setCreating(false) if (res.ok) { setNewOpen(false) setNewForm({ product: '', batch: '', client: '', clientEmail: '', shippedAt: '' }) setSuggested([]) showToast('Shipment recorded') load() } else { showToast('Failed to create shipment', 'error') } } function toggleShipment(s: any) { if (openId === s.id) { setOpenId(null); return } setOpenId(s.id) const sel: Record = {} s.items.forEach((it: any) => { sel[it.id] = it.included }) setItems(sel) } function openCompose(s: any) { const selected = s.items.filter((it: any) => items[it.id]) const lines = selected.map((it: any) => `- ${it.label}`).join('\n') setComposeShipment(s) setComposeForm({ email: s.clientEmail || '', subject: `Quality Release Package — ${s.product} — Batch ${s.batch}`, message: `Hello,\n\nPlease find confirmation that the following items have passed QC standards and the product has been cleared for shipment:\n\n${lines}\n\nIf you have any questions, just reply to this email.\n\nThanks,\nQuality team`, }) } async function sendCompose() { if (!composeForm.email) { showToast('Client email required', 'error'); return } setSending(true) const res = await fetch(`/api/shipments/${composeShipment.id}/send`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ clientEmail: composeForm.email, subject: composeForm.subject, message: composeForm.message }), }) setSending(false) if (res.ok) { showToast(`Package sent to ${composeForm.email}`) setComposeShipment(null) load() } else { showToast('Failed to send', 'error') } } return (

Client release packages

Shipments grouped by product, batch, and date — send "good status" confirmation to clients

setNewOpen(true)}>+ Record shipment
Send access: Production leads · Logistics lead · Admin
{loading ? (
Loading…
) : shipments.length === 0 ? ( setNewOpen(true) }}/> ) : shipments.map((s: any) => (
toggleShipment(s)} style={{ padding: '13px 16px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', cursor: 'pointer' }}>
{s.product} — Batch {s.batch}
{s.ref} · shipped {new Date(s.shippedAt).toLocaleDateString()} · {s.client} · {s.items.filter((i: any) => i.included).length} records included {s.sentAt && · sent to {s.sentTo}} {s._count?.escapes > 0 && · {s._count.escapes} client issue{s._count.escapes > 1 ? 's' : ''}}
{openId === s.id && (
{s.items.map((it: any) => ( ))} {canSend ? ( openCompose(s)} style={{ marginTop: '12px' }}>Email selected to client ) : (
Only Production leads, Logistics lead, or Admin can send this package.
)}
)}
))} {/* New shipment modal */} setNewOpen(false)} title="Record shipment" width={460}> { setNewForm(f => ({ ...f, product: e.target.value })); suggestItems(e.target.value) }} placeholder="Widget A Rev 2"/>
setNewForm(f => ({ ...f, batch: e.target.value }))} placeholder="B-2024-06"/> setNewForm(f => ({ ...f, shippedAt: e.target.value }))}/>
setNewForm(f => ({ ...f, client: e.target.value }))} placeholder="Acme Distribution"/> setNewForm(f => ({ ...f, clientEmail: e.target.value }))} placeholder="qa@acme.com"/> {suggested.length > 0 && (
Auto-suggested {suggested.length} record{suggested.length !== 1 ? 's' : ''} for this product — adjust after creating.
)}
setNewOpen(false)}>Cancel {creating ? 'Saving…' : 'Record shipment'}
{/* Compose modal */} setComposeShipment(null)} title="Send quality release package" width={480}> setComposeForm(f => ({ ...f, email: e.target.value }))} placeholder="client@company.com"/> setComposeForm(f => ({ ...f, subject: e.target.value }))}/>