import { Box, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, IconButton, MenuItem, Chip, Typography, Button, Tooltip, } from '@mui/material'; import AddIcon from '@mui/icons-material/Add'; import DeleteIcon from '@mui/icons-material/Delete'; import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; import type { PricingMode, LineItemSource } from '../../lib/api/lineItems'; export interface EditableLineItem { id: string | null; description: string; quantity: number; unit: string; unitPrice: number | null; totalPrice: number; pricingMode: PricingMode; sortOrder: number; source: LineItemSource; } interface LineItemEditorProps { items: EditableLineItem[]; onChange: (items: EditableLineItem[]) => void; disabled?: boolean; } const SOURCE_COLORS: Record = { AI: 'primary', Vendor: 'secondary', Manual: 'default', Historical: 'success', }; const UNITS = ['each', 'sq ft', 'linear ft', 'hours', 'days', 'lot', 'gallon', 'ton']; function createEmptyItem(sortOrder: number): EditableLineItem { return { id: null, description: '', quantity: 1, unit: 'each', unitPrice: 0, totalPrice: 0, pricingMode: 'UnitPrice', sortOrder, source: 'Manual', }; } export default function LineItemEditor({ items, onChange, disabled = false }: LineItemEditorProps) { const updateItem = (index: number, field: keyof EditableLineItem, value: unknown) => { const updated = [...items]; const current = updated[index]; if (!current) return; updated[index] = { ...current, [field]: value }; if (field === 'quantity' || field === 'unitPrice') { const item = updated[index]!; if (item.pricingMode === 'UnitPrice' || item.pricingMode === 'Both') { item.totalPrice = (item.quantity || 0) * (item.unitPrice || 0); } } onChange(updated); }; const addItem = () => { const maxSort = items.length > 0 ? Math.max(...items.map((i) => i.sortOrder)) : 0; onChange([...items, createEmptyItem(maxSort + 1)]); }; const removeItem = (index: number) => { const updated = items.filter((_, i) => i !== index); onChange(updated.map((item, i) => ({ ...item, sortOrder: i + 1 }))); }; const moveItem = (index: number, direction: -1 | 1) => { const targetIndex = index + direction; if (targetIndex < 0 || targetIndex >= items.length) return; const updated = [...items]; const a = updated[index]; const b = updated[targetIndex]; if (!a || !b) return; updated[index] = b; updated[targetIndex] = a; onChange(updated.map((item, i) => ({ ...item, sortOrder: i + 1 }))); }; const subtotal = items.reduce((sum, item) => sum + item.totalPrice, 0); return ( Line Items ({items.length}) {!disabled && ( )} # Description Qty Unit Unit Price Total Source {!disabled && Actions} {items.map((item, index) => ( {!disabled && ( moveItem(index, -1)} disabled={index === 0} aria-label={`Move item ${index + 1} up`}> {index + 1} moveItem(index, 1)} disabled={index === items.length - 1} aria-label={`Move item ${index + 1} down`}> )} {disabled && {index + 1}} updateItem(index, 'description', e.target.value)} disabled={disabled} variant="standard" /> updateItem(index, 'quantity', parseFloat(e.target.value) || 0)} disabled={disabled} variant="standard" slotProps={{ htmlInput: { min: 0, step: 0.01 } }} /> updateItem(index, 'unit', e.target.value)} disabled={disabled} variant="standard" fullWidth > {UNITS.map((u) => ( {u} ))} updateItem(index, 'unitPrice', parseFloat(e.target.value) || 0)} disabled={disabled || item.pricingMode === 'TotalPrice'} variant="standard" slotProps={{ htmlInput: { min: 0, step: 0.01 } }} /> updateItem(index, 'totalPrice', parseFloat(e.target.value) || 0)} disabled={disabled || item.pricingMode === 'UnitPrice'} variant="standard" slotProps={{ htmlInput: { min: 0, step: 0.01 } }} sx={{ fontWeight: 600 }} /> {!disabled && ( removeItem(index)}> )} ))} {items.length === 0 && ( No line items yet. {!disabled && 'Click "Add Row" to start.'} )}
Total: {new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(subtotal)}
); }