added gst non gst lable hided hsn and gst related fields for the non-gst claims

This commit is contained in:
laxmanhalaki 2026-02-26 16:47:13 +05:30
parent 170f9a1788
commit b04776a5f8
8 changed files with 453 additions and 340 deletions

View File

@ -12,6 +12,13 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from '@/components/ui/dialog'; } from '@/components/ui/dialog';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { import {
FileText, FileText,
Plus, Plus,
@ -89,16 +96,17 @@ export function ActivityTypeManager() {
try { try {
setError(null); setError(null);
if (!formData.title.trim()) { if (!formData.title.trim() || !formData.taxationType.trim() || !formData.sapRefNo.trim()) {
setError('Activity type title is required'); setError('Title, Taxation Type, and Claim Document Type (SAP Ref) are required');
toast.error('Please fill in all mandatory fields');
return; return;
} }
const payload: Partial<ActivityType> = { const payload: Partial<ActivityType> = {
title: formData.title.trim(), title: formData.title.trim(),
itemCode: formData.itemCode.trim() || null, itemCode: formData.itemCode.trim() || null,
taxationType: formData.taxationType.trim() || null, taxationType: formData.taxationType.trim(),
sapRefNo: formData.sapRefNo.trim() || null sapRefNo: formData.sapRefNo.trim()
}; };
if (editingActivityType) { if (editingActivityType) {
@ -397,32 +405,37 @@ export function ActivityTypeManager() {
{/* Taxation Type Field */} {/* Taxation Type Field */}
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="taxationType" className="text-sm font-semibold text-slate-900"> <Label htmlFor="taxationType" className="text-sm font-semibold text-slate-900 flex items-center gap-1">
Taxation Type <span className="text-slate-400 font-normal text-xs">(Optional)</span> Taxation Type <span className="text-red-500">*</span>
</Label> </Label>
<Input <Select
id="taxationType"
placeholder="e.g., GST, VAT, Exempt"
value={formData.taxationType} value={formData.taxationType}
onChange={(e) => setFormData({ ...formData, taxationType: e.target.value })} onValueChange={(value) => setFormData({ ...formData, taxationType: value })}
className="h-11 border-slate-300 focus:border-re-green focus:ring-2 focus:ring-re-green/20 rounded-lg transition-all shadow-sm" >
/> <SelectTrigger id="taxationType" className="h-11 border-slate-300 focus:border-re-green focus:ring-2 focus:ring-re-green/20 rounded-lg transition-all shadow-sm">
<p className="text-xs text-slate-500">Optional taxation type for the activity</p> <SelectValue placeholder="Select Taxation Type" />
</SelectTrigger>
<SelectContent className="rounded-lg">
<SelectItem value="GST" className="p-3">GST</SelectItem>
<SelectItem value="Non GST" className="p-3">Non GST</SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-slate-500">Select whether the activity is GST or Non-GST</p>
</div> </div>
{/* SAP Reference Number Field */} {/* SAP Reference Number Field */}
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="sapRefNo" className="text-sm font-semibold text-slate-900"> <Label htmlFor="sapRefNo" className="text-sm font-semibold text-slate-900 flex items-center gap-1">
SAP Reference Number <span className="text-slate-400 font-normal text-xs">(Optional)</span> Claim Document Type (SAP Ref) <span className="text-red-500">*</span>
</Label> </Label>
<Input <Input
id="sapRefNo" id="sapRefNo"
placeholder="e.g., SAP-12345" placeholder="e.g., ZCNS, ZRE"
value={formData.sapRefNo} value={formData.sapRefNo}
onChange={(e) => setFormData({ ...formData, sapRefNo: e.target.value })} onChange={(e) => setFormData({ ...formData, sapRefNo: e.target.value })}
className="h-11 border-slate-300 focus:border-re-green focus:ring-2 focus:ring-re-green/20 rounded-lg transition-all shadow-sm" className="h-11 border-slate-300 focus:border-re-green focus:ring-2 focus:ring-re-green/20 rounded-lg transition-all shadow-sm"
/> />
<p className="text-xs text-slate-500">Optional SAP reference number</p> <p className="text-xs text-slate-500">Required SAP reference number for CSV generation</p>
</div> </div>
</div> </div>
@ -436,7 +449,7 @@ export function ActivityTypeManager() {
</Button> </Button>
<Button <Button
onClick={handleSave} onClick={handleSave}
disabled={!formData.title.trim()} disabled={!formData.title.trim() || !formData.taxationType || !formData.sapRefNo.trim()}
className="h-11 bg-re-green hover:bg-re-green/90 text-white shadow-md hover:shadow-lg transition-all disabled:opacity-50 disabled:cursor-not-allowed" className="h-11 bg-re-green hover:bg-re-green/90 text-white shadow-md hover:shadow-lg transition-all disabled:opacity-50 disabled:cursor-not-allowed"
> >
<FileText className="w-4 h-4 mr-2" /> <FileText className="w-4 h-4 mr-2" />

View File

@ -2525,6 +2525,7 @@ export function DealerClaimWorkflowTab({
preFilledIONumber={request?.internalOrder?.ioNumber || request?.internalOrder?.io_number || request?.internal_order?.ioNumber || request?.internal_order?.io_number || undefined} preFilledIONumber={request?.internalOrder?.ioNumber || request?.internalOrder?.io_number || request?.internal_order?.ioNumber || request?.internal_order?.io_number || undefined}
preFilledBlockedAmount={request?.internalOrder?.ioBlockedAmount || request?.internalOrder?.io_blocked_amount || request?.internal_order?.ioBlockedAmount || request?.internal_order?.io_blocked_amount || undefined} preFilledBlockedAmount={request?.internalOrder?.ioBlockedAmount || request?.internalOrder?.io_blocked_amount || request?.internal_order?.ioBlockedAmount || request?.internal_order?.io_blocked_amount || undefined}
preFilledRemainingBalance={request?.internalOrder?.ioRemainingBalance || request?.internalOrder?.io_remaining_balance || request?.internal_order?.ioRemainingBalance || request?.internal_order?.io_remaining_balance || undefined} preFilledRemainingBalance={request?.internalOrder?.ioRemainingBalance || request?.internalOrder?.io_remaining_balance || request?.internal_order?.ioRemainingBalance || request?.internal_order?.io_remaining_balance || undefined}
taxationType={request?.claimDetails?.taxationType}
/> />
{/* Dealer Completion Documents Modal */} {/* Dealer Completion Documents Modal */}
@ -2562,12 +2563,14 @@ export function DealerClaimWorkflowTab({
completionDocuments={completionDocumentsData} completionDocuments={completionDocumentsData}
requestTitle={request?.title} requestTitle={request?.title}
requestNumber={request?.requestNumber || request?.request_number || request?.id} requestNumber={request?.requestNumber || request?.request_number || request?.id}
taxationType={request?.claimDetails?.taxationType}
/> />
{/* Credit Note from SAP Modal (Step 8) */} {/* Credit Note from SAP Modal (Step 8) */}
<CreditNoteSAPModal <CreditNoteSAPModal
isOpen={showCreditNoteModal} isOpen={showCreditNoteModal}
onClose={() => setShowCreditNoteModal(false)} onClose={() => setShowCreditNoteModal(false)}
taxationType={request?.claimDetails?.taxationType}
onDownload={async () => { onDownload={async () => {
toast.info('Download functionality will be implemented'); toast.info('Download functionality will be implemented');
}} }}

View File

@ -40,6 +40,7 @@ interface CreditNoteSAPModalProps {
requestNumber?: string; requestNumber?: string;
requestId?: string; requestId?: string;
dueDate?: string; dueDate?: string;
taxationType?: string | null;
} }
export function CreditNoteSAPModal({ export function CreditNoteSAPModal({
@ -53,10 +54,13 @@ export function CreditNoteSAPModal({
requestNumber, requestNumber,
requestId: _requestId, requestId: _requestId,
dueDate, dueDate,
taxationType,
}: CreditNoteSAPModalProps) { }: CreditNoteSAPModalProps) {
const [downloading, setDownloading] = useState(false); const [downloading, setDownloading] = useState(false);
const [sending, setSending] = useState(false); const [sending, setSending] = useState(false);
const isNonGst = taxationType === 'Non GST' || taxationType === 'Non-GST';
const hasCreditNote = creditNoteData?.creditNoteNumber && creditNoteData?.creditNoteNumber !== ''; const hasCreditNote = creditNoteData?.creditNoteNumber && creditNoteData?.creditNoteNumber !== '';
const creditNoteNumber = creditNoteData?.creditNoteNumber || ''; const creditNoteNumber = creditNoteData?.creditNoteNumber || '';
const creditNoteDate = creditNoteData?.creditNoteDate const creditNoteDate = creditNoteData?.creditNoteDate
@ -118,9 +122,16 @@ export function CreditNoteSAPModal({
<Dialog open={isOpen} onOpenChange={onClose}> <Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="sm:max-w-lg lg:max-w-[1000px] max-w-4xl max-h-[90vh] overflow-y-auto"> <DialogContent className="sm:max-w-lg lg:max-w-[1000px] max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader> <DialogHeader>
<DialogTitle className="font-semibold flex items-center gap-2 text-2xl"> <DialogTitle className="font-semibold flex items-center gap-2 text-2xl flex-wrap">
<Receipt className="w-6 h-6 text-[--re-green]" /> <div className="flex items-center gap-2">
Credit Note from SAP <Receipt className="w-6 h-6 text-[--re-green]" />
Credit Note from SAP
</div>
{taxationType && (
<Badge className={`ml-2 border-none shadow-sm ${!isNonGst ? 'bg-emerald-600 text-white hover:bg-emerald-700' : 'bg-indigo-600 text-white hover:bg-indigo-700'}`}>
{!isNonGst ? 'GST Claim' : 'Non-GST Claim'}
</Badge>
)}
</DialogTitle> </DialogTitle>
<DialogDescription className="text-base"> <DialogDescription className="text-base">
Review and send credit note to dealer Review and send credit note to dealer

View File

@ -85,6 +85,7 @@ interface DMSPushModalProps {
completionDocuments?: CompletionDocuments | null; completionDocuments?: CompletionDocuments | null;
requestTitle?: string; requestTitle?: string;
requestNumber?: string; requestNumber?: string;
taxationType?: string | null;
} }
export function DMSPushModal({ export function DMSPushModal({
@ -96,7 +97,12 @@ export function DMSPushModal({
completionDocuments, completionDocuments,
requestTitle, requestTitle,
requestNumber, requestNumber,
taxationType,
}: DMSPushModalProps) { }: DMSPushModalProps) {
const isNonGst = useMemo(() => {
return taxationType === 'Non GST' || taxationType === 'Non-GST';
}, [taxationType]);
const [comments, setComments] = useState(''); const [comments, setComments] = useState('');
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
const [previewDocument, setPreviewDocument] = useState<{ const [previewDocument, setPreviewDocument] = useState<{
@ -268,8 +274,13 @@ export function DMSPushModal({
<Activity className="w-4 h-4 sm:w-5 sm:h-5 sm:w-6 sm:h-6 text-indigo-600" /> <Activity className="w-4 h-4 sm:w-5 sm:h-5 sm:w-6 sm:h-6 text-indigo-600" />
</div> </div>
<div className="flex-1"> <div className="flex-1">
<DialogTitle className="font-semibold text-lg sm:text-xl"> <DialogTitle className="font-semibold text-lg sm:text-xl flex items-center gap-2 flex-wrap">
E-Invoice Generation & Sync E-Invoice Generation & Sync
{taxationType && (
<Badge className={`ml-2 border-none shadow-sm ${!isNonGst ? 'bg-emerald-600 text-white hover:bg-emerald-700' : 'bg-indigo-600 text-white hover:bg-indigo-700'}`}>
{!isNonGst ? 'GST Claim' : 'Non-GST Claim'}
</Badge>
)}
</DialogTitle> </DialogTitle>
<DialogDescription className="text-xs sm:text-sm mt-1"> <DialogDescription className="text-xs sm:text-sm mt-1">
Review completion details and expenses before generating e-invoice and initiating SAP settlement Review completion details and expenses before generating e-invoice and initiating SAP settlement
@ -402,10 +413,14 @@ export function DMSPushModal({
<CardContent> <CardContent>
{/* Table Header */} {/* Table Header */}
<div className="grid grid-cols-12 gap-2 mb-2 px-3 text-xs font-medium text-gray-500 uppercase tracking-wider hidden sm:grid"> <div className="grid grid-cols-12 gap-2 mb-2 px-3 text-xs font-medium text-gray-500 uppercase tracking-wider hidden sm:grid">
<div className="col-span-4">Description</div> <div className={`${isNonGst ? 'col-span-8' : 'col-span-4'}`}>Description</div>
<div className="col-span-2 text-right">Base</div> <div className="col-span-2 text-right">Base</div>
<div className="col-span-2 text-right">GST Rate</div> {!isNonGst && (
<div className="col-span-2 text-right">GST Amt</div> <>
<div className="col-span-2 text-right">GST Rate</div>
<div className="col-span-2 text-right">GST Amt</div>
</>
)}
<div className="col-span-2 text-right">Total</div> <div className="col-span-2 text-right">Total</div>
</div> </div>
@ -428,11 +443,13 @@ export function DMSPushModal({
</div> </div>
<div className="sm:hidden flex justify-between w-full text-xs text-gray-500"> <div className="sm:hidden flex justify-between w-full text-xs text-gray-500">
<span>Base: {formatCurrency(amount)}</span> <span>Base: {formatCurrency(amount)}</span>
<span>GST: {gstRate}% ({formatCurrency(gstAmt)})</span> {!isNonGst && (
<span>GST: {gstRate}% ({formatCurrency(gstAmt)})</span>
)}
</div> </div>
{/* Desktop View: Grid */} {/* Desktop View: Grid */}
<div className="hidden sm:block col-span-4 min-w-0"> <div className={`hidden sm:block ${isNonGst ? 'col-span-8' : 'col-span-4'} min-w-0`}>
<p className="font-medium text-gray-900 truncate" title={expense.description}> <p className="font-medium text-gray-900 truncate" title={expense.description}>
{expense.description || `Expense ${index + 1}`} {expense.description || `Expense ${index + 1}`}
</p> </p>
@ -440,12 +457,16 @@ export function DMSPushModal({
<div className="hidden sm:block col-span-2 text-right text-gray-600"> <div className="hidden sm:block col-span-2 text-right text-gray-600">
{formatCurrency(amount)} {formatCurrency(amount)}
</div> </div>
<div className="hidden sm:block col-span-2 text-right text-gray-600"> {!isNonGst && (
{gstRate}% <>
</div> <div className="hidden sm:block col-span-2 text-right text-gray-600">
<div className="hidden sm:block col-span-2 text-right text-gray-600"> {gstRate}%
{formatCurrency(gstAmt)} </div>
</div> <div className="hidden sm:block col-span-2 text-right text-gray-600">
{formatCurrency(gstAmt)}
</div>
</>
)}
<div className="hidden sm:block col-span-2 text-right font-semibold text-gray-900"> <div className="hidden sm:block col-span-2 text-right font-semibold text-gray-900">
{formatCurrency(total)} {formatCurrency(total)}
</div> </div>

View File

@ -96,6 +96,10 @@ export function DealerCompletionDocumentsModal({
return getActiveTaxComponents(stateCode); return getActiveTaxComponents(stateCode);
}, [dealerGSTIN]); }, [dealerGSTIN]);
const isNonGst = useMemo(() => {
return taxationType === 'Non GST' || taxationType === 'Non-GST';
}, [taxationType]);
const [expenseItems, setExpenseItems] = useState<ExpenseItem[]>([]); const [expenseItems, setExpenseItems] = useState<ExpenseItem[]>([]);
const [completionDocuments, setCompletionDocuments] = useState<File[]>([]); const [completionDocuments, setCompletionDocuments] = useState<File[]>([]);
const [activityPhotos, setActivityPhotos] = useState<File[]>([]); const [activityPhotos, setActivityPhotos] = useState<File[]>([]);
@ -225,13 +229,13 @@ export function DealerCompletionDocumentsModal({
const hasPhotos = activityPhotos.length > 0; const hasPhotos = activityPhotos.length > 0;
const hasDescription = completionDescription.trim().length > 0; const hasDescription = completionDescription.trim().length > 0;
const hasHSNSACErrors = expenseItems.some(item => { const hasHSNSACErrors = isNonGst ? false : expenseItems.some(item => {
const { isValid } = validateHSNSAC(item.hsnCode, item.isService); const { isValid } = validateHSNSAC(item.hsnCode, item.isService);
return !isValid; return !isValid;
}); });
return hasCompletionDate && hasDocuments && hasPhotos && hasDescription && !hasHSNSACErrors; return hasCompletionDate && hasDocuments && hasPhotos && hasDescription && !hasHSNSACErrors;
}, [activityCompletionDate, completionDocuments, activityPhotos, completionDescription]); }, [activityCompletionDate, completionDocuments, activityPhotos, completionDescription, isNonGst, expenseItems]);
// Get today's date in YYYY-MM-DD format for max date // Get today's date in YYYY-MM-DD format for max date
const maxDate = new Date().toISOString().split('T')[0]; const maxDate = new Date().toISOString().split('T')[0];
@ -524,8 +528,8 @@ export function DealerCompletionDocumentsModal({
(item) => item.description.trim() !== '' && item.amount > 0 (item) => item.description.trim() !== '' && item.amount > 0
); );
// Validation: Alert for 0% GST on taxable items // Validation: Alert for 0% GST on taxable items (Skip for Non-GST)
const hasZeroGstItems = validExpenses.some(item => const hasZeroGstItems = !isNonGst && validExpenses.some(item =>
item.description.trim() !== '' && item.amount > 0 && (item.gstRate === 0 || !item.gstRate) item.description.trim() !== '' && item.amount > 0 && (item.gstRate === 0 || !item.gstRate)
); );
@ -616,15 +620,17 @@ export function DealerCompletionDocumentsModal({
<Dialog open={isOpen} onOpenChange={handleClose}> <Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="dealer-completion-documents-modal overflow-hidden flex flex-col"> <DialogContent className="dealer-completion-documents-modal overflow-hidden flex flex-col">
<DialogHeader className="px-6 pt-6 pb-3 flex-shrink-0"> <DialogHeader className="px-6 pt-6 pb-3 flex-shrink-0">
<DialogTitle className="font-semibold flex items-center gap-2 text-xl sm:text-2xl"> <DialogTitle className="font-semibold flex items-center gap-2 text-xl sm:text-2xl flex-wrap">
<Upload className="w-5 h-5 sm:w-6 sm:h-6 text-[--re-green]" /> <div className="flex items-center gap-2">
Activity Completion Documents <Upload className="w-5 h-5 sm:w-6 sm:h-6 text-[--re-green]" />
Activity Completion Documents
</div>
{taxationType && (
<Badge className={`ml-2 border-none shadow-sm ${taxationType === 'GST' ? 'bg-emerald-600 text-white hover:bg-emerald-700' : 'bg-indigo-600 text-white hover:bg-indigo-700'}`}>
{taxationType === 'GST' ? 'GST Claim' : 'Non-GST Claim'}
</Badge>
)}
</DialogTitle> </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"> <DialogDescription className="text-sm sm:text-base">
Step 5: Upload completion proof and final documents Step 5: Upload completion proof and final documents
</DialogDescription> </DialogDescription>
@ -666,9 +672,11 @@ export function DealerCompletionDocumentsModal({
<h3 className="font-semibold text-base sm:text-lg">Closed Expenses</h3> <h3 className="font-semibold text-base sm:text-lg">Closed Expenses</h3>
<Badge className="bg-secondary text-secondary-foreground text-xs">Optional</Badge> <Badge className="bg-secondary text-secondary-foreground text-xs">Optional</Badge>
</div> </div>
<div className="text-[10px] text-gray-500 italic mt-0.5"> {!isNonGst && (
Tax fields are automatically toggled based on the dealer's state (Inter-state vs Intra-state). <div className="text-[10px] text-gray-500 italic mt-0.5">
</div> Tax fields are automatically toggled based on the dealer's state (Inter-state vs Intra-state).
</div>
)}
<Button <Button
type="button" type="button"
onClick={handleAddExpense} onClick={handleAddExpense}
@ -683,7 +691,7 @@ export function DealerCompletionDocumentsModal({
{expenseItems.map((item) => ( {expenseItems.map((item) => (
<div key={item.id} className="p-4 border rounded-lg bg-gray-50/50 space-y-4 relative group"> <div key={item.id} className="p-4 border rounded-lg bg-gray-50/50 space-y-4 relative group">
<div className="flex gap-3 items-start w-full"> <div className="flex gap-3 items-start w-full">
<div className="flex-1 min-w-0"> <div className={`${isNonGst ? 'flex-[3]' : 'flex-1'} min-w-0`}>
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Item description</Label> <Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Item description</Label>
<Input <Input
placeholder="e.g., Venue rental, Refreshments" placeholder="e.g., Venue rental, Refreshments"
@ -694,116 +702,141 @@ export function DealerCompletionDocumentsModal({
className="w-full bg-white text-sm" className="w-full bg-white text-sm"
/> />
</div> </div>
<div className="w-28 sm:w-36 flex-shrink-0"> {isNonGst && (
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Amount (Base)</Label> <div className="w-28 sm:w-36 flex-shrink-0">
<div className="relative"> <Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Amount</Label>
<IndianRupee className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-gray-400" /> <div className="relative">
<Input <IndianRupee className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-gray-400" />
type="number" <Input
placeholder="0.00" type="number"
min="0" placeholder="0.00"
step="0.01" min="0"
value={item.amount || ''} step="0.01"
onChange={(e) => value={item.amount || ''}
handleExpenseChange(item.id, 'amount', e.target.value) onChange={(e) =>
} handleExpenseChange(item.id, 'amount', e.target.value)
className="w-full bg-white text-sm pl-8" }
/> className="w-full bg-white text-sm pl-8"
/>
</div>
</div> </div>
</div> )}
<div className="w-20 sm:w-24 flex-shrink-0"> {!isNonGst && (
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">HSN Code</Label> <div className="w-28 sm:w-36 flex-shrink-0">
<Input <Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Amount (Base)</Label>
placeholder="HSN" <div className="relative">
value={item.hsnCode || ''} <IndianRupee className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-gray-400" />
onChange={(e) => <Input
handleExpenseChange(item.id, 'hsnCode', e.target.value) type="number"
} placeholder="0.00"
className={`w-full bg-white text-sm ${!validateHSNSAC(item.hsnCode, item.isService).isValid ? 'border-red-500 focus-visible:ring-red-500' : ''}`} min="0"
/> step="0.01"
{!validateHSNSAC(item.hsnCode, item.isService).isValid && ( value={item.amount || ''}
<span className="text-[9px] text-red-500 mt-1 block leading-tight"> onChange={(e) =>
{validateHSNSAC(item.hsnCode, item.isService).message} handleExpenseChange(item.id, 'amount', e.target.value)
</span> }
)} className="w-full bg-white text-sm pl-8"
</div> />
<div className="w-24 sm:w-28 flex-shrink-0"> </div>
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Item Type</Label> </div>
<select )}
value={item.isService ? 'SAC' : 'HSN'} {!isNonGst && (
onChange={(e) => <>
handleExpenseChange(item.id, 'isService', e.target.value === 'SAC') <div className="w-20 sm:w-24 flex-shrink-0">
} <Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">HSN Code</Label>
className="flex h-9 w-full rounded-md border border-input bg-white px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50" <Input
> placeholder="HSN"
<option value="HSN">HSN (Goods)</option> value={item.hsnCode || ''}
<option value="SAC">SAC (Service)</option> onChange={(e) =>
</select> handleExpenseChange(item.id, 'hsnCode', e.target.value)
</div> }
<div className="w-14 sm:w-16 flex-shrink-0"> className={`w-full bg-white text-sm ${!validateHSNSAC(item.hsnCode, item.isService).isValid ? 'border-red-500 focus-visible:ring-red-500' : ''}`}
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">CGST %</Label> />
<Input {!validateHSNSAC(item.hsnCode, item.isService).isValid && (
type="number" <span className="text-[9px] text-red-500 mt-1 block leading-tight">
placeholder="%" {validateHSNSAC(item.hsnCode, item.isService).message}
min="0" </span>
max="100" )}
step="0.1" </div>
value={item.cgstRate || ''} <div className="w-24 sm:w-28 flex-shrink-0">
onChange={(e) => <Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Item Type</Label>
handleExpenseChange(item.id, 'cgstRate', e.target.value) <select
} value={item.isService ? 'SAC' : 'HSN'}
disabled={!taxConfig.isCGST} onChange={(e) =>
className="w-full bg-white text-xs px-1 text-center disabled:bg-gray-100 disabled:text-gray-400" handleExpenseChange(item.id, 'isService', e.target.value === 'SAC')
/> }
</div> className="flex h-9 w-full rounded-md border border-input bg-white px-3 py-1 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
<div className="w-14 sm:w-16 flex-shrink-0"> >
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">SGST %</Label> <option value="HSN">HSN (Goods)</option>
<Input <option value="SAC">SAC (Service)</option>
type="number" </select>
placeholder="%" </div>
min="0" <div className="w-14 sm:w-16 flex-shrink-0">
max="100" <Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">CGST %</Label>
step="0.1" <Input
value={item.sgstRate || ''} type="number"
onChange={(e) => placeholder="%"
handleExpenseChange(item.id, 'sgstRate', e.target.value) min="0"
} max="100"
disabled={!taxConfig.isSGST} step="0.1"
className="w-full bg-white text-xs px-1 text-center disabled:bg-gray-100 disabled:text-gray-400" value={item.cgstRate || ''}
/> onChange={(e) =>
</div> handleExpenseChange(item.id, 'cgstRate', e.target.value)
<div className="w-14 sm:w-16 flex-shrink-0"> }
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">UTGST %</Label> disabled={!taxConfig.isCGST}
<Input className="w-full bg-white text-xs px-1 text-center disabled:bg-gray-100 disabled:text-gray-400"
type="number" />
placeholder="%" </div>
min="0" <div className="w-14 sm:w-16 flex-shrink-0">
max="100" <Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">SGST %</Label>
step="0.1" <Input
value={item.utgstRate || ''} type="number"
onChange={(e) => placeholder="%"
handleExpenseChange(item.id, 'utgstRate', e.target.value) min="0"
} max="100"
disabled={!taxConfig.isUTGST} step="0.1"
className="w-full bg-white text-xs px-1 text-center disabled:bg-gray-100 disabled:text-gray-400" value={item.sgstRate || ''}
/> onChange={(e) =>
</div> handleExpenseChange(item.id, 'sgstRate', e.target.value)
<div className="w-14 sm:w-16 flex-shrink-0"> }
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">IGST %</Label> disabled={!taxConfig.isSGST}
<Input className="w-full bg-white text-xs px-1 text-center disabled:bg-gray-100 disabled:text-gray-400"
type="number" />
placeholder="%" </div>
min="0" <div className="w-14 sm:w-16 flex-shrink-0">
max="100" <Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">UTGST %</Label>
step="0.1" <Input
value={item.igstRate || ''} type="number"
onChange={(e) => placeholder="%"
handleExpenseChange(item.id, 'igstRate', e.target.value) min="0"
} max="100"
disabled={!taxConfig.isIGST} step="0.1"
className="w-full bg-white text-xs px-1 text-center disabled:bg-gray-100 disabled:text-gray-400" value={item.utgstRate || ''}
/> onChange={(e) =>
</div> 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"
/>
</div>
<div className="w-14 sm:w-16 flex-shrink-0">
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">IGST %</Label>
<Input
type="number"
placeholder="%"
min="0"
max="100"
step="0.1"
value={item.igstRate || ''}
onChange={(e) =>
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"
/>
</div>
</>
)}
<Button <Button
type="button" type="button"
variant="ghost" variant="ghost"
@ -815,34 +848,26 @@ export function DealerCompletionDocumentsModal({
</Button> </Button>
</div> </div>
<div className="grid grid-cols-2 sm:grid-cols-5 gap-3 pt-3 border-t border-dashed border-gray-200"> <div className={`grid grid-cols-2 sm:grid-cols-5 gap-3 pt-3 border-t border-dashed border-gray-200 ${isNonGst ? 'items-center' : ''}`}>
<div className="flex flex-col"> {!isNonGst ? (
<span className="text-[10px] text-gray-500 uppercase">CGST</span> <>
<span className="text-xs font-semibold">{item.cgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })}</span> <div className="flex flex-wrap gap-4 text-gray-500 font-medium">
</div> <span>CGST: <span className="text-gray-900 font-semibold">{(item.cgstAmt || 0).toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span>
{item.sgstAmt > 0 && ( {item.sgstAmt > 0 && <span>SGST: <span className="text-gray-900 font-semibold">{(item.sgstAmt || 0).toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span>}
<div className="flex flex-col"> {item.utgstAmt > 0 && <span>UTGST: <span className="text-gray-900 font-semibold">{(item.utgstAmt || 0).toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span>}
<span className="text-[10px] text-gray-500 uppercase">SGST</span> <span>IGST: <span className="text-gray-900 font-semibold">{(item.igstAmt || 0).toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span>
<span className="text-xs font-semibold">{item.sgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })}</span> </div>
</div> <div className="flex flex-wrap gap-4 items-center sm:justify-end">
<span className="text-gray-500">GST Total: <span className="text-gray-900 font-bold">{(item.gstAmt || 0).toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span>
<Badge className="bg-[#2d4a3e] text-white px-3 py-1 text-xs">Item Total: {(item.totalAmt || 0).toLocaleString('en-IN', { minimumFractionDigits: 1 })}</Badge>
</div>
</>
) : (
<div className="col-span-4 invisible"></div>
)} )}
{item.utgstAmt > 0 && (
<div className="flex flex-col">
<span className="text-[10px] text-gray-500 uppercase">UTGST</span>
<span className="text-xs font-semibold">{item.utgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })}</span>
</div>
)}
<div className="flex flex-col">
<span className="text-[10px] text-gray-500 uppercase">IGST</span>
<span className="text-xs font-semibold">{item.igstAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })}</span>
</div>
<div className="flex flex-col">
<span className="text-[10px] text-gray-500 uppercase">GST Total</span>
<span className="text-xs font-semibold">{item.gstAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })}</span>
</div>
<div className="flex flex-col items-end"> <div className="flex flex-col items-end">
<span className="text-[10px] text-gray-500 uppercase">Item Total</span> <span className="text-[10px] text-gray-500 uppercase">Item Total</span>
<span className="text-sm font-bold text-[#2d4a3e]">{item.totalAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })}</span> <span className="text-sm font-bold text-[#2d4a3e]">{(item.amount || 0).toLocaleString('en-IN', { minimumFractionDigits: 2 })}</span>
</div> </div>
</div> </div>
</div> </div>
@ -1348,7 +1373,7 @@ export function DealerCompletionDocumentsModal({
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog >
{/* File Preview Modal - Matching DocumentsTab style */} {/* File Preview Modal - Matching DocumentsTab style */}
{ {

View File

@ -96,6 +96,10 @@ export function DealerProposalSubmissionModal({
return getActiveTaxComponents(stateCode); return getActiveTaxComponents(stateCode);
}, [dealerGSTIN]); }, [dealerGSTIN]);
const isNonGst = useMemo(() => {
return taxationType === 'Non GST' || taxationType === 'Non-GST';
}, [taxationType]);
const [costItems, setCostItems] = useState<CostItem[]>([ const [costItems, setCostItems] = useState<CostItem[]>([
{ {
id: '1', id: '1',
@ -260,13 +264,13 @@ export function DealerProposalSubmissionModal({
: numberOfDays !== '' && parseInt(numberOfDays) > 0; : numberOfDays !== '' && parseInt(numberOfDays) > 0;
const hasValidComments = dealerComments.trim().length > 0; const hasValidComments = dealerComments.trim().length > 0;
const hasHSNSACErrors = costItems.some(item => { const hasHSNSACErrors = isNonGst ? false : costItems.some(item => {
const { isValid } = validateHSNSAC(item.hsnCode, item.isService); const { isValid } = validateHSNSAC(item.hsnCode, item.isService);
return !isValid; return !isValid;
}); });
return hasProposalDoc && hasValidCostItems && hasTimeline && hasValidComments && !hasHSNSACErrors; return hasProposalDoc && hasValidCostItems && hasTimeline && hasValidComments && !hasHSNSACErrors;
}, [proposalDocument, costItems, timelineMode, expectedCompletionDate, numberOfDays, dealerComments]); }, [proposalDocument, costItems, timelineMode, expectedCompletionDate, numberOfDays, dealerComments, isNonGst]);
const handleProposalDocChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleProposalDocChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]; const file = e.target.files?.[0];
@ -462,8 +466,8 @@ export function DealerProposalSubmissionModal({
try { try {
setSubmitting(true); setSubmitting(true);
// Validation: Alert for 0% GST on taxable items // Validation: Alert for 0% GST on taxable items (Skip for Non-GST)
const hasZeroGstItems = costItems.some(item => const hasZeroGstItems = !isNonGst && costItems.some(item =>
item.description.trim() !== '' && item.amount > 0 && (item.gstRate === 0 || !item.gstRate) item.description.trim() !== '' && item.amount > 0 && (item.gstRate === 0 || !item.gstRate)
); );
@ -546,15 +550,17 @@ export function DealerProposalSubmissionModal({
<Dialog open={isOpen} onOpenChange={handleClose}> <Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="dealer-proposal-modal overflow-hidden flex flex-col"> <DialogContent className="dealer-proposal-modal overflow-hidden flex flex-col">
<DialogHeader className="flex-shrink-0 pb-3 lg:pb-4"> <DialogHeader className="flex-shrink-0 pb-3 lg:pb-4">
<DialogTitle className="flex items-center gap-2 text-xl lg:text-2xl"> <DialogTitle className="flex items-center gap-2 text-xl lg:text-2xl flex-wrap">
<Upload className="w-5 h-5 lg:w-6 lg:h-6 text-[--re-green]" /> <div className="flex items-center gap-2">
Dealer Proposal Submission <Upload className="w-5 h-5 lg:w-6 lg:h-6 text-[--re-green]" />
Dealer Proposal Submission
</div>
{taxationType && (
<Badge className={`ml-2 border-none shadow-sm ${taxationType === 'GST' ? 'bg-emerald-600 text-white hover:bg-emerald-700' : 'bg-indigo-600 text-white hover:bg-indigo-700'}`}>
{taxationType === 'GST' ? 'GST Claim' : 'Non-GST Claim'}
</Badge>
)}
</DialogTitle> </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"> <DialogDescription className="text-sm lg:text-base">
Step 1: Upload proposal and planning details Step 1: Upload proposal and planning details
</DialogDescription> </DialogDescription>
@ -824,9 +830,11 @@ export function DealerProposalSubmissionModal({
<h3 className="font-semibold text-lg text-[#2d4a3e]">Cost Breakup</h3> <h3 className="font-semibold text-lg text-[#2d4a3e]">Cost Breakup</h3>
<Badge variant="outline" className="text-xs border-[#2d4a3e] text-[#2d4a3e] bg-green-50 font-medium">Required</Badge> <Badge variant="outline" className="text-xs border-[#2d4a3e] text-[#2d4a3e] bg-green-50 font-medium">Required</Badge>
</div> </div>
<div className="text-[10px] text-gray-500 italic mt-0.5"> {!isNonGst && (
Tax fields are automatically toggled based on the dealer's state (Inter-state vs Intra-state). <div className="text-[10px] text-gray-500 italic mt-0.5">
</div> Tax fields are automatically toggled based on the dealer's state (Inter-state vs Intra-state).
</div>
)}
<Button <Button
type="button" type="button"
onClick={handleAddCostItem} onClick={handleAddCostItem}
@ -844,7 +852,7 @@ export function DealerProposalSubmissionModal({
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
{/* Row 1: Description and Close */} {/* Row 1: Description and Close */}
<div className="flex gap-3 items-start"> <div className="flex gap-3 items-start">
<div className="flex-1"> <div className={`${isNonGst ? 'flex-[3]' : 'flex-1'}`}>
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Item Description</Label> <Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Item Description</Label>
<Input <Input
placeholder="e.g., Venue branding, Logistics, etc." placeholder="e.g., Venue branding, Logistics, etc."
@ -853,6 +861,20 @@ export function DealerProposalSubmissionModal({
className="w-full bg-white shadow-sm" className="w-full bg-white shadow-sm"
/> />
</div> </div>
{isNonGst && (
<div className="flex-1 min-w-[140px]">
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Amount</Label>
<div className="relative">
<IndianRupee className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-gray-400" />
<Input
type="number"
value={item.amount || ''}
onChange={(e) => handleCostItemChange(item.id, 'amount', e.target.value)}
className="pl-8 bg-white shadow-sm"
/>
</div>
</div>
)}
<Button <Button
type="button" type="button"
variant="ghost" variant="ghost"
@ -865,94 +887,96 @@ export function DealerProposalSubmissionModal({
</Button> </Button>
</div> </div>
{/* Row 2: Numeric Calculations - Optimization: Narrower widths for GST to save space */} {!isNonGst && (
<div className="flex flex-wrap gap-x-4 gap-y-3 items-start"> <div className="flex flex-wrap gap-x-4 gap-y-3 items-start">
<div className="flex-1 min-w-[140px]"> <div className="flex-1 min-w-[140px]">
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Amount (Base)</Label> <Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Amount (Base)</Label>
<div className="relative"> <div className="relative">
<IndianRupee className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-gray-400" /> <IndianRupee className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-gray-400" />
<Input
type="number"
value={item.amount || ''}
onChange={(e) => handleCostItemChange(item.id, 'amount', e.target.value)}
className="pl-8 bg-white shadow-sm"
/>
</div>
</div>
<div className="w-28 sm:w-32">
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">HSN/SAC</Label>
<Input
value={item.hsnCode || ''}
onChange={(e) => handleCostItemChange(item.id, 'hsnCode', e.target.value)}
className={`bg-white shadow-sm ${!validateHSNSAC(item.hsnCode, item.isService).isValid ? 'border-red-500' : ''}`}
/>
</div>
<div className="w-28 sm:w-32">
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Type</Label>
<select
value={item.isService ? 'SAC' : 'HSN'}
onChange={(e) => handleCostItemChange(item.id, 'isService', e.target.value === 'SAC')}
className="flex h-10 w-full rounded-md border border-input bg-white px-3 py-2 text-sm shadow-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-[#2d4a3e]"
>
<option value="HSN">HSN (Goods)</option>
<option value="SAC">SAC (Service)</option>
</select>
</div>
<div className="w-16">
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">CGST %</Label>
<Input <Input
type="number" type="number"
value={item.amount || ''} value={item.cgstRate || ''}
onChange={(e) => handleCostItemChange(item.id, 'amount', e.target.value)} onChange={(e) => handleCostItemChange(item.id, 'cgstRate', e.target.value)}
className="pl-8 bg-white shadow-sm" disabled={!taxConfig.isCGST}
className="bg-white shadow-sm text-center px-1 disabled:bg-gray-100 disabled:text-gray-400"
/>
</div>
<div className="w-16">
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">SGST %</Label>
<Input
type="number"
value={item.sgstRate || ''}
onChange={(e) => 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"
/>
</div>
<div className="w-16">
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">UTGST %</Label>
<Input
type="number"
value={item.utgstRate || ''}
onChange={(e) => 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"
/>
</div>
<div className="w-16">
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">IGST %</Label>
<Input
type="number"
value={item.igstRate || ''}
onChange={(e) => 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"
/> />
</div> </div>
</div> </div>
<div className="w-28 sm:w-32"> )}
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">HSN/SAC</Label>
<Input
value={item.hsnCode || ''}
onChange={(e) => handleCostItemChange(item.id, 'hsnCode', e.target.value)}
className={`bg-white shadow-sm ${!validateHSNSAC(item.hsnCode, item.isService).isValid ? 'border-red-500' : ''}`}
/>
</div>
<div className="w-28 sm:w-32">
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Type</Label>
<select
value={item.isService ? 'SAC' : 'HSN'}
onChange={(e) => handleCostItemChange(item.id, 'isService', e.target.value === 'SAC')}
className="flex h-10 w-full rounded-md border border-input bg-white px-3 py-2 text-sm shadow-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-[#2d4a3e]"
>
<option value="HSN">HSN (Goods)</option>
<option value="SAC">SAC (Service)</option>
</select>
</div>
<div className="w-16">
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">CGST %</Label>
<Input
type="number"
value={item.cgstRate || ''}
onChange={(e) => 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"
/>
</div>
<div className="w-16">
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">SGST %</Label>
<Input
type="number"
value={item.sgstRate || ''}
onChange={(e) => 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"
/>
</div>
<div className="w-16">
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">UTGST %</Label>
<Input
type="number"
value={item.utgstRate || ''}
onChange={(e) => 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"
/>
</div>
<div className="w-16">
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">IGST %</Label>
<Input
type="number"
value={item.igstRate || ''}
onChange={(e) => 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"
/>
</div>
</div>
{/* Item Summary Row */} {!isNonGst && (
<div className="flex flex-wrap gap-4 pt-3 border-t border-dashed items-center justify-between text-xs"> <div className="flex flex-wrap gap-4 pt-3 border-t border-dashed items-center justify-between text-xs">
<div className="flex gap-4 text-gray-500 font-medium"> <div className="flex gap-4 text-gray-500 font-medium">
<span>CGST: <span className="text-gray-900">{item.cgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span> <span>CGST: <span className="text-gray-900">{item.cgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span>
{item.sgstAmt > 0 && <span>SGST: <span className="text-gray-900">{item.sgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span>} {item.sgstAmt > 0 && <span>SGST: <span className="text-gray-900">{item.sgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span>}
{item.utgstAmt > 0 && <span>UTGST: <span className="text-gray-900">{item.utgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span>} {item.utgstAmt > 0 && <span>UTGST: <span className="text-gray-900">{item.utgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span>}
<span>IGST: <span className="text-gray-900">{item.igstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span> <span>IGST: <span className="text-gray-900">{item.igstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span>
</div>
<div className="flex gap-4 items-center">
<span className="text-gray-500">GST Total: <span className="text-gray-900 font-semibold">{item.gstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span>
<span className="text-sm font-bold text-[#2d4a3e] bg-green-50 px-3 py-1 rounded-lg">Item Total: {item.totalAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span>
</div>
</div> </div>
<div className="flex gap-4 items-center"> )}
<span className="text-gray-500">GST Total: <span className="text-gray-900 font-semibold">{item.gstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span>
<span className="text-sm font-bold text-[#2d4a3e] bg-green-50 px-3 py-1 rounded-lg">Item Total: {item.totalAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span>
</div>
</div>
</div> </div>
</div> </div>
))} ))}
@ -966,7 +990,7 @@ export function DealerProposalSubmissionModal({
</div> </div>
<div> <div>
<p className="text-xs text-white/70 uppercase font-bold tracking-wider">Estimated Total Budget</p> <p className="text-xs text-white/70 uppercase font-bold tracking-wider">Estimated Total Budget</p>
<p className="text-sm text-white/90">Inclusive of all applicable taxes</p> {!isNonGst && <p className="text-sm text-white/90">Inclusive of all applicable taxes</p>}
</div> </div>
</div> </div>
<div className="text-3xl font-bold border-l border-white/20 pl-6"> <div className="text-3xl font-bold border-l border-white/20 pl-6">

View File

@ -37,6 +37,7 @@ interface DeptLeadIOApprovalModalProps {
preFilledIONumber?: string; preFilledIONumber?: string;
preFilledBlockedAmount?: number; preFilledBlockedAmount?: number;
preFilledRemainingBalance?: number; preFilledRemainingBalance?: number;
taxationType?: string | null;
} }
export function DeptLeadIOApprovalModal({ export function DeptLeadIOApprovalModal({
@ -49,11 +50,16 @@ export function DeptLeadIOApprovalModal({
preFilledIONumber, preFilledIONumber,
preFilledBlockedAmount, preFilledBlockedAmount,
preFilledRemainingBalance, preFilledRemainingBalance,
taxationType,
}: DeptLeadIOApprovalModalProps) { }: DeptLeadIOApprovalModalProps) {
const [actionType, setActionType] = useState<'approve' | 'reject'>('approve'); const [actionType, setActionType] = useState<'approve' | 'reject'>('approve');
const [comments, setComments] = useState(''); const [comments, setComments] = useState('');
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
const isNonGst = useMemo(() => {
return taxationType === 'Non GST' || taxationType === 'Non-GST';
}, [taxationType]);
// Get IO number from props (read-only, from IO table) // Get IO number from props (read-only, from IO table)
const ioNumber = preFilledIONumber || ''; const ioNumber = preFilledIONumber || '';
@ -138,8 +144,13 @@ export function DeptLeadIOApprovalModal({
<CircleCheckBig className="w-5 h-5 lg:w-6 lg:h-6 text-green-600" /> <CircleCheckBig className="w-5 h-5 lg:w-6 lg:h-6 text-green-600" />
</div> </div>
<div className="flex-1"> <div className="flex-1">
<DialogTitle className="font-semibold text-lg lg:text-xl"> <DialogTitle className="font-semibold text-lg lg:text-xl flex items-center gap-2 flex-wrap">
Review and Approve Review and Approve
{taxationType && (
<Badge className={`ml-2 border-none shadow-sm ${!isNonGst ? 'bg-emerald-600 text-white hover:bg-emerald-700' : 'bg-indigo-600 text-white hover:bg-indigo-700'}`}>
{!isNonGst ? 'GST Claim' : 'Non-GST Claim'}
</Badge>
)}
</DialogTitle> </DialogTitle>
<DialogDescription className="text-xs lg:text-sm mt-1"> <DialogDescription className="text-xs lg:text-sm mt-1">
Review IO details and provide your approval comments Review IO details and provide your approval comments
@ -174,11 +185,10 @@ export function DeptLeadIOApprovalModal({
<Button <Button
type="button" type="button"
onClick={() => setActionType('approve')} onClick={() => setActionType('approve')}
className={`flex-1 text-sm lg:text-base ${ className={`flex-1 text-sm lg:text-base ${actionType === 'approve'
actionType === 'approve' ? 'bg-green-600 text-white shadow-sm'
? 'bg-green-600 text-white shadow-sm' : 'text-gray-700 hover:bg-gray-200'
: 'text-gray-700 hover:bg-gray-200' }`}
}`}
variant={actionType === 'approve' ? 'default' : 'ghost'} variant={actionType === 'approve' ? 'default' : 'ghost'}
> >
<CircleCheckBig className="w-4 h-4 mr-1" /> <CircleCheckBig className="w-4 h-4 mr-1" />
@ -187,11 +197,10 @@ export function DeptLeadIOApprovalModal({
<Button <Button
type="button" type="button"
onClick={() => setActionType('reject')} onClick={() => setActionType('reject')}
className={`flex-1 text-sm lg:text-base ${ className={`flex-1 text-sm lg:text-base ${actionType === 'reject'
actionType === 'reject' ? 'bg-red-600 text-white shadow-sm'
? 'bg-red-600 text-white shadow-sm' : 'text-gray-700 hover:bg-gray-200'
: 'text-gray-700 hover:bg-gray-200' }`}
}`}
variant={actionType === 'reject' ? 'destructive' : 'ghost'} variant={actionType === 'reject' ? 'destructive' : 'ghost'}
> >
<CircleX className="w-4 h-4 mr-1" /> <CircleX className="w-4 h-4 mr-1" />
@ -309,11 +318,10 @@ export function DeptLeadIOApprovalModal({
<Button <Button
onClick={handleSubmit} onClick={handleSubmit}
disabled={!isFormValid || submitting} disabled={!isFormValid || submitting}
className={`text-sm lg:text-base ${ className={`text-sm lg:text-base ${actionType === 'approve'
actionType === 'approve' ? 'bg-green-600 hover:bg-green-700'
? 'bg-green-600 hover:bg-green-700' : 'bg-red-600 hover:bg-red-700'
: 'bg-red-600 hover:bg-red-700' } text-white`}
} text-white`}
> >
{submitting ? ( {submitting ? (
`${actionType === 'approve' ? 'Approving' : 'Rejecting'}...` `${actionType === 'approve' ? 'Approving' : 'Rejecting'}...`

View File

@ -88,6 +88,10 @@ export function InitiatorProposalApprovalModal({
previousProposalData, previousProposalData,
taxationType, taxationType,
}: InitiatorProposalApprovalModalProps) { }: InitiatorProposalApprovalModalProps) {
const isNonGst = useMemo(() => {
return taxationType === 'Non GST' || taxationType === 'Non-GST';
}, [taxationType]);
const [comments, setComments] = useState(''); const [comments, setComments] = useState('');
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
const [actionType, setActionType] = useState<'approve' | 'reject' | 'revision' | null>(null); const [actionType, setActionType] = useState<'approve' | 'reject' | 'revision' | null>(null);
@ -280,18 +284,20 @@ export function InitiatorProposalApprovalModal({
<Dialog open={isOpen} onOpenChange={handleClose}> <Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="dealer-proposal-modal overflow-hidden flex flex-col"> <DialogContent className="dealer-proposal-modal overflow-hidden flex flex-col">
<DialogHeader className="flex-shrink-0 pb-3 lg:pb-4 px-6 pt-4 lg:pt-6 border-b"> <DialogHeader className="flex-shrink-0 pb-3 lg:pb-4 px-6 pt-4 lg:pt-6 border-b">
<DialogTitle className="flex items-center gap-2 text-lg lg:text-xl"> <DialogTitle className="flex items-center gap-2 text-lg lg:text-xl flex-wrap">
<CheckCircle className="w-4 h-4 lg:w-5 lg:h-5 text-green-600" /> <div className="flex items-center gap-2">
Requestor Evaluation & Confirmation <CheckCircle className="w-4 h-4 lg:w-5 lg:h-5 text-green-600" />
Requestor Evaluation & Confirmation
</div>
{taxationType && (
<Badge className={`ml-2 border-none shadow-sm ${!isNonGst ? 'bg-emerald-600 text-white hover:bg-emerald-700' : 'bg-indigo-600 text-white hover:bg-indigo-700'}`}>
{!isNonGst ? 'GST Claim' : 'Non-GST Claim'}
</Badge>
)}
</DialogTitle> </DialogTitle>
<DialogDescription className="text-xs lg:text-sm"> <DialogDescription className="text-xs lg:text-sm">
Step 2: Review dealer proposal and make a decision Step 2: Review dealer proposal and make a decision
</DialogDescription> </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="space-y-1 mt-2 text-xs text-gray-600">
<div className="flex flex-wrap gap-x-4 gap-y-1"> <div className="flex flex-wrap gap-x-4 gap-y-1">
<div> <div>
@ -615,26 +621,28 @@ export function InitiatorProposalApprovalModal({
<> <>
<div className="border rounded-lg overflow-hidden max-h-[200px] lg:max-h-[180px] overflow-y-auto"> <div className="border rounded-lg overflow-hidden max-h-[200px] lg:max-h-[180px] overflow-y-auto">
<div className="bg-gray-50 px-3 lg:px-4 py-2 border-b sticky top-0"> <div className="bg-gray-50 px-3 lg:px-4 py-2 border-b sticky top-0">
<div className="grid grid-cols-4 gap-4 text-xs lg:text-sm font-semibold text-gray-700"> <div className={`grid ${isNonGst ? 'grid-cols-3' : 'grid-cols-4'} gap-4 text-xs lg:text-sm font-semibold text-gray-700`}>
<div className="col-span-1">Item Description</div> <div className="col-span-1">Item Description</div>
<div className="text-right">Base</div> <div className="text-right">Base</div>
<div className="text-right">GST</div> {!isNonGst && <div className="text-right">GST</div>}
<div className="text-right">Total</div> <div className="text-right">Total</div>
</div> </div>
</div> </div>
<div className="divide-y"> <div className="divide-y">
{costBreakup.map((item: any, index: number) => ( {costBreakup.map((item: any, index: number) => (
<div key={item?.id || item?.description || index} className="px-3 lg:px-4 py-2 lg:py-3 grid grid-cols-4 gap-4"> <div key={item?.id || item?.description || index} className={`px-3 lg:px-4 py-2 lg:py-3 grid ${isNonGst ? 'grid-cols-3' : 'grid-cols-4'} gap-4`}>
<div className="col-span-1 text-xs lg:text-sm text-gray-700"> <div className="col-span-1 text-xs lg:text-sm text-gray-700">
{item?.description || 'N/A'} {item?.description || 'N/A'}
{item?.gstRate ? <span className="block text-[10px] text-gray-400">{item.gstRate}% GST</span> : null} {!isNonGst && item?.gstRate ? <span className="block text-[10px] text-gray-400">{item.gstRate}% GST</span> : null}
</div> </div>
<div className="text-xs lg:text-sm text-gray-900 text-right"> <div className="text-xs lg:text-sm text-gray-900 text-right">
{(Number(item?.amount) || 0).toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} {(Number(item?.amount) || 0).toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div> </div>
<div className="text-xs lg:text-sm text-gray-900 text-right"> {!isNonGst && (
{(Number(item?.gstAmt) || 0).toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} <div className="text-xs lg:text-sm text-gray-900 text-right">
</div> {(Number(item?.gstAmt) || 0).toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div>
)}
<div className="text-xs lg:text-sm font-semibold text-gray-900 text-right"> <div className="text-xs lg:text-sm font-semibold text-gray-900 text-right">
{(Number(item?.totalAmt || ((item?.amount || 0) * (item?.quantity || 1) + (item?.gstAmt || 0))) || 0).toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} {(Number(item?.totalAmt || ((item?.amount || 0) * (item?.quantity || 1) + (item?.gstAmt || 0))) || 0).toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div> </div>