import React from 'react'; import superagent from 'superagent'; import { Header, Segment, Form, Button, Icon, Table, Label, Divider, Grid, Message, Input, Dropdown, Modal, TextArea, Statistic, Loader } from 'semantic-ui-react'; import ExperimentalWrapper from './ExperimentalWrapper.jsx'; // Status options for charge flags const STATUS_OPTIONS = [ { key: 'new', text: 'New', value: 'new' }, { key: 'reviewed', text: 'Reviewed', value: 'reviewed' }, { key: 'disputed', text: 'Disputed', value: 'disputed' }, { key: 'resolved', text: 'Resolved', value: 'resolved' }, ]; export default class DHLChargesPage extends React.Component { constructor(props) { super(props); this.state = { // Data invoices: [], flaggedCharges: [], shipmentLookupResults: {}, // UI State loading: false, loadingCharges: false, selectedInvoice: null, statusFilter: 'all', searchTrackingNumber: '', // Modal state editModalOpen: false, editingCharge: null, editStatus: '', editNotes: '', // CSV Upload state uploading: false, uploadResult: null, // Reconcile state reconcilingInvoiceId: null, // Clear all state clearing: false, clearConfirmOpen: false, clearResult: null, // Lookup state lookupModalOpen: false, lookupTrackingNumber: '', lookupResult: null, lookupLoading: false, // Fee breakdown expanded rows expandedChargeIds: {}, // Raw data state rawDataExpanded: {}, // Summary stats from API summary: null, // Selected invoice detail (with hydrated charges + shipment data) selectedInvoiceDetail: null, // Tab state for invoice detail view activeTab: 'charges' }; } componentDidMount() { this.loadInvoices(); this.loadFlaggedCharges(); this.loadSummary(); } async loadInvoices() { this.setState({ loading: true }); try { const res = await superagent.get('/api/admin/dhl/invoices').accept('json'); this.setState({ invoices: res.body.items || [] }); } catch (error) { console.error('Failed to load invoices:', error); } finally { this.setState({ loading: false }); } } async loadSummary() { try { const res = await superagent.get('/api/admin/dhl/invoices/summary').accept('json'); this.setState({ summary: res.body }); } catch (error) { console.error('Failed to load summary:', error); } } async loadFlaggedCharges(invoiceId) { this.setState({ loadingCharges: true }); try { const query = {}; if (invoiceId) { query.invoiceId = invoiceId; } const res = await superagent.get('/api/admin/dhl/charges/flagged') .query(query) .accept('json'); this.setState({ flaggedCharges: res.body.items || [] }); } catch (error) { console.error('Failed to load flagged charges:', error); } finally { this.setState({ loadingCharges: false }); } } async uploadCSV(file) { this.setState({ uploading: true, uploadResult: null }); try { const res = await superagent.post('/api/admin/dhl/invoices/upload-csv') .attach('file', file) .accept('json'); this.setState({ uploadResult: res.body }); await this.loadInvoices(); await this.loadSummary(); } catch (error) { console.error('CSV upload failed:', error); this.setState({ uploadResult: { error: error.response?.body?.error || error.message } }); } finally { this.setState({ uploading: false }); } } async clearAllData() { this.setState({ clearing: true, clearResult: null }); try { const res = await superagent.delete('/api/admin/dhl/all').accept('json'); this.setState({ clearResult: res.body, clearConfirmOpen: false }); await this.loadInvoices(); await this.loadFlaggedCharges(); await this.loadSummary(); } catch (error) { console.error('Clear all failed:', error); this.setState({ clearResult: { error: error.response?.body?.error || error.message }, clearConfirmOpen: false }); } finally { this.setState({ clearing: false }); } } async reconcileInvoice(invoiceId) { this.setState({ reconcilingInvoiceId: invoiceId }); try { await superagent.post('/api/admin/dhl/reconcile') .send({ invoiceId }) .accept('json'); // Reload both invoices and charges await this.loadInvoices(); await this.loadFlaggedCharges(this.state.selectedInvoice); } catch (error) { console.error('Reconciliation failed:', error); } finally { this.setState({ reconcilingInvoiceId: null }); } } async lookupShipment(trackingNumber) { this.setState({ lookupLoading: true }); try { const res = await superagent.get('/api/admin/shipper/shipments') .query({ trackingNumber, limit: 1 }); const shipments = res.body; if (shipments && shipments.items && shipments.items.length > 0) { const shipment = shipments.items[0]; this.setState({ lookupResult: shipment, shipmentLookupResults: { ...this.state.shipmentLookupResults, [trackingNumber]: shipment } }); return shipment; } else if (shipments && shipments.length > 0) { const shipment = shipments[0]; this.setState({ lookupResult: shipment, shipmentLookupResults: { ...this.state.shipmentLookupResults, [trackingNumber]: shipment } }); return shipment; } else { this.setState({ lookupResult: { notFound: true } }); return null; } } catch (error) { console.error('Failed to lookup shipment:', error); this.setState({ lookupResult: { error: error.message } }); return null; } finally { this.setState({ lookupLoading: false }); } } openEditModal(charge) { this.setState({ editModalOpen: true, editingCharge: charge, editStatus: charge.status, editNotes: charge.notes || '' }); } async saveChargeEdits() { const { editingCharge, editStatus, editNotes } = this.state; try { await superagent.put(`/api/admin/dhl/charges/${editingCharge.uniqueId || editingCharge.id}`) .send({ status: editStatus, notes: editNotes }) .accept('json'); this.setState({ editModalOpen: false, editingCharge: null }); // Reload charges await this.loadFlaggedCharges(this.state.selectedInvoice); } catch (error) { console.error('Failed to save charge edits:', error); } } openLookupModal(trackingNumber) { this.setState({ lookupModalOpen: true, lookupTrackingNumber: trackingNumber, lookupResult: this.state.shipmentLookupResults[trackingNumber] || null }); if (!this.state.shipmentLookupResults[trackingNumber]) { this.lookupShipment(trackingNumber); } } async selectInvoice(invoiceId) { this.setState({ selectedInvoice: invoiceId, activeTab: 'charges', selectedInvoiceDetail: null }); this.loadFlaggedCharges(invoiceId); if (invoiceId) { // Find the invoice's numeric id for the API call const invoice = this.state.invoices.find(inv => (inv.uniqueId || inv.id) === invoiceId); if (invoice) { this.setState({ loadingCharges: true }); try { const res = await superagent.get(`/api/admin/dhl/invoices/${invoice.id}`).accept('json'); this.setState({ selectedInvoiceDetail: res.body }); } catch (error) { console.error('Failed to load invoice detail:', error); } finally { this.setState({ loadingCharges: false }); } } } } toggleRawData(invoiceId) { this.setState(prev => ({ rawDataExpanded: { ...prev.rawDataExpanded, [invoiceId]: !prev.rawDataExpanded[invoiceId] } })); } formatDate(timestamp) { if (!timestamp) return '-'; return new Date(timestamp).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); } formatCurrency(amount, currency = 'USD') { if (amount === null || amount === undefined) return '-'; return new Intl.NumberFormat('en-US', { style: 'currency', currency: currency }).format(amount); } toggleFeeBreakdown(chargeId) { this.setState(prev => ({ expandedChargeIds: { ...prev.expandedChargeIds, [chargeId]: !prev.expandedChargeIds[chargeId] } })); } renderFeeBreakdown(fb, currency) { if (!fb || typeof fb !== 'object') return null; const items = []; if (fb.weightCharge != null) items.push({ label: 'Weight Charge', amount: fb.weightCharge }); if (fb.fuelSurcharge != null) items.push({ label: 'Fuel Surcharge', amount: fb.fuelSurcharge }); if (fb.taxAdjustment != null) items.push({ label: 'Tax Adjustment', amount: fb.taxAdjustment }); if (fb.invoiceFee != null) items.push({ label: 'Invoice Fee', amount: fb.invoiceFee }); if (fb.otherCharges && fb.otherCharges.length > 0) { fb.otherCharges.forEach(oc => items.push({ label: `Other: ${oc.code}`, amount: oc.amount })); } if (fb.extraCharges && fb.extraCharges.length > 0) { fb.extraCharges.forEach(xc => items.push({ label: xc.name || xc.code, amount: xc.amount })); } if (fb.discounts && fb.discounts.length > 0) { fb.discounts.forEach(d => items.push({ label: `Discount: ${d.code}`, amount: d.amount })); } if (fb.totalExtraCharges != null) items.push({ label: 'Total Extra Charges', amount: fb.totalExtraCharges, bold: true }); if (items.length === 0) return No breakdown; return ( {items.map((item, i) => ( {item.label} {this.formatCurrency(item.amount, currency)} ))}
); } getStatusColor(status) { switch (status) { case 'new': return 'red'; case 'reviewed': return 'yellow'; case 'disputed': return 'orange'; case 'resolved': return 'green'; case 'pending': return 'yellow'; case 'reconciled': return 'blue'; default: return 'grey'; } } getFilteredCharges() { const { flaggedCharges, statusFilter, selectedInvoice, searchTrackingNumber } = this.state; return flaggedCharges.filter(charge => { if (statusFilter !== 'all' && charge.status !== statusFilter) return false; if (selectedInvoice && charge.invoiceId !== selectedInvoice) return false; if (searchTrackingNumber && !charge.trackingNumber.toLowerCase().includes(searchTrackingNumber.toLowerCase())) return false; return true; }); } calculateSummaryStats() { const { flaggedCharges } = this.state; const newCount = flaggedCharges.filter(c => c.status === 'new').length; const totalDiscrepancy = flaggedCharges .filter(c => c.discrepancyAmount !== null) .reduce((sum, c) => sum + c.discrepancyAmount, 0); const unmatched = flaggedCharges.filter(c => !c.bigShipmentId).length; return { newCount, totalDiscrepancy, unmatched }; } render() { const { invoices, loading, loadingCharges, selectedInvoice, statusFilter, searchTrackingNumber, editModalOpen, editingCharge, editStatus, editNotes, uploading, uploadResult, reconcilingInvoiceId, lookupModalOpen, lookupTrackingNumber, lookupResult, lookupLoading, rawDataExpanded, summary, activeTab, clearing, clearConfirmOpen, clearResult, selectedInvoiceDetail } = this.state; const filteredCharges = this.getFilteredCharges(); const stats = this.calculateSummaryStats(); const selectedInvoiceData = invoices.find(inv => (inv.uniqueId || inv.id) === selectedInvoice); return (
DHL Charges Tracking Track and validate DHL shipping charges against expected costs
{/* Summary Statistics */} {summary ? summary.invoiceCount : '-'} Invoices {summary ? summary.totalChargeCount : '-'} Total Charges {summary ? this.formatCurrency(summary.totalValue) : '-'} Total Invoice Value {stats.newCount} New Flags {this.formatCurrency(stats.totalDiscrepancy)} Total Discrepancy {stats.unmatched} Unmatched {/* Invoices List */}
Invoices
this.csvFileInput = el} type="file" accept=".csv" style={{ display: 'none' }} onChange={(e) => { const file = e.target.files[0]; if (file) { this.uploadCSV(file); e.target.value = ''; } }} /> {uploadResult && ( this.setState({ uploadResult: null })} style={{ marginBottom: '1em' }} > {uploadResult.error ? 'Upload Failed' : 'CSV Uploaded'} {uploadResult.error ? (

{uploadResult.error}

) : (

{uploadResult.chargesCreated} charge(s) added{uploadResult.chargesUpdated > 0 ? `, ${uploadResult.chargesUpdated} replaced` : ''}. {(uploadResult.invoicesCreated > 0 || uploadResult.invoicesUpdated > 0) && ( {uploadResult.invoicesCreated > 0 && `${uploadResult.invoicesCreated} invoice(s) created. `} {uploadResult.invoicesUpdated > 0 && `${uploadResult.invoicesUpdated} invoice(s) updated.`} )} {uploadResult.errors && uploadResult.errors.length > 0 && ( <>
{uploadResult.errors.length} warning(s). )}

)}
)} this.setState({ clearConfirmOpen: false })} > Clear All DHL Data

This will permanently delete all DHL invoices, charges, and flags. This action cannot be undone.

Are you sure you want to continue?

{clearResult && ( this.setState({ clearResult: null })} style={{ marginBottom: '1em' }} > {clearResult.error ? 'Clear Failed' : 'Data Cleared'} {clearResult.error ? (

{clearResult.error}

) : (

Deleted {clearResult.invoicesDeleted} invoice(s), {clearResult.chargesDeleted} charge(s), {clearResult.flagsDeleted} flag(s).

)}
)} {loading ? ( ) : invoices.map(invoice => { const invId = invoice.uniqueId || invoice.id; return ( this.selectInvoice(invId)} > {invoice.invoiceNumber}
{this.formatDate(invoice.invoiceDate)}
{this.formatCurrency(invoice.totalAmount)}
{invoice.chargeCount} charges
{rawDataExpanded[invId] && ( e.stopPropagation()} >
                                                        {JSON.stringify(invoice, null, 4)}
                                                    
)}
); })}
{/* Filters */}
this.setState({ statusFilter: value })} /> this.setState({ searchTrackingNumber: e.target.value })} />
{/* Tabs when an invoice is selected */} {selectedInvoiceData && ( )} {/* Invoice Charges Table (only when invoice selected and charges tab active) */} {selectedInvoiceData && activeTab === 'charges' && (
Invoice Charges - {selectedInvoiceData.invoiceNumber}
{loadingCharges ? ( ) : ( <> Tracking # DHL Invoiced Label Price Discrepancy Currency DHL Service Label Provider Label Service Level Weight Shipment Status Shipment Fees {(selectedInvoiceDetail?.charges || []).map((charge, idx) => { const shipment = charge.shipment; const label = shipment?.currentShippingLabel; const quotedRate = charge.quotedRate; const discrepancy = charge.discrepancy; const chargeKey = charge.uniqueId || charge.id || idx; const isExpanded = this.state.expandedChargeIds[chargeKey]; const fb = charge.feeBreakdown; const hasFeeData = fb && typeof fb === 'object' && ( fb.weightCharge != null || fb.fuelSurcharge != null || (fb.extraCharges && fb.extraCharges.length > 0) || (fb.discounts && fb.discounts.length > 0) || (fb.otherCharges && fb.otherCharges.length > 0) ); return ( 0.01}> {charge.trackingNumber ? ( { e.preventDefault(); this.openLookupModal(charge.trackingNumber); }} > {charge.trackingNumber} ) : '-'} {this.formatCurrency(charge.amount, charge.currency)} {quotedRate != null ? this.formatCurrency(quotedRate, charge.currency) : '-'} {discrepancy != null ? ( ) : '-'} {charge.currency || '-'} {charge.serviceType || '-'} {label?.rate?.provider || '-'} {label?.rate?.servicelevel_name || '-'} {charge.weight || '-'} {shipment ? ( ) : '-'} {shipment ? ( View ) : ( No match )} {hasFeeData ? (
{(!selectedInvoiceDetail?.charges || selectedInvoiceDetail.charges.length === 0) && ( No charges found for this invoice. )} )}
)} {/* Flagged Charges Table (shown when no invoice selected, or flagged tab active) */} {(!selectedInvoiceData || activeTab === 'flagged') && (
Flagged Charges ({filteredCharges.length})
{loadingCharges ? ( ) : ( Tracking # Invoice Invoiced Expected Carrier Service Discrepancy Reason Status Actions {filteredCharges.map(charge => ( { e.preventDefault(); this.openLookupModal(charge.trackingNumber); }} > {charge.trackingNumber} {charge.bigShipmentId && ( )} {charge.invoiceNumber} {this.formatCurrency(charge.invoicedAmount)} {charge.expectedAmount ? this.formatCurrency(charge.expectedAmount) : } {charge.shipmentData?.provider || '-'} {charge.shipmentData?.servicelevelName || '-'} 50}> {charge.discrepancyAmount ? ( <> {this.formatCurrency(charge.discrepancyAmount)}
({charge.discrepancyPercent.toFixed(1)}%) ) : '-'}
{charge.flagReason} {charge.notes && (
Note: {charge.notes}
)}
)} {filteredCharges.length === 0 && !loadingCharges && ( No flagged charges match the current filters. )}
)}
{/* Edit Charge Modal */} this.setState({ editModalOpen: false })} size="small" > Edit Charge Flag {editingCharge && (
this.setState({ editStatus: value })} />