2import { useEffect, useState } from 'react';
3import { useParams, useRouter } from 'next/navigation';
4import { Icon } from '@/contexts/IconContext';
7 f105?: number; // Sale ID
8 f200?: number; // Product ID
9 f202?: number; // Quantity
10 f203?: number; // Line total
11 f205?: number; // Unit price
12 f212?: string; // Product description
15interface SaleDetails {
16 f100?: number; // Sale ID
17 f101?: string; // Created date
18 f102?: number; // Total ex tax
19 f103?: number; // Total inc tax
20 f109?: string; // Completed date
21 f117?: number; // Phase
27export default function InvoiceDetailPage() {
28 const params = useParams();
29 const router = useRouter();
30 const saleId = params.id as string;
32 const [saleDetails, setSaleDetails] = useState<SaleDetails | null>(null);
33 const [loading, setLoading] = useState(true);
34 const [error, setError] = useState<string | null>(null);
38 loadSaleDetails(parseInt(saleId));
42 async function loadSaleDetails(id: number) {
47 console.log('[Sale Details] Fetching details for sale:', id);
49 const response = await fetch(`/api/v1/elink/sales/${id}`);
52 throw new Error(`HTTP error! status: ${response.status}`);
55 const result = await response.json();
57 if (result.success && result.data) {
58 console.log('[Sale Details] Loaded:', result.data);
59 setSaleDetails(result.data);
61 throw new Error(result.error || 'Failed to load sale details');
64 console.error('[Sale Details] Failed to load:', err);
65 setError(`Failed to load sale details: ${err instanceof Error ? err.message : 'Unknown error'}`);
71 async function deleteSale() {
72 if (!confirm(`Are you sure you want to void/delete sale #${saleId}?`)) {
77 const response = await fetch(`/api/v1/elink/sales/${saleId}`, {
80 const result = await response.json();
83 alert('✅ Sale voided successfully');
84 router.push('/pages/invoices');
86 alert(`❌ Failed to void sale: ${result.error || result.details}`);
89 console.error('Delete failed:', error);
90 alert(`❌ Failed to void sale: ${error instanceof Error ? error.message : 'Unknown error'}`);
94 function formatDate(dateStr?: string) {
95 if (!dateStr) return 'N/A';
97 // Handle ELINK pipe-delimited format: "YYYY|M|DD|HH|MM|SS||"
98 if (dateStr.includes('|')) {
99 const parts = dateStr.split('|').filter(p => p !== '');
100 if (parts.length >= 6) {
101 const [year, month, day, hour, minute, second] = parts.map(p => parseInt(p));
102 const date = new Date(year, month - 1, day, hour, minute, second);
103 return date.toLocaleString('en-US', {
112 // Fallback to standard date parsing
113 return new Date(dateStr).toLocaleString();
121 <div className="p-8 h-full overflow-y-auto bg-bg">
122 <div className="max-w-7xl mx-auto">
123 <div className="text-center py-16">
124 <Icon name="hourglass_empty" size={48} className="mx-auto text-muted" />
125 <p className="mt-4 text-xl text-muted">Loading sale details...</p>
132 if (error || !saleDetails) {
134 <div className="p-8 h-full overflow-y-auto bg-bg">
135 <div className="max-w-7xl mx-auto">
137 onClick={() => router.push('/pages/invoices')}
138 className="mb-6 px-4 py-2 bg-surface text-text rounded-lg hover:bg-surface-2 inline-flex items-center gap-2"
140 <Icon name="arrow_back" size={20} /> Back to Sales History
143 <div className="text-center py-16">
144 <Icon name="error" size={48} className="mx-auto text-danger" />
145 <p className="mt-4 text-xl text-text">Failed to load sale details</p>
146 <p className="mt-2 text-muted">{error}</p>
154 <div className="p-8 h-full overflow-y-auto bg-bg">
155 <div className="max-w-7xl mx-auto">
157 <div className="flex justify-between items-center mb-6">
158 <div className="flex items-center gap-4">
160 onClick={() => router.push('/pages/invoices')}
161 className="px-4 py-2 bg-surface text-text rounded-lg hover:bg-surface-2 inline-flex items-center gap-2"
163 <Icon name="arrow_back" size={20} /> Back
165 <h1 className="text-3xl font-bold text-text">Sale #{saleDetails.f100}</h1>
167 <div className="flex gap-3">
169 onClick={() => window.print()}
170 className="px-4 py-2 bg-info text-surface rounded-lg hover:bg-info/80 inline-flex items-center gap-2"
172 <Icon name="print" size={20} /> Print
176 className="px-4 py-2 bg-danger text-surface rounded-lg hover:bg-danger/80 inline-flex items-center gap-2"
178 <Icon name="delete" size={20} /> Void Sale
183 {/* Sale Details Card */}
184 <div className="bg-surface rounded-lg shadow-sm p-8 mb-6">
185 <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
187 <label className="block text-sm font-medium text-muted mb-2">
190 <div className="text-2xl font-bold text-text">#{saleDetails.f100}</div>
194 <label className="block text-sm font-medium text-muted mb-2">
197 <div className="text-lg text-text">{formatDate(saleDetails.f101)}</div>
201 <label className="block text-sm font-medium text-muted mb-2">
204 <div className="text-lg text-text">{formatDate(saleDetails.f109)}</div>
208 <label className="block text-sm font-medium text-muted mb-2">
211 <div className="text-2xl font-semibold text-text">
212 ${saleDetails.f102?.toFixed(2) || '0.00'}
217 <label className="block text-sm font-medium text-muted mb-2">
220 <div className="text-2xl font-bold text-brand">
221 ${saleDetails.f103?.toFixed(2) || '0.00'}
226 <label className="block text-sm font-medium text-muted mb-2">
231 className={`px-4 py-2 text-sm font-semibold rounded inline-block ${
232 saleDetails.f117 === 1
233 ? 'bg-success/20 text-success'
234 : saleDetails.f117 === 200
235 ? 'bg-warn/20 text-warn'
236 : 'bg-surface-2 text-muted'
239 {saleDetails.f117 === 1
241 : saleDetails.f117 === 200
243 : `Phase ${saleDetails.f117 || 'Unknown'}`}
251 {saleDetails.LINE && saleDetails.LINE.length > 0 && (
252 <div className="bg-surface rounded-lg shadow-sm p-8 mb-6">
253 <h2 className="text-2xl font-bold text-text mb-6">Line Items</h2>
254 <div className="overflow-x-auto">
255 <table className="w-full">
256 <thead className="bg-surface-2">
258 <th className="px-6 py-3 text-left text-xs font-medium text-muted uppercase tracking-wider">
261 <th className="px-6 py-3 text-right text-xs font-medium text-muted uppercase tracking-wider">
264 <th className="px-6 py-3 text-right text-xs font-medium text-muted uppercase tracking-wider">
267 <th className="px-6 py-3 text-right text-xs font-medium text-muted uppercase tracking-wider">
272 <tbody className="divide-y divide-border">
273 {saleDetails.LINE.map((line, idx) => (
274 <tr key={idx} className="hover:bg-surface-2">
275 <td className="px-6 py-4">
276 <div className="text-sm font-medium text-text">
277 {line.f212 || 'Unknown Product'}
279 <div className="text-xs text-muted">
280 Product ID: {line.f200 || 'N/A'}
283 <td className="px-6 py-4 text-right text-sm text-text">
286 <td className="px-6 py-4 text-right text-sm text-text">
287 ${line.f205?.toFixed(2) || '0.00'}
289 <td className="px-6 py-4 text-right text-sm font-semibold text-text">
290 ${line.f203?.toFixed(2) || '0.00'}
294 <tr className="bg-surface-2 font-bold">
295 <td colSpan={3} className="px-6 py-4 text-right text-text">
298 <td className="px-6 py-4 text-right text-lg text-brand">
299 ${saleDetails.LINE.reduce((sum, line) => sum + (line.f203 || 0), 0).toFixed(2)}
309 {saleDetails.PAYM && saleDetails.PAYM.length > 0 && (
310 <div className="bg-surface rounded-lg shadow-sm p-8 mb-6">
311 <h2 className="text-2xl font-bold text-text mb-6">Payments</h2>
312 <div className="space-y-4">
313 {saleDetails.PAYM.map((payment, idx) => (
314 <div key={idx} className="flex justify-between items-center p-4 bg-surface-2 rounded-lg">
316 <div className="font-medium text-text">
317 {payment.f900 || payment.f200 || 'Payment Method'}
319 <div className="text-sm text-muted">
320 Payment ID: {payment.f100 || 'N/A'}
323 <div className="text-xl font-semibold text-text">
324 ${payment.f201?.toFixed(2) || '0.00'}
332 {/* Location Info */}
333 {saleDetails.LOCN && saleDetails.LOCN.length > 0 && (
334 <div className="bg-surface rounded-lg shadow-sm p-8">
335 <h2 className="text-2xl font-bold text-text mb-6">Location</h2>
336 <div className="space-y-4">
337 {saleDetails.LOCN.map((location, idx) => (
338 <div key={idx} className="p-4 bg-surface-2 rounded-lg">
339 <div className="font-medium text-text">
340 {location.f101 || 'Unknown Location'}
342 <div className="text-sm text-muted">
343 Location ID: {location.f100 || 'N/A'}