From d15f6bfb958acffd3a95bce27ba58e642ce0a323 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Fri, 22 May 2026 18:57:49 -0400 Subject: [PATCH] workspace: sticky panels, action bar polish, breadcrumb nav, line item UX improvements --- web/src/components/admin/LineItemEditor.tsx | 45 +++++- .../pages/admin/workspace/AdminWorkspace.tsx | 143 ++++++++++++------ 2 files changed, 138 insertions(+), 50 deletions(-) diff --git a/web/src/components/admin/LineItemEditor.tsx b/web/src/components/admin/LineItemEditor.tsx index 53535e6..42e0674 100644 --- a/web/src/components/admin/LineItemEditor.tsx +++ b/web/src/components/admin/LineItemEditor.tsx @@ -15,6 +15,7 @@ import { Tooltip, } from '@mui/material'; import AddIcon from '@mui/icons-material/Add'; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import DeleteIcon from '@mui/icons-material/Delete'; import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; @@ -88,6 +89,25 @@ export default function LineItemEditor({ items, onChange, disabled = false }: Li onChange(updated.map((item, i) => ({ ...item, sortOrder: i + 1 }))); }; + const duplicateItem = (index: number) => { + const source = items[index]; + if (!source) return; + const newItem: EditableLineItem = { + id: null, + description: source.description, + quantity: source.quantity, + unit: source.unit, + unitPrice: source.unitPrice, + totalPrice: source.totalPrice, + pricingMode: source.pricingMode, + sortOrder: 0, + source: 'Manual', + }; + const updated = [...items]; + updated.splice(index + 1, 0, newItem); + 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; @@ -109,8 +129,8 @@ export default function LineItemEditor({ items, onChange, disabled = false }: Li Line Items ({items.length}) {!disabled && ( - )} @@ -126,7 +146,7 @@ export default function LineItemEditor({ items, onChange, disabled = false }: Li Unit Price Total Source - {!disabled && Actions} + {!disabled && Actions} @@ -217,6 +237,11 @@ export default function LineItemEditor({ items, onChange, disabled = false }: Li {!disabled && ( + + duplicateItem(index)}> + + + removeItem(index)}> @@ -228,10 +253,16 @@ export default function LineItemEditor({ items, onChange, disabled = false }: Li ))} {items.length === 0 && ( - - - No line items yet. {!disabled && 'Click "Add Row" to start.'} + + No line items yet + + Add labor, material, equipment, or other cost items. + {!disabled && ( + + )} )} @@ -241,7 +272,7 @@ export default function LineItemEditor({ items, onChange, disabled = false }: Li - Total: {new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(subtotal)} + Proposal Total: {subtotal === 0 ? 'Pending pricing' : new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(subtotal)} diff --git a/web/src/pages/admin/workspace/AdminWorkspace.tsx b/web/src/pages/admin/workspace/AdminWorkspace.tsx index 5eff598..9a398bf 100644 --- a/web/src/pages/admin/workspace/AdminWorkspace.tsx +++ b/web/src/pages/admin/workspace/AdminWorkspace.tsx @@ -17,10 +17,13 @@ import { DialogActions, CircularProgress, Alert, + Tooltip, + Link, } from '@mui/material'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import SaveIcon from '@mui/icons-material/Save'; import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import NavigateNextIcon from '@mui/icons-material/NavigateNext'; import SendIcon from '@mui/icons-material/Send'; import RefreshIcon from '@mui/icons-material/Refresh'; import HistoryIcon from '@mui/icons-material/History'; @@ -59,6 +62,14 @@ export default function AdminWorkspace() { enabled: !!id, }); + const { data: similarProposals } = useQuery({ + queryKey: ['similarProposals', id], + queryFn: () => adminApi.getSimilar(id!), + enabled: !!id, + }); + + const hasSimilar = similarProposals && similarProposals.length > 0; + useEffect(() => { if (fetchedLineItems) { setLineItems( @@ -214,13 +225,21 @@ export default function AdminWorkspace() { return ( {/* Header */} - - - - {proposal.proposalNumber} - + + + navigate('/admin')} + sx={{ fontSize: 14, color: '#0B5A73', cursor: 'pointer' }} + > + Admin Queue + + + + {proposal.proposalNumber} + + @@ -255,7 +274,7 @@ export default function AdminWorkspace() { {/* Left Panel: Reference */} - + Submission Details @@ -290,7 +309,7 @@ export default function AdminWorkspace() { {/* Center Panel: Editor */} - + Refined Scope @@ -315,28 +334,30 @@ export default function AdminWorkspace() { - {/* Right Panel: Similar Proposals */} - - - - { - const maxSort = lineItems.length > 0 ? Math.max(...lineItems.map((i) => i.sortOrder)) : 0; - handleLineItemsChange([ - ...lineItems, - { ...item, id: null, sortOrder: maxSort + 1, source: 'Historical' }, - ]); - }} - disabled={!isEditable} - /> - - - + {/* Right Panel: Similar Proposals (hidden when empty) */} + {hasSimilar && ( + + + + { + const maxSort = lineItems.length > 0 ? Math.max(...lineItems.map((i) => i.sortOrder)) : 0; + handleLineItemsChange([ + ...lineItems, + { ...item, id: null, sortOrder: maxSort + 1, source: 'Historical' }, + ]); + }} + disabled={!isEditable} + /> + + + + )} {/* Action Bar */} - + {dirty && ( Unsaved changes @@ -353,23 +374,59 @@ export default function AdminWorkspace() { > Regenerate - - + + + 0 && bidTotal > 0} > - Approve - + + + + + 0 && bidTotal > 0} + > + + + + )}