'use client'; import { useState, useEffect } from 'react'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { DollarSign, FileText, AlertCircle, Receipt } from 'lucide-react'; import { format } from 'date-fns'; interface PurchaseHistoryModalProps { configItemId: number; serialNumber?: string; createDate?: string; open: boolean; onOpenChange: (open: boolean) => void; } interface PurchaseHistoryData { billingItems: any[]; invoices?: any[]; } export function PurchaseHistoryModal({ configItemId, serialNumber, createDate, open, onOpenChange, }: PurchaseHistoryModalProps) { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [searchAttempt, setSearchAttempt] = useState(0); // 0 = initial, 1 = before, 2 = after const [selectedInvoice, setSelectedInvoice] = useState(null); const [selectedTicket, setSelectedTicket] = useState(null); const [loadingTicket, setLoadingTicket] = useState(false); const [timeEntries, setTimeEntries] = useState([]); const [loadingTimeEntries, setLoadingTimeEntries] = useState(false); const [showTimeEntries, setShowTimeEntries] = useState(false); const [invoiceLineItems, setInvoiceLineItems] = useState([]); const [loadingInvoiceItems, setLoadingInvoiceItems] = useState(false); const [showInvoiceItems, setShowInvoiceItems] = useState(false); useEffect(() => { if (open && configItemId) { setSearchAttempt(0); // Reset search attempt fetchPurchaseHistory(0); } }, [open, configItemId]); const fetchPurchaseHistory = async (attempt: number) => { setLoading(true); setError(null); try { // Calculate date range based on creation date and attempt let startDate: Date; let endDate: Date; if (!createDate) { // Fallback if no creation date: search last 120 days endDate = new Date(); startDate = new Date(); startDate.setDate(startDate.getDate() - 120); } else { const created = new Date(createDate); if (attempt === 0) { // Initial: 60 days before to 60 days after creation startDate = new Date(created); startDate.setDate(startDate.getDate() - 60); endDate = new Date(created); endDate.setDate(endDate.getDate() + 60); console.log('Initial search: 60 days before/after creation date'); } else if (attempt === 1) { // Second attempt: 120 days before the initial window startDate = new Date(created); startDate.setDate(startDate.getDate() - 180); // 60 + 120 endDate = new Date(created); endDate.setDate(endDate.getDate() - 60); console.log('Extended search: 120 days BEFORE initial window'); } else { // Third attempt: 120 days after the initial window startDate = new Date(created); startDate.setDate(startDate.getDate() + 60); endDate = new Date(created); endDate.setDate(endDate.getDate() + 180); // 60 + 120 console.log('Extended search: 120 days AFTER initial window'); } } const url = `/api/config-enrichment?configItemId=${configItemId}&startDate=${startDate.toISOString().split('T')[0]}&endDate=${endDate.toISOString().split('T')[0]}`; console.log('Fetching from:', url); const response = await fetch(url); console.log('Response status:', response.status); if (!response.ok) { const errorText = await response.text(); console.error('Error response:', errorText); throw new Error('Failed to fetch purchase history'); } const result = await response.json(); console.log('Purchase history result:', result); // If no results and we haven't tried all attempts yet, try next window if (result.billingItems.length === 0 && attempt < 2) { console.log('No results found, trying extended search...'); setSearchAttempt(attempt + 1); await fetchPurchaseHistory(attempt + 1); } else { setData(result); setSearchAttempt(attempt); } } catch (err) { console.error('Purchase history error:', err); setError(err instanceof Error ? err.message : 'An error occurred'); } finally { setLoading(false); } }; const fetchTicketDetails = async (ticketId: number) => { console.log('Fetching ticket details for ID:', ticketId); setLoadingTicket(true); setShowTimeEntries(false); setTimeEntries([]); try { const url = `/api/tickets/${ticketId}`; console.log('Fetching from:', url); const response = await fetch(url); console.log('Response status:', response.status); if (!response.ok) { const errorText = await response.text(); console.error('Error response:', errorText); throw new Error('Failed to fetch ticket details'); } const result = await response.json(); console.log('Ticket result:', result); setSelectedTicket(result.ticket); } catch (err) { console.error('Error fetching ticket:', err); setSelectedTicket({ ticketNumber: ticketId, title: 'Error loading ticket details' }); } finally { setLoadingTicket(false); } }; const fetchTimeEntries = async (ticketId: number) => { setLoadingTimeEntries(true); try { const response = await fetch(`/api/tickets/${ticketId}/time-entries`); if (!response.ok) { throw new Error('Failed to fetch time entries'); } const result = await response.json(); setTimeEntries(result.timeEntries || []); setShowTimeEntries(true); } catch (err) { console.error('Error fetching time entries:', err); setTimeEntries([]); } finally { setLoadingTimeEntries(false); } }; const fetchInvoiceLineItems = async (invoiceId: number) => { setLoadingInvoiceItems(true); try { const response = await fetch(`/api/invoices/${invoiceId}/line-items`); if (!response.ok) { throw new Error('Failed to fetch invoice line items'); } const result = await response.json(); setInvoiceLineItems(result.lineItems || []); setShowInvoiceItems(true); } catch (err) { console.error('Error fetching invoice line items:', err); setInvoiceLineItems([]); } finally { setLoadingInvoiceItems(false); } }; return (
Purchase History Hardware purchase history for this device {serialNumber && ` - Serial: ${serialNumber}`} {createDate && ( Searching around creation date: {format(new Date(createDate), 'MMM d, yyyy')} {searchAttempt > 0 && ` (Extended search ${searchAttempt}/2)`} )}
{/* Summary Cards - Compact */} {!loading && !error && data && data.billingItems.length > 0 && (

Sale Price

${data.billingItems.reduce((sum: number, item: any) => sum + (Number(item.totalAmount) || 0), 0).toFixed(2)}

Cost

${data.billingItems.reduce((sum: number, item: any) => sum + (Number(item.ourCost) || 0), 0).toFixed(2)}

Profit

${data.billingItems.reduce((sum: number, item: any) => sum + (Number(item.profit) || 0), 0).toFixed(2)}

)}
{loading && (
)} {error && (

Error: {error}

)} {!loading && !error && data && (
{/* Billing Items */} {data.billingItems.length > 0 ? (

Purchase Details

Date Description Serial Cost Sale Price Profit Invoice Ticket {data.billingItems.map((item: any, index: number) => ( {item.itemDate ? format(new Date(item.itemDate), 'MMM d, yyyy') : '-'}
{item.description}
{item.purchaseOrderNumber && (
PO: {item.purchaseOrderNumber}
)}
{item.extractedSerialNumber && ( {item.extractedSerialNumber} )} ${(item.ourCost || 0).toFixed(2)} ${(item.totalAmount || 0).toFixed(2)} 0 ? 'text-green-600' : 'text-red-600'}> ${(item.profit || 0).toFixed(2)} {item.profitMargin}% {item.invoiceID ? ( ) : '-'} {item.ticketID ? ( ) : '-'}
))}
) : (

No purchase records found for this serial number

Serial number: {serialNumber || 'Not set'}

This device may have been:

  • • Purchased more than 90 days ago
  • • Added manually without an invoice
  • • Serial number not found in invoice line items
)}
)} {/* Invoice Details Section */} {selectedInvoice && (

Invoice #{selectedInvoice.id} Details

Invoice Date

{selectedInvoice.invoiceDateTime ? format(new Date(selectedInvoice.invoiceDateTime), 'MMM d, yyyy') : '-'}

Total

${(selectedInvoice.invoiceTotal || 0).toFixed(2)}

Status

{selectedInvoice.isPaid ? 'Paid' : 'Unpaid'}

Due Date

{selectedInvoice.dueDateTime ? format(new Date(selectedInvoice.dueDateTime), 'MMM d, yyyy') : '-'}

{/* Invoice Line Items */}
{loadingInvoiceItems && (
)} {showInvoiceItems && invoiceLineItems.length > 0 && (
Description Qty Unit Price Total {invoiceLineItems.map((item: any, index: number) => (
{item.description || '-'}
{item.quantity || 0} ${(item.unitPrice || 0).toFixed(2)} ${(item.totalAmount || 0).toFixed(2)}
))}
)} {showInvoiceItems && invoiceLineItems.length === 0 && !loadingInvoiceItems && (

No line items found

)}
)} {/* Ticket Details Section */} {selectedTicket && (

Ticket #{selectedTicket.ticketNumber} Details

{loadingTicket ? ( ) : (

Ticket Number

{selectedTicket.ticketNumber}

Description

{selectedTicket.title || '-'}

Primary Resource

{selectedTicket.assignedResourceName || '-'}

Status

{selectedTicket.status || '-'}

Created

{selectedTicket.createDate ? format(new Date(selectedTicket.createDate), 'MMM d, yyyy') : '-'}

)} {/* Time Entries Timeline */}
{loadingTimeEntries && (
)} {showTimeEntries && timeEntries.length > 0 && (
{timeEntries.map((entry: any, index: number) => (

{entry.resourceName || 'Unknown Resource'}

{entry.dateWorked ? format(new Date(entry.dateWorked), 'MMM d, yyyy') : '-'}

{entry.hoursWorked || 0}h
{entry.summaryNotes && (

{entry.summaryNotes}

)}
))}
)} {showTimeEntries && timeEntries.length === 0 && !loadingTimeEntries && (

No time entries found

)}
)}
); }