mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-10-04 13:42:03 +00:00
Some checks failed
Deploy / Deploy to AWS (push) Has been cancelled
## Summary Implements Phase 3 of the AUDIT-2026-05-20 findings: - 29 FIX-severity items across API, web, infra, and lambdas - 7 accessibility NITs (aria-labels, document titles) - 4 code quality NITs (deduplication, constants extraction) Key changes: - API: N+1 fix, pagination clamping, idempotent transitions, upload confirm endpoint, revision TotalBidAmount carry-forward - Web: confirmation dialogs, currency formatting, error states, date range filters, document titles - Infra: S3 CORS lockdown, API Gateway throttling, AOSS network policy fix, CI concurrency - Lambdas: skip empty suggestions, remove status side-effect - Scripts: post-deploy health check ## Test plan - [x] tsc --noEmit (web + infra) - [x] dotnet build (api) - [x] ruff check + format (lambdas) - [x] Cross-review via orchestrator (no blockers) [skip deploy]
91 lines
2.9 KiB
TypeScript
91 lines
2.9 KiB
TypeScript
import { useQuery } from '@tanstack/react-query';
|
|
import { Box, Typography, Skeleton, Chip } from '@mui/material';
|
|
import apiClient from '../../lib/api/client';
|
|
import { formatCurrency } from '../../lib/format';
|
|
|
|
interface VendorProposal {
|
|
id: string;
|
|
vendorName: string;
|
|
fileName: string;
|
|
totalVendorCost: number;
|
|
processingStatus: string;
|
|
extractedData: {
|
|
lineItems?: Array<{
|
|
description: string;
|
|
quantity?: number;
|
|
unitPrice?: number;
|
|
total?: number;
|
|
}>;
|
|
} | null;
|
|
}
|
|
|
|
interface VendorDataPanelProps {
|
|
proposalId: string;
|
|
}
|
|
|
|
export default function VendorDataPanel({ proposalId }: VendorDataPanelProps) {
|
|
const { data: vendors, isLoading } = useQuery<VendorProposal[]>({
|
|
queryKey: ['vendorProposals', proposalId],
|
|
queryFn: async () => {
|
|
const res = await apiClient.get(`/proposals/${proposalId}/vendors`);
|
|
return res.data;
|
|
},
|
|
enabled: !!proposalId,
|
|
});
|
|
|
|
if (isLoading) {
|
|
return <Skeleton variant="rectangular" height={60} />;
|
|
}
|
|
|
|
if (!vendors || vendors.length === 0) {
|
|
return (
|
|
<Box>
|
|
<Typography variant="subtitle2" sx={{ mb: 0.5 }}>Vendor Data</Typography>
|
|
<Typography variant="body2" color="text.secondary">No vendor proposals attached</Typography>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Box>
|
|
<Typography variant="subtitle2" sx={{ mb: 1 }}>Vendor Data</Typography>
|
|
{vendors.map((vp) => (
|
|
<Box key={vp.id} sx={{ mb: 2 }}>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5 }}>
|
|
<Typography variant="body2" sx={{ fontWeight: 600 }}>{vp.vendorName}</Typography>
|
|
<Chip
|
|
label={vp.processingStatus}
|
|
size="small"
|
|
color={vp.processingStatus === 'Complete' ? 'success' : 'default'}
|
|
variant="outlined"
|
|
/>
|
|
</Box>
|
|
<Typography variant="caption" color="text.secondary">{vp.fileName}</Typography>
|
|
|
|
{vp.totalVendorCost > 0 && (
|
|
<Typography variant="body2" sx={{ mt: 0.5, fontWeight: 600 }}>
|
|
Vendor Total: {formatCurrency(vp.totalVendorCost)}
|
|
</Typography>
|
|
)}
|
|
|
|
{vp.extractedData?.lineItems && vp.extractedData.lineItems.length > 0 && (
|
|
<Box sx={{ mt: 1, pl: 1, borderLeft: 2, borderColor: 'divider' }}>
|
|
{vp.extractedData.lineItems.map((li, i) => (
|
|
<Box key={i} sx={{ mb: 0.5 }}>
|
|
<Typography variant="caption" sx={{ display: 'block' }}>
|
|
{li.description}
|
|
</Typography>
|
|
{li.total != null && (
|
|
<Typography variant="caption" color="text.secondary">
|
|
{formatCurrency(li.total)}
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
))}
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
))}
|
|
</Box>
|
|
);
|
|
}
|