fix: crash bugs in TeamInvoices + faster load
Bug fixes: - TeamInvoices: add useAuth import/currentUser (invoice-create crash) - TeamInvoices: setChartYear -> setExportYear (year-dropdown crash) - TeamDashboard: drop redundant setState-in-effect (cascading renders) - Companies: delete dead _UnusedClientCompanies (illegal hook calls) - annotate intentional empty PDF-fallback catches Load speed: - preconnect/dns-prefetch to Supabase origin - lazy-load heic-to in Converters: page chunk 2737KB -> 9KB - split recharts into its own 'charts' vendor chunk Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import SortTh from './SortTh';
|
||||
|
||||
function fmt(val) {
|
||||
return `$${Number(val || 0).toFixed(2)}`;
|
||||
}
|
||||
|
||||
function inferItemType(item) {
|
||||
const match = String(item?.description || '').match(/\bR(\d{2})\b/i);
|
||||
if (match) {
|
||||
return Number(match[1]) <= 0
|
||||
? { label: 'New', badgeClass: 'badge-initial' }
|
||||
: { label: 'Revision', badgeClass: 'badge-client_revision' };
|
||||
}
|
||||
if (item?.task_id) return { label: 'Task', badgeClass: 'badge-in_progress' };
|
||||
return { label: 'Other', badgeClass: 'badge-initial' };
|
||||
}
|
||||
|
||||
export default function SubcontractorInvoiceDetailView({
|
||||
error,
|
||||
headerActions,
|
||||
leftCardTitle,
|
||||
leftCardBody,
|
||||
rightCardTitle,
|
||||
rightCardBody,
|
||||
sortKey,
|
||||
sortDir,
|
||||
onSort,
|
||||
items,
|
||||
notes,
|
||||
total,
|
||||
}) {
|
||||
return (
|
||||
<div className="invoice-detail-shell">
|
||||
{error ? <div className="notification notification-info" style={{ marginBottom: 16, flexShrink: 0 }}>{error}</div> : null}
|
||||
|
||||
<div className="invoice-detail-stack">
|
||||
<div className="invoice-detail-summary-grid">
|
||||
<div className="card invoice-detail-card">
|
||||
<div className="invoice-detail-section-title">{leftCardTitle}</div>
|
||||
{leftCardBody}
|
||||
</div>
|
||||
<div className="card invoice-detail-card">
|
||||
<div className="invoice-detail-card-header">
|
||||
<div className="invoice-detail-section-title" style={{ marginBottom: 0 }}>{rightCardTitle}</div>
|
||||
{headerActions ? <div className="invoice-detail-card-actions">{headerActions}</div> : null}
|
||||
</div>
|
||||
{rightCardBody}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card invoice-detail-card invoice-detail-line-items-card">
|
||||
<div className="invoice-detail-section-title">Line Items</div>
|
||||
{items.length === 0 ? (
|
||||
<div className="card-empty-center" style={{ minHeight: 120 }}>No line items.</div>
|
||||
) : (
|
||||
<div className="scrollbar-thin-theme table-scroll-hidden" style={{ overflowY: 'auto' }}>
|
||||
<table className="table-sticky-head invoice-detail-table" style={{ width: '100%', borderCollapse: 'collapse', tableLayout: 'fixed' }}>
|
||||
<colgroup>
|
||||
<col style={{ width: '12%' }} />
|
||||
<col style={{ width: '46%' }} />
|
||||
<col style={{ width: '14%' }} />
|
||||
<col style={{ width: '14%' }} />
|
||||
<col style={{ width: '14%' }} />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<SortTh col="type" sortKey={sortKey} sortDir={sortDir} onSort={onSort}>Type</SortTh>
|
||||
<SortTh col="description" sortKey={sortKey} sortDir={sortDir} onSort={onSort}>Description</SortTh>
|
||||
<SortTh col="quantity" sortKey={sortKey} sortDir={sortDir} onSort={onSort} style={{ textAlign: 'center' }}>Qty</SortTh>
|
||||
<SortTh col="unit_price" sortKey={sortKey} sortDir={sortDir} onSort={onSort} style={{ textAlign: 'right' }}>Unit Price</SortTh>
|
||||
<SortTh col="line_total" sortKey={sortKey} sortDir={sortDir} onSort={onSort} style={{ textAlign: 'right' }}>Total</SortTh>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item) => {
|
||||
const itemType = inferItemType(item);
|
||||
return (
|
||||
<tr key={item.id}>
|
||||
<td style={{ padding: '5px 0' }}>
|
||||
<span className={`badge ${itemType.badgeClass}`}>{itemType.label}</span>
|
||||
</td>
|
||||
<td className="invoice-detail-cell">{item.description}</td>
|
||||
<td className="invoice-detail-cell" style={{ textAlign: 'center' }}>{item.quantity}</td>
|
||||
<td className="invoice-detail-cell" style={{ textAlign: 'right' }}>{fmt(item.unit_price)}</td>
|
||||
<td className="invoice-detail-cell" style={{ textAlign: 'right', fontWeight: 400 }}>
|
||||
{fmt(Number(item.unit_price || 0) * Number(item.quantity || 1))}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
<div className="invoice-detail-total-row">
|
||||
<div style={{ textAlign: 'right' }}>
|
||||
<div className="invoice-detail-total-label">Total</div>
|
||||
<div className="invoice-detail-total-value">{fmt(total)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{notes ? (
|
||||
<div className="card invoice-detail-card">
|
||||
<div className="invoice-detail-section-title">Notes</div>
|
||||
<p className="invoice-detail-notes">{notes}</p>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user