quest-vorteq/src/app/(portal)/coil-activity/receipts/page.tsx
Lorentz Hinrichsen 53c35cc273
Some checks failed
Build and Deploy / build (push) Successful in 10m53s
Build and Deploy / deploy (push) Failing after 2s
feat: add order ack detail/PDF, BOL detail/PDF, coil activity usage & receipts
C-005: Order Acknowledgement Detail + PDF
- Order detail page with full line items, releases, addresses, paint codes
- PDF export via Puppeteer matching legacy format
- Clickable order # links and PDF icons in orders table

C-007: BOL Detail + PDF
- BOL detail page with ship-from/to, line items, weights
- PDF export matching legacy BOL format
- PDF icon column in shipments table

C-008: Coil Activity - Usage Report
- Date range picker (max 31 days, default last 10 days)
- Reusable UI: Popover, Calendar (react-day-picker v9), DateRangePicker
- SQL from legacy portal_CoilActivityUsage.sql with OnHandQty dedup
- 11-column sortable table with search and CSV export

C-009: Coil Activity - Receipts Report
- VGL customer exception (special SQL vs portal view)
- 10-column sortable table with search and CSV export
- Shared date range picker component

Also: dashboard API route, shipments API route, HDC→HDM mapping,
Puppeteer PDF infrastructure, improved error handling.

Progress: 9/16 Phase 2 tasks complete.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:14:52 -05:00

92 lines
2.6 KiB
TypeScript

'use client';
import { useEffect, useState, useCallback } from 'react';
import { subDays } from 'date-fns';
import type { CoilReceiptRow } from '@/types/coil-activity';
import { ReceiptsTable } from '@/components/coil-activity/receipts-table';
import { DateRangePicker } from '@/components/ui/date-range-picker';
import { Card, CardContent } from '@/components/ui/card';
function LoadingSkeleton() {
return (
<Card>
<CardContent className="p-6">
<div className="space-y-4">
{[...Array(10)].map((_, i) => (
<div
key={i}
className="h-12 w-full animate-pulse rounded bg-muted"
/>
))}
</div>
</CardContent>
</Card>
);
}
export default function CoilReceiptsPage() {
const [data, setData] = useState<CoilReceiptRow[] | null>(null);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
const [dateRange, setDateRange] = useState(() => ({
from: subDays(new Date(), 9),
to: new Date(),
}));
const fetchData = useCallback(async () => {
setLoading(true);
setError(null);
try {
const startDate = dateRange.from.toISOString().split('T')[0];
const endDate = dateRange.to.toISOString().split('T')[0];
const res = await fetch(
`/api/coil-activity/receipts?startDate=${startDate}&endDate=${endDate}`
);
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.error || `HTTP ${res.status}`);
}
const json = await res.json();
setData(json);
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
} finally {
setLoading(false);
}
}, [dateRange]);
useEffect(() => {
fetchData();
}, [fetchData]);
return (
<div>
<div className="mb-6 flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between">
<div>
<h1 className="mb-2 text-3xl font-bold">Coil Activity Receipts</h1>
<p className="text-muted-foreground">
View coil material receipts for the selected date range
</p>
</div>
<DateRangePicker
from={dateRange.from}
to={dateRange.to}
onUpdate={setDateRange}
maxDays={31}
/>
</div>
{error ? (
<Card>
<CardContent className="p-6 text-center text-destructive">
Failed to load receipts data: {error}
</CardContent>
</Card>
) : loading || data === null ? (
<LoadingSkeleton />
) : (
<ReceiptsTable data={data} />
)}
</div>
);
}