diff --git a/src/dealer-claim/components/request-detail/WorkflowTab.tsx b/src/dealer-claim/components/request-detail/WorkflowTab.tsx index df7fe09..7042024 100644 --- a/src/dealer-claim/components/request-detail/WorkflowTab.tsx +++ b/src/dealer-claim/components/request-detail/WorkflowTab.tsx @@ -10,7 +10,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Progress } from '@/components/ui/progress'; -import { TrendingUp, Clock, CheckCircle, CircleCheckBig, Upload, Mail, Download, Receipt, Activity, AlertTriangle, AlertOctagon, XCircle, History, ChevronDown, ChevronUp, RefreshCw, RotateCw, Eye, FileSpreadsheet } from 'lucide-react'; +import { TrendingUp, Clock, CheckCircle, CircleCheckBig, Upload, Mail, Download, Receipt, Activity, AlertTriangle, AlertOctagon, XCircle, History, ChevronDown, ChevronUp, RefreshCw, RotateCw, Eye, FileSpreadsheet, X, Loader2 } from 'lucide-react'; import { formatDateTime, formatDateDDMMYYYY } from '@/utils/dateFormatter'; import { formatHoursMinutes } from '@/utils/slaTracker'; import { @@ -189,6 +189,9 @@ export function DealerClaimWorkflowTab({ const [showHistory, setShowHistory] = useState(false); const [expandedVersionSteps, setExpandedVersionSteps] = useState>(new Set()); const [viewSnapshot, setViewSnapshot] = useState<{ data: any, type: 'PROPOSAL' | 'COMPLETION', title?: string } | null>(null); + const [invoicePdfUrl, setInvoicePdfUrl] = useState(null); + const [showInvoicePdfModal, setShowInvoicePdfModal] = useState(false); + const [invoicePdfLoading, setInvoicePdfLoading] = useState(false); // Load approval flows from real API const [approvalFlow, setApprovalFlow] = useState([]); @@ -1238,7 +1241,14 @@ export function DealerClaimWorkflowTab({ handleRefresh(); } catch (error: any) { console.error('[DealerClaimWorkflowTab] Error generating e-invoice:', error); - const errorMessage = error?.response?.data?.message || error?.message || 'Failed to generate e-invoice. Please try again.'; + // Backend now translates PWC error codes to user-friendly messages in 'message' field + // Prefer 'message' (user-friendly) over 'error' (raw technical details) + const responseMessage = error?.response?.data?.message; + let errorMessage = responseMessage || error?.message || 'E-Invoice generation failed. Please try again.'; + // Truncate very long error messages for toast display (keep first 300 chars) + if (errorMessage.length > 300) { + errorMessage = errorMessage.substring(0, 300) + '...'; + } toast.error(errorMessage); throw error; } @@ -1502,14 +1512,56 @@ export function DealerClaimWorkflowTab({ return; } - const token = TokenManager.getAccessToken(); - // Construct API URL for PDF preview - const previewUrl = `${import.meta.env.VITE_API_BASE_URL || 'http://localhost:5000/api/v1'}/dealer-claims/${requestId}/e-invoice/pdf?token=${token}`; + setInvoicePdfLoading(true); + setShowInvoicePdfModal(true); - window.open(previewUrl, '_blank'); + // Fetch PDF securely via Authorization header (not in URL query) + const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:5000/api/v1'; + const response = await fetch(`${baseUrl}/dealer-claims/${requestId}/e-invoice/pdf`, { + headers: { + 'Authorization': `Bearer ${TokenManager.getAccessToken()}` + } + }); + + if (!response.ok) { + throw new Error('Failed to fetch invoice PDF'); + } + + const blob = await response.blob(); + const blobUrl = window.URL.createObjectURL(blob); + + // Revoke previous blob URL to prevent memory leaks + if (invoicePdfUrl) { + window.URL.revokeObjectURL(invoicePdfUrl); + } + + setInvoicePdfUrl(blobUrl); } catch (error) { console.error('Failed to preview invoice:', error); - toast.error('Failed to open invoice preview'); + toast.error('Failed to load invoice preview'); + setShowInvoicePdfModal(false); + } finally { + setInvoicePdfLoading(false); + } + }; + + const handleCloseInvoicePdf = () => { + setShowInvoicePdfModal(false); + if (invoicePdfUrl) { + window.URL.revokeObjectURL(invoicePdfUrl); + setInvoicePdfUrl(null); + } + }; + + const handleDownloadInvoicePdf = () => { + if (invoicePdfUrl) { + const a = document.createElement('a'); + a.href = invoicePdfUrl; + a.download = `Invoice_${request.requestNumber || 'Download'}.pdf`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + toast.success('Invoice PDF downloaded'); } }; @@ -2435,6 +2487,7 @@ export function DealerClaimWorkflowTab({ requestId={request?.id || request?.requestId} previousProposalData={versionHistory?.find(v => v.snapshotType === 'PROPOSAL')?.snapshotData} documentPolicy={documentPolicy} + taxationType={request?.claimDetails?.taxationType} /> {/* Initiator Proposal Approval Modal */} @@ -2458,6 +2511,7 @@ export function DealerClaimWorkflowTab({ // proposalSnapshots[1] is the previous proposal (last iteration - 1) return proposalSnapshots.length > 1 ? proposalSnapshots[1].snapshotData : null; })()} + taxationType={request?.claimDetails?.taxationType} /> {/* Dept Lead IO Approval Modal */} @@ -2484,6 +2538,7 @@ export function DealerClaimWorkflowTab({ defaultGstRate={request?.claimDetails?.defaultGstRate} requestId={request?.id || request?.requestId} documentPolicy={documentPolicy} + taxationType={request?.claimDetails?.taxationType} /> {/* DMS Push Modal */} @@ -2818,6 +2873,65 @@ export function DealerClaimWorkflowTab({ type={viewSnapshot?.type || 'PROPOSAL'} title={viewSnapshot?.title} /> + + {/* Invoice PDF Viewer Modal */} + {showInvoicePdfModal && ( +
+ {/* Backdrop */} +
+ {/* Modal */} +
+ {/* Header */} +
+
+ +

Invoice Preview

+ {request.requestNumber} +
+
+ {invoicePdfUrl && ( + + )} + +
+
+ {/* Body */} +
+ {invoicePdfLoading ? ( +
+ +

Loading invoice...

+
+ ) : invoicePdfUrl ? ( +