invoice elae changes done

This commit is contained in:
laxmanhalaki 2026-02-25 19:15:14 +05:30
parent 32a486d6f4
commit 170f9a1788
5 changed files with 143 additions and 8 deletions

View File

@ -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<Set<number>>(new Set());
const [viewSnapshot, setViewSnapshot] = useState<{ data: any, type: 'PROPOSAL' | 'COMPLETION', title?: string } | null>(null);
const [invoicePdfUrl, setInvoicePdfUrl] = useState<string | null>(null);
const [showInvoicePdfModal, setShowInvoicePdfModal] = useState(false);
const [invoicePdfLoading, setInvoicePdfLoading] = useState(false);
// Load approval flows from real API
const [approvalFlow, setApprovalFlow] = useState<any[]>([]);
@ -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 && (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div className="absolute inset-0 bg-black/60" onClick={handleCloseInvoicePdf} />
{/* Modal */}
<div className="relative w-[95vw] max-w-5xl h-[90vh] bg-white rounded-xl shadow-2xl flex flex-col overflow-hidden">
{/* Header */}
<div className="flex items-center justify-between px-5 py-3 border-b bg-gray-50">
<div className="flex items-center gap-2">
<Receipt className="w-5 h-5 text-amber-600" />
<h3 className="font-semibold text-gray-900">Invoice Preview</h3>
<Badge className="bg-amber-100 text-amber-800 text-xs">{request.requestNumber}</Badge>
</div>
<div className="flex items-center gap-2">
{invoicePdfUrl && (
<Button
variant="outline"
size="sm"
onClick={handleDownloadInvoicePdf}
className="gap-1.5 text-xs"
>
<Download className="w-3.5 h-3.5" />
Download
</Button>
)}
<Button
variant="ghost"
size="icon"
onClick={handleCloseInvoicePdf}
className="h-8 w-8 hover:bg-gray-200"
>
<X className="w-4 h-4" />
</Button>
</div>
</div>
{/* Body */}
<div className="flex-1 overflow-hidden">
{invoicePdfLoading ? (
<div className="flex flex-col items-center justify-center h-full gap-3">
<Loader2 className="w-8 h-8 text-amber-600 animate-spin" />
<p className="text-sm text-gray-500">Loading invoice...</p>
</div>
) : invoicePdfUrl ? (
<iframe
src={invoicePdfUrl}
className="w-full h-full border-0"
title="Invoice PDF Preview"
/>
) : (
<div className="flex items-center justify-center h-full">
<p className="text-sm text-gray-500">Failed to load invoice</p>
</div>
)}
</div>
</div>
</div>
)}
</>
);
}

View File

@ -72,6 +72,7 @@ interface DealerCompletionDocumentsModalProps {
maxFileSizeMB: number;
allowedFileTypes: string[];
};
taxationType?: string | null;
}
export function DealerCompletionDocumentsModal({
@ -84,6 +85,7 @@ export function DealerCompletionDocumentsModal({
requestId: _requestId,
defaultGstRate = 18,
documentPolicy,
taxationType,
}: DealerCompletionDocumentsModalProps) {
const [activityCompletionDate, setActivityCompletionDate] = useState('');
const [numberOfParticipants, setNumberOfParticipants] = useState('');
@ -618,6 +620,11 @@ export function DealerCompletionDocumentsModal({
<Upload className="w-5 h-5 sm:w-6 sm:h-6 text-[--re-green]" />
Activity Completion Documents
</DialogTitle>
{taxationType && (
<Badge className={`mt-1 mb-1 w-fit ${taxationType === 'GST' ? 'bg-[#2d4a3e] text-white hover:bg-[#2d4a3e]/90' : 'bg-slate-200 text-slate-800 hover:bg-slate-200/90'}`}>
{taxationType === 'GST' ? 'GST Claim' : 'Non-GST Claim'}
</Badge>
)}
<DialogDescription className="text-sm sm:text-base">
Step 5: Upload completion proof and final documents
</DialogDescription>

View File

@ -72,6 +72,7 @@ interface DealerProposalSubmissionModalProps {
maxFileSizeMB: number;
allowedFileTypes: string[];
};
taxationType?: string | null;
}
export function DealerProposalSubmissionModal({
@ -85,6 +86,7 @@ export function DealerProposalSubmissionModal({
previousProposalData,
defaultGstRate = 18,
documentPolicy,
taxationType,
}: DealerProposalSubmissionModalProps) {
const [proposalDocument, setProposalDocument] = useState<File | null>(null);
@ -548,6 +550,11 @@ export function DealerProposalSubmissionModal({
<Upload className="w-5 h-5 lg:w-6 lg:h-6 text-[--re-green]" />
Dealer Proposal Submission
</DialogTitle>
{taxationType && (
<Badge className={`mt-1 mb-1 w-fit ${taxationType === 'GST' ? 'bg-[#2d4a3e] text-white hover:bg-[#2d4a3e]/90' : 'bg-slate-200 text-slate-800 hover:bg-slate-200/90'}`}>
{taxationType === 'GST' ? 'GST Claim' : 'Non-GST Claim'}
</Badge>
)}
<DialogDescription className="text-sm lg:text-base">
Step 1: Upload proposal and planning details
</DialogDescription>

View File

@ -71,6 +71,7 @@ interface InitiatorProposalApprovalModalProps {
requestId?: string;
request?: any; // Request object to check IO blocking status
previousProposalData?: any;
taxationType?: string | null;
}
export function InitiatorProposalApprovalModal({
@ -85,6 +86,7 @@ export function InitiatorProposalApprovalModal({
requestId: _requestId, // Prefix with _ to indicate intentionally unused
request,
previousProposalData,
taxationType,
}: InitiatorProposalApprovalModalProps) {
const [comments, setComments] = useState('');
const [submitting, setSubmitting] = useState(false);
@ -285,6 +287,11 @@ export function InitiatorProposalApprovalModal({
<DialogDescription className="text-xs lg:text-sm">
Step 2: Review dealer proposal and make a decision
</DialogDescription>
{taxationType && (
<Badge className={`mt-1 mb-1 w-fit ${taxationType === 'GST' ? 'bg-[#2d4a3e] text-white hover:bg-[#2d4a3e]/90' : 'bg-slate-200 text-slate-800 hover:bg-slate-200/90'}`}>
{taxationType === 'GST' ? 'GST Claim' : 'Non-GST Claim'}
</Badge>
)}
<div className="space-y-1 mt-2 text-xs text-gray-600">
<div className="flex flex-wrap gap-x-4 gap-y-1">
<div>

View File

@ -135,7 +135,7 @@ export const bulkImportHolidays = async (holidays: Partial<Holiday>[]): Promise<
};
/**
* Get all activity types (public endpoint - no auth required)
* Get all active activity types (requires authentication)
*/
export const getActivityTypes = async (): Promise<ActivityType[]> => {
const response = await apiClient.get('/config/activity-types');