From dfe94555ab578dda7fdc3ab3dceadc74cf49d2c7 Mon Sep 17 00:00:00 2001 From: laxmanhalaki Date: Fri, 20 Feb 2026 20:41:30 +0530 Subject: [PATCH] cost item ui enhanced and export csv feture added for ivoice line items and validation added for gst inputs based on inter-state and intra-state --- .../components/request-detail/WorkflowTab.tsx | 59 +- .../claim-cards/ActivityInformationCard.tsx | 10 +- .../claim-cards/ProposalDetailsCard.tsx | 14 +- .../modals/DealerCompletionDocumentsModal.tsx | 238 +++-- .../modals/DealerProposalSubmissionModal.tsx | 831 ++++++++---------- .../modals/InitiatorProposalApprovalModal.tsx | 8 +- src/utils/gstUtils.ts | 122 +++ 7 files changed, 749 insertions(+), 533 deletions(-) create mode 100644 src/utils/gstUtils.ts diff --git a/src/dealer-claim/components/request-detail/WorkflowTab.tsx b/src/dealer-claim/components/request-detail/WorkflowTab.tsx index 74223f9..df7fe09 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 } from 'lucide-react'; +import { TrendingUp, Clock, CheckCircle, CircleCheckBig, Upload, Mail, Download, Receipt, Activity, AlertTriangle, AlertOctagon, XCircle, History, ChevronDown, ChevronUp, RefreshCw, RotateCw, Eye, FileSpreadsheet } from 'lucide-react'; import { formatDateTime, formatDateDDMMYYYY } from '@/utils/dateFormatter'; import { formatHoursMinutes } from '@/utils/slaTracker'; import { @@ -1453,6 +1453,41 @@ export function DealerClaimWorkflowTab({ loadCompletionDocuments(); }, [request]); + const handleDownloadCSV = async () => { + try { + const requestId = request.id || request.requestId; + if (!requestId) { + toast.error('Request ID not found'); + return; + } + + const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:5000/api/v1'; + const response = await fetch(`${baseUrl}/dealer-claims/${requestId}/e-invoice/csv`, { + headers: { + 'Authorization': `Bearer ${TokenManager.getAccessToken()}` + } + }); + + if (!response.ok) { + throw new Error('Failed to download CSV'); + } + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `Invoice_${request.requestNumber || 'Export'}.csv`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + toast.success('CSV downloaded successfully'); + } catch (error) { + console.error('Error downloading CSV:', error); + toast.error('Failed to download CSV'); + } + }; + const handlePreviewInvoice = async () => { try { const requestId = request.id || request.requestId; @@ -1482,6 +1517,9 @@ export function DealerClaimWorkflowTab({ const dealerName = request?.claimDetails?.dealerName || request?.dealerInfo?.name || 'Dealer'; + const dealerGSTIN = request?.claimDetails?.dealerGstin || + request?.dealerInfo?.gstin || + request?.dealerInfo?.dealerGSTIN; const activityName = request?.claimDetails?.activityName || request?.activityInfo?.activityName || request?.title || @@ -1627,6 +1665,23 @@ export function DealerClaimWorkflowTab({ ); })()} + {/* CSV Export Button (Requestor Claim Approval) */} + {(() => { + const isRequestorClaimStep = (step.levelName || step.title || '').toLowerCase().includes('requestor claim') || + (step.levelName || step.title || '').toLowerCase().includes('requestor - claim'); + const hasInvoice = request?.invoice || (request?.irn && step.status === 'approved'); + return isRequestorClaimStep && hasInvoice && ( + + ); + })()}

{step.approver}

{step.description}

@@ -2374,6 +2429,7 @@ export function DealerClaimWorkflowTab({ onClose={() => setShowProposalModal(false)} onSubmit={handleProposalSubmit} dealerName={dealerName} + dealerGSTIN={dealerGSTIN} activityName={activityName} defaultGstRate={request?.claimDetails?.defaultGstRate} requestId={request?.id || request?.requestId} @@ -2423,6 +2479,7 @@ export function DealerClaimWorkflowTab({ onClose={() => setShowCompletionModal(false)} onSubmit={handleCompletionSubmit} dealerName={dealerName} + dealerGSTIN={dealerGSTIN} activityName={activityName} defaultGstRate={request?.claimDetails?.defaultGstRate} requestId={request?.id || request?.requestId} diff --git a/src/dealer-claim/components/request-detail/claim-cards/ActivityInformationCard.tsx b/src/dealer-claim/components/request-detail/claim-cards/ActivityInformationCard.tsx index a3a40c1..a50629e 100644 --- a/src/dealer-claim/components/request-detail/claim-cards/ActivityInformationCard.tsx +++ b/src/dealer-claim/components/request-detail/claim-cards/ActivityInformationCard.tsx @@ -125,7 +125,7 @@ export function ActivityInformationCard({ {formatCurrency( activityInfo.closedExpensesBreakdown && activityInfo.closedExpensesBreakdown.length > 0 - ? activityInfo.closedExpensesBreakdown.reduce((sum, item: any) => sum + (item.totalAmt || ((item.amount * (item.quantity || 1)) + (item.gstAmt || 0))), 0) + ? activityInfo.closedExpensesBreakdown.reduce((sum, item: any) => sum + (item.totalAmt || (Number(item.amount) + Number(item.gstAmt || 0))), 0) : activityInfo.closedExpenses )}

@@ -156,7 +156,6 @@ export function ActivityInformationCard({ Description - Qty Base GST Total @@ -169,19 +168,18 @@ export function ActivityInformationCard({ {item.description} {item.gstRate ? {item.gstRate}% GST : null} - {item.quantity || 1} {formatCurrency(item.amount)} {formatCurrency(item.gstAmt || 0)} - {formatCurrency(item.totalAmt || ((item.amount * (item.quantity || 1)) + (item.gstAmt || 0)))} + {formatCurrency(item.totalAmt || (Number(item.amount) + Number(item.gstAmt || 0)))} ))} - Final Claim Amount + Final Claim Amount {formatCurrency( - activityInfo.closedExpensesBreakdown.reduce((sum: number, item: any) => sum + (item.totalAmt || ((item.amount * (item.quantity || 1)) + (item.gstAmt || 0))), 0) + activityInfo.closedExpensesBreakdown.reduce((sum: number, item: any) => sum + (item.totalAmt || (Number(item.amount) + Number(item.gstAmt || 0))), 0) )} diff --git a/src/dealer-claim/components/request-detail/claim-cards/ProposalDetailsCard.tsx b/src/dealer-claim/components/request-detail/claim-cards/ProposalDetailsCard.tsx index 80464fa..e18c721 100644 --- a/src/dealer-claim/components/request-detail/claim-cards/ProposalDetailsCard.tsx +++ b/src/dealer-claim/components/request-detail/claim-cards/ProposalDetailsCard.tsx @@ -46,10 +46,8 @@ export function ProposalDetailsCard({ proposalDetails, className }: ProposalDeta if (proposalDetails.costBreakup && proposalDetails.costBreakup.length > 0) { const total = proposalDetails.costBreakup.reduce((sum, item) => { const amount = item.amount || 0; - const quantity = item.quantity || 1; - const baseTotal = amount * quantity; const gst = item.gstAmt || 0; - const lineTotal = item.totalAmt || (baseTotal + gst); + const lineTotal = item.totalAmt || (Number(amount) + Number(gst)); return sum + (Number.isNaN(lineTotal) ? 0 : lineTotal); }, 0); return total; @@ -111,9 +109,6 @@ export function ProposalDetailsCard({ proposalDetails, className }: ProposalDeta Item Description - - Qty - Base Amount @@ -136,9 +131,6 @@ export function ProposalDetailsCard({ proposalDetails, className }: ProposalDeta ) : null} - - {item.quantity || 1} - {formatCurrency(item.amount)} @@ -146,12 +138,12 @@ export function ProposalDetailsCard({ proposalDetails, className }: ProposalDeta {formatCurrency(item.gstAmt)} - {formatCurrency(item.totalAmt || ((item.amount || 0) * (item.quantity || 1)) + (item.gstAmt || 0))} + {formatCurrency(item.totalAmt || (Number(item.amount || 0) + Number(item.gstAmt || 0)))} ))} - + Estimated Budget (Total Inclusive of GST) diff --git a/src/dealer-claim/components/request-detail/modals/DealerCompletionDocumentsModal.tsx b/src/dealer-claim/components/request-detail/modals/DealerCompletionDocumentsModal.tsx index 0d24b34..dcaa94f 100644 --- a/src/dealer-claim/components/request-detail/modals/DealerCompletionDocumentsModal.tsx +++ b/src/dealer-claim/components/request-detail/modals/DealerCompletionDocumentsModal.tsx @@ -24,6 +24,7 @@ import { toast } from 'sonner'; import '@/components/common/FilePreview/FilePreview.css'; import './DealerCompletionDocumentsModal.css'; import { validateHSNSAC } from '@/utils/validationUtils'; +import { getStateCodeFromGSTIN, getActiveTaxComponents } from '@/utils/gstUtils'; interface ExpenseItem { id: string; @@ -62,6 +63,7 @@ interface DealerCompletionDocumentsModalProps { completionDescription: string; }) => Promise; dealerName?: string; + dealerGSTIN?: string; activityName?: string; requestId?: string; defaultGstRate?: number; @@ -76,6 +78,7 @@ export function DealerCompletionDocumentsModal({ onClose, onSubmit, dealerName = 'Jaipur Royal Enfield', + dealerGSTIN, activityName = 'Activity', requestId: _requestId, defaultGstRate = 18, @@ -83,6 +86,13 @@ export function DealerCompletionDocumentsModal({ }: DealerCompletionDocumentsModalProps) { const [activityCompletionDate, setActivityCompletionDate] = useState(''); const [numberOfParticipants, setNumberOfParticipants] = useState(''); + + // Determine active tax components based on dealer GSTIN + const taxConfig = useMemo(() => { + const stateCode = getStateCodeFromGSTIN(dealerGSTIN); + return getActiveTaxComponents(stateCode); + }, [dealerGSTIN]); + const [expenseItems, setExpenseItems] = useState([]); const [completionDocuments, setCompletionDocuments] = useState([]); const [activityPhotos, setActivityPhotos] = useState([]); @@ -181,36 +191,28 @@ export function DealerCompletionDocumentsModal({ }, [expenseItems]); // GST Calculation Helper - const calculateGST = (amount: number, rate: number, quantity: number = 1, type: 'IGST' | 'CGST_SGST' = 'CGST_SGST') => { + const calculateGST = (amount: number, rates: { cgstRate: number; sgstRate: number; igstRate: number; utgstRate: number }, quantity: number = 1) => { const baseTotal = amount * quantity; - const gstAmt = (baseTotal * rate) / 100; + const cgstAmt = (baseTotal * (rates.cgstRate || 0)) / 100; + const sgstAmt = (baseTotal * (rates.sgstRate || 0)) / 100; + const utgstAmt = (baseTotal * (rates.utgstRate || 0)) / 100; + const igstAmt = (baseTotal * (rates.igstRate || 0)) / 100; + const gstAmt = cgstAmt + sgstAmt + utgstAmt + igstAmt; const totalAmt = baseTotal + gstAmt; - if (type === 'IGST') { - return { - gstAmt, - igstRate: rate, - igstAmt: gstAmt, - cgstRate: 0, - cgstAmt: 0, - sgstRate: 0, - sgstAmt: 0, - totalAmt - }; - } else { - const halfRate = rate / 2; - const halfAmt = gstAmt / 2; - return { - gstAmt, - igstRate: 0, - igstAmt: 0, - cgstRate: halfRate, - cgstAmt: halfAmt, - sgstRate: halfRate, - sgstAmt: halfAmt, - totalAmt - }; - } + return { + cgstRate: rates.cgstRate, + cgstAmt, + sgstRate: rates.sgstRate, + sgstAmt, + utgstRate: rates.utgstRate, + utgstAmt, + igstRate: rates.igstRate, + igstAmt, + gstAmt, + gstRate: (rates.cgstRate || 0) + (rates.sgstRate || 0) + (rates.utgstRate || 0) + (rates.igstRate || 0), + totalAmt + }; }; // Check if all required fields are filled @@ -262,21 +264,75 @@ export function DealerCompletionDocumentsModal({ setExpenseItems( expenseItems.map((item) => { if (item.id === id) { - const updatedItem = { ...item, [field]: value }; + let updatedItem = { ...item, [field]: value }; - // Re-calculate GST if amount or rate changes - if (field === 'amount' || field === 'gstRate') { + // Re-calculate GST if relevant fields change + if (['amount', 'gstRate', 'cgstRate', 'sgstRate', 'utgstRate', 'igstRate', 'quantity'].includes(field)) { const amount = field === 'amount' ? parseFloat(value) || 0 : item.amount; - const rate = field === 'gstRate' ? parseFloat(value) || 0 : item.gstRate; - const quantity = 1; - const gst = calculateGST(amount, rate, quantity); + const quantity = field === 'quantity' ? parseInt(value) || 1 : item.quantity; + + let cgstRate = item.cgstRate; + let sgstRate = item.sgstRate; + let utgstRate = item.utgstRate; + let igstRate = item.igstRate; + + if (field === 'cgstRate') { + if (!taxConfig.isCGST) return item; + cgstRate = parseFloat(value) || 0; + // If UTGST is active for this dealer, sync with it + if (taxConfig.isUTGST) { + utgstRate = cgstRate; + sgstRate = 0; + } else { + sgstRate = cgstRate; + utgstRate = 0; + } + igstRate = 0; + } else if (field === 'sgstRate') { + if (!taxConfig.isSGST) return item; + sgstRate = parseFloat(value) || 0; + cgstRate = sgstRate; + utgstRate = 0; + igstRate = 0; + } else if (field === 'utgstRate') { + if (!taxConfig.isUTGST) return item; + utgstRate = parseFloat(value) || 0; + cgstRate = utgstRate; + sgstRate = 0; + igstRate = 0; + } else if (field === 'igstRate') { + if (!taxConfig.isIGST) return item; + igstRate = parseFloat(value) || 0; + cgstRate = 0; + sgstRate = 0; + utgstRate = 0; + } else if (field === 'gstRate') { + const totalRate = parseFloat(value) || 0; + if (taxConfig.isIGST) { + igstRate = totalRate; + cgstRate = 0; + sgstRate = 0; + utgstRate = 0; + } else { + cgstRate = totalRate / 2; + if (taxConfig.isUTGST) { + utgstRate = totalRate / 2; + sgstRate = 0; + } else { + sgstRate = totalRate / 2; + utgstRate = 0; + } + igstRate = 0; + } + } + + const calculation = calculateGST(amount, { cgstRate, sgstRate, igstRate, utgstRate }, quantity); return { ...updatedItem, amount, - gstRate: rate, quantity, - ...gst + ...calculation }; } @@ -576,7 +632,7 @@ export function DealerCompletionDocumentsModal({
-
+
{/* Activity Completion Date */}
+
+ Tax fields are automatically toggled based on the dealer's state (Inter-state vs Intra-state). +
-
- +
+ - handleExpenseChange(item.id, 'gstRate', e.target.value) + handleExpenseChange(item.id, 'cgstRate', e.target.value) } - className="w-full bg-white text-sm" + disabled={!taxConfig.isCGST} + className="w-full bg-white text-xs px-1 text-center disabled:bg-gray-100 disabled:text-gray-400" + /> +
+
+ + + handleExpenseChange(item.id, 'sgstRate', e.target.value) + } + disabled={!taxConfig.isSGST} + className="w-full bg-white text-xs px-1 text-center disabled:bg-gray-100 disabled:text-gray-400" + /> +
+
+ + + handleExpenseChange(item.id, 'utgstRate', e.target.value) + } + disabled={!taxConfig.isUTGST} + className="w-full bg-white text-xs px-1 text-center disabled:bg-gray-100 disabled:text-gray-400" + /> +
+
+ + + handleExpenseChange(item.id, 'igstRate', e.target.value) + } + disabled={!taxConfig.isIGST} + className="w-full bg-white text-xs px-1 text-center disabled:bg-gray-100 disabled:text-gray-400" />
- {item.gstAmt > 0 && ( -
-
- CGST ({item.cgstRate}%) - ₹{item.cgstAmt.toLocaleString('en-IN')} -
-
- SGST ({item.sgstRate}%) - ₹{item.sgstAmt.toLocaleString('en-IN')} -
-
- GST Total - ₹{item.gstAmt.toLocaleString('en-IN')} -
-
- Item Total - ₹{item.totalAmt.toLocaleString('en-IN')} -
+
+
+ CGST + ₹{item.cgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })}
- )} + {item.sgstAmt > 0 && ( +
+ SGST + ₹{item.sgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })} +
+ )} + {item.utgstAmt > 0 && ( +
+ UTGST + ₹{item.utgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })} +
+ )} +
+ IGST + ₹{item.igstAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })} +
+
+ GST Total + ₹{item.gstAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })} +
+
+ Item Total + ₹{item.totalAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })} +
+
))}
diff --git a/src/dealer-claim/components/request-detail/modals/DealerProposalSubmissionModal.tsx b/src/dealer-claim/components/request-detail/modals/DealerProposalSubmissionModal.tsx index 27f610b..b0cca76 100644 --- a/src/dealer-claim/components/request-detail/modals/DealerProposalSubmissionModal.tsx +++ b/src/dealer-claim/components/request-detail/modals/DealerProposalSubmissionModal.tsx @@ -27,6 +27,7 @@ import { getDocumentPreviewUrl, downloadDocument } from '@/services/workflowApi' import '@/components/common/FilePreview/FilePreview.css'; import './DealerProposalModal.css'; import { validateHSNSAC } from '@/utils/validationUtils'; +import { getStateCodeFromGSTIN, getActiveTaxComponents } from '@/utils/gstUtils'; interface CostItem { id: string; @@ -61,6 +62,7 @@ interface DealerProposalSubmissionModalProps { dealerComments: string; }) => Promise; dealerName?: string; + dealerGSTIN?: string; activityName?: string; requestId?: string; previousProposalData?: any; @@ -76,6 +78,7 @@ export function DealerProposalSubmissionModal({ onClose, onSubmit, dealerName = 'Jaipur Royal Enfield', + dealerGSTIN, activityName = 'Activity', requestId: _requestId, previousProposalData, @@ -83,6 +86,13 @@ export function DealerProposalSubmissionModal({ documentPolicy, }: DealerProposalSubmissionModalProps) { const [proposalDocument, setProposalDocument] = useState(null); + + // Determine active tax components based on dealer GSTIN + const taxConfig = useMemo(() => { + const stateCode = getStateCodeFromGSTIN(dealerGSTIN); + return getActiveTaxComponents(stateCode); + }, [dealerGSTIN]); + const [costItems, setCostItems] = useState([ { id: '1', @@ -108,36 +118,28 @@ export function DealerProposalSubmissionModal({ ]); // GST Calculation Helper - const calculateGST = (amount: number, rate: number, quantity: number = 1, type: 'IGST' | 'CGST_SGST' = 'CGST_SGST') => { + const calculateGST = (amount: number, rates: { cgstRate: number; sgstRate: number; igstRate: number; utgstRate: number }, quantity: number = 1) => { const baseTotal = amount * quantity; - const gstAmt = (baseTotal * rate) / 100; + const cgstAmt = (baseTotal * (rates.cgstRate || 0)) / 100; + const sgstAmt = (baseTotal * (rates.sgstRate || 0)) / 100; + const utgstAmt = (baseTotal * (rates.utgstRate || 0)) / 100; + const igstAmt = (baseTotal * (rates.igstRate || 0)) / 100; + const gstAmt = cgstAmt + sgstAmt + utgstAmt + igstAmt; const totalAmt = baseTotal + gstAmt; - if (type === 'IGST') { - return { - gstAmt, - igstRate: rate, - igstAmt: gstAmt, - cgstRate: 0, - cgstAmt: 0, - sgstRate: 0, - sgstAmt: 0, - totalAmt - }; - } else { - const halfRate = rate / 2; - const halfAmt = gstAmt / 2; - return { - gstAmt, - igstRate: 0, - igstAmt: 0, - cgstRate: halfRate, - cgstAmt: halfAmt, - sgstRate: halfRate, - sgstAmt: halfAmt, - totalAmt - }; - } + return { + cgstRate: rates.cgstRate, + cgstAmt, + sgstRate: rates.sgstRate, + sgstAmt, + utgstRate: rates.utgstRate, + utgstAmt, + igstRate: rates.igstRate, + igstAmt, + gstAmt, + gstRate: (rates.cgstRate || 0) + (rates.sgstRate || 0) + (rates.utgstRate || 0) + (rates.igstRate || 0), + totalAmt + }; }; const [timelineMode, setTimelineMode] = useState<'date' | 'days'>('date'); const [expectedCompletionDate, setExpectedCompletionDate] = useState(''); @@ -356,21 +358,75 @@ export function DealerProposalSubmissionModal({ setCostItems(prev => prev.map(item => { if (item.id === id) { - const updatedItem = { ...item, [field]: value }; + let updatedItem = { ...item, [field]: value }; - // Re-calculate GST if amount or rate changes - if (field === 'amount' || field === 'gstRate') { + // Re-calculate GST if relevant fields change + if (['amount', 'gstRate', 'cgstRate', 'sgstRate', 'utgstRate', 'igstRate', 'quantity'].includes(field)) { const amount = field === 'amount' ? parseFloat(value) || 0 : item.amount; - const rate = field === 'gstRate' ? parseFloat(value) || 0 : item.gstRate; - const quantity = 1; - const gst = calculateGST(amount, rate, quantity); + const quantity = field === 'quantity' ? parseInt(value) || 1 : item.quantity; + + let cgstRate = item.cgstRate; + let sgstRate = item.sgstRate; + let utgstRate = item.utgstRate; + let igstRate = item.igstRate; + + if (field === 'cgstRate') { + if (!taxConfig.isCGST) return item; + cgstRate = parseFloat(value) || 0; + // If UTGST is active for this dealer, sync with it + if (taxConfig.isUTGST) { + utgstRate = cgstRate; + sgstRate = 0; + } else { + sgstRate = cgstRate; + utgstRate = 0; + } + igstRate = 0; + } else if (field === 'sgstRate') { + if (!taxConfig.isSGST) return item; + sgstRate = parseFloat(value) || 0; + cgstRate = sgstRate; + utgstRate = 0; + igstRate = 0; + } else if (field === 'utgstRate') { + if (!taxConfig.isUTGST) return item; + utgstRate = parseFloat(value) || 0; + cgstRate = utgstRate; + sgstRate = 0; + igstRate = 0; + } else if (field === 'igstRate') { + if (!taxConfig.isIGST) return item; + igstRate = parseFloat(value) || 0; + cgstRate = 0; + sgstRate = 0; + utgstRate = 0; + } else if (field === 'gstRate') { + const totalRate = parseFloat(value) || 0; + if (taxConfig.isIGST) { + igstRate = totalRate; + cgstRate = 0; + sgstRate = 0; + utgstRate = 0; + } else { + cgstRate = totalRate / 2; + if (taxConfig.isUTGST) { + utgstRate = totalRate / 2; + sgstRate = 0; + } else { + sgstRate = totalRate / 2; + utgstRate = 0; + } + igstRate = 0; + } + } + + const calculation = calculateGST(amount, { cgstRate, sgstRate, igstRate, utgstRate }, quantity); return { ...updatedItem, amount, - gstRate: rate, quantity, - ...gst + ...calculation }; } @@ -662,448 +718,325 @@ export function DealerProposalSubmissionModal({
)} -
- {/* Left Column - Documents */} -
- {/* Proposal Document Section */} -
-
-

Proposal Document

- Required -
-
- -

- Detailed proposal with activity details and requested information -

-
- documentPolicy.allowedFileTypes.includes(ext.replace('.', ''))).join(',')} - className="hidden" - id="proposalDoc" - onChange={handleProposalDocChange} - /> - -
-
+
+ {/* 1. Proposal Document - Column 1 */} +
+
+

Proposal Document

+ Required
- - {/* Other Supporting Documents Section */} -
-
-

Other Supporting Documents

- Optional -
-
- -

- Any other supporting documents (invoices, receipts, photos, etc.) -

-
0 - ? 'border-blue-500 bg-blue-50 hover:border-blue-600' - : 'border-gray-300 hover:border-blue-500 bg-white' - }`} - > - `.${ext}`).join(',')} - className="hidden" - id="otherDocs" - onChange={handleOtherDocsChange} - /> - -
- {otherDocuments.length > 0 && ( -
-

- Selected Documents ({otherDocuments.length}): -

- {otherDocuments.map((file, index) => ( -
-
- - - {file.name} - -
-
- {canPreviewFile(file) && ( - - )} - - -
-
- ))} +
+ documentPolicy.allowedFileTypes.includes(ext.replace('.', ''))).join(',')} + className="hidden" + id="proposalDoc" + onChange={handleProposalDocChange} + /> +
+
- {/* Right Column - Planning & Budget */} -
- {/* Cost Breakup Section */} -
-
-
-

Cost Breakup

- Required -
+ {/* 2. Timeline for Closure - Column 2 */} +
+
+

Timeline for Closure

+ Required +
+
+
+
-
- {costItems.map((item) => ( -
-
- {/* Row 1: Description and Close Button */} -
-
- - - handleCostItemChange(item.id, 'description', e.target.value) - } - className="w-full bg-white" - /> -
- -
+ {timelineMode === 'date' ? ( + setExpectedCompletionDate(date || '')} + minDate={minDate} + placeholderText="dd/mm/yyyy" + className="w-full" + /> + ) : ( + setNumberOfDays(e.target.value)} + className="h-10 w-full" + /> + )} +
+
- {/* Row 2: Numeric Fields */} -
-
- + {/* 3. Cost Breakup Section - Full Width (Row 2) */} +
+
+
+

Cost Breakup

+ Required +
+
+ Tax fields are automatically toggled based on the dealer's state (Inter-state vs Intra-state). +
+ +
+ +
+ {costItems.map((item) => ( +
+
+ {/* Row 1: Description and Close */} +
+
+ + handleCostItemChange(item.id, 'description', e.target.value)} + className="w-full bg-white shadow-sm" + /> +
+ +
+ + {/* Row 2: Numeric Calculations - Optimization: Narrower widths for GST to save space */} +
+
+ +
+ - handleCostItemChange(item.id, 'amount', e.target.value) - } - className="w-full bg-white" - /> -
-
- - - handleCostItemChange(item.id, 'hsnCode', e.target.value) - } - className={`w-full bg-white ${!validateHSNSAC(item.hsnCode, item.isService).isValid ? 'border-red-500 focus-visible:ring-red-500' : ''}`} - /> - {!validateHSNSAC(item.hsnCode, item.isService).isValid && ( - - {validateHSNSAC(item.hsnCode, item.isService).message} - - )} -
-
- - -
-
- - - handleCostItemChange(item.id, 'gstRate', e.target.value) - } - className="w-full bg-white" + onChange={(e) => handleCostItemChange(item.id, 'amount', e.target.value)} + className="pl-8 bg-white shadow-sm" />
+
+ + handleCostItemChange(item.id, 'hsnCode', e.target.value)} + className={`bg-white shadow-sm ${!validateHSNSAC(item.hsnCode, item.isService).isValid ? 'border-red-500' : ''}`} + /> +
+
+ + +
+
+ + handleCostItemChange(item.id, 'cgstRate', e.target.value)} + disabled={!taxConfig.isCGST} + className="bg-white shadow-sm text-center px-1 disabled:bg-gray-100 disabled:text-gray-400" + /> +
+
+ + handleCostItemChange(item.id, 'sgstRate', e.target.value)} + disabled={!taxConfig.isSGST} + className="bg-white shadow-sm text-center px-1 disabled:bg-gray-100 disabled:text-gray-400" + /> +
+
+ + handleCostItemChange(item.id, 'utgstRate', e.target.value)} + disabled={!taxConfig.isUTGST} + className="bg-white shadow-sm text-center px-1 disabled:bg-gray-100 disabled:text-gray-400" + /> +
+
+ + handleCostItemChange(item.id, 'igstRate', e.target.value)} + disabled={!taxConfig.isIGST} + className="bg-white shadow-sm text-center px-1 disabled:bg-gray-100 disabled:text-gray-400" + /> +
- {item.gstAmt > 0 && ( -
-
- CGST ({item.cgstRate}%): - ₹{item.cgstAmt.toLocaleString('en-IN')} -
-
- SGST ({item.sgstRate}%): - ₹{item.sgstAmt.toLocaleString('en-IN')} -
-
- GST Total: - ₹{item.gstAmt.toLocaleString('en-IN')} -
-
- Item Total: - ₹{item.totalAmt.toLocaleString('en-IN')} -
+ {/* Item Summary Row */} +
+
+ CGST: ₹{item.cgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })} + {item.sgstAmt > 0 && SGST: ₹{item.sgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}} + {item.utgstAmt > 0 && UTGST: ₹{item.utgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}} + IGST: ₹{item.igstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}
- )} -
- ))} -
-
-
-
- - Estimated Budget -
-
- ₹{totalBudget.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} +
+ GST Total: ₹{item.gstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })} + Item Total: ₹{item.totalAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })} +
+
-
+ ))}
- {/* Timeline for Closure Section */} -
-
-

Timeline for Closure

- Required -
-
-
- - + {/* Total Budget Card */} +
+
+
+ +
+
+

Estimated Total Budget

+

Inclusive of all applicable taxes

- {timelineMode === 'date' ? ( -
- - setExpectedCompletionDate(date || '')} - minDate={minDate} - placeholderText="dd/mm/yyyy" - className="w-full" - /> -
- ) : ( -
- - setNumberOfDays(e.target.value)} - className="h-9 lg:h-10 w-full" - /> -
- )}
-
- - {/* Dealer Comments Section */} -
- -