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,
DialogTitle,
} from '@/components/ui/dialog';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
FileText,
Plus,
@ -89,16 +96,17 @@ export function ActivityTypeManager() {
try {
setError(null);
if (!formData.title.trim()) {
setError('Activity type title is required');
if (!formData.title.trim() || !formData.taxationType.trim() || !formData.sapRefNo.trim()) {
setError('Title, Taxation Type, and Claim Document Type (SAP Ref) are required');
toast.error('Please fill in all mandatory fields');
return;
}
const payload: Partial<ActivityType> = {
title: formData.title.trim(),
itemCode: formData.itemCode.trim() || null,
taxationType: formData.taxationType.trim() || null,
sapRefNo: formData.sapRefNo.trim() || null
taxationType: formData.taxationType.trim(),
sapRefNo: formData.sapRefNo.trim()
};
if (editingActivityType) {
@ -397,32 +405,37 @@ export function ActivityTypeManager() {
{/* Taxation Type Field */}
<div className="space-y-2">
<Label htmlFor="taxationType" className="text-sm font-semibold text-slate-900">
Taxation Type <span className="text-slate-400 font-normal text-xs">(Optional)</span>
<Label htmlFor="taxationType" className="text-sm font-semibold text-slate-900 flex items-center gap-1">
Taxation Type <span className="text-red-500">*</span>
</Label>
<Input
id="taxationType"
placeholder="e.g., GST, VAT, Exempt"
<Select
value={formData.taxationType}
onChange={(e) => setFormData({ ...formData, taxationType: 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"
/>
<p className="text-xs text-slate-500">Optional taxation type for the activity</p>
onValueChange={(value) => setFormData({ ...formData, taxationType: value })}
>
<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">
<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>
{/* SAP Reference Number Field */}
<div className="space-y-2">
<Label htmlFor="sapRefNo" className="text-sm font-semibold text-slate-900">
SAP Reference Number <span className="text-slate-400 font-normal text-xs">(Optional)</span>
<Label htmlFor="sapRefNo" className="text-sm font-semibold text-slate-900 flex items-center gap-1">
Claim Document Type (SAP Ref) <span className="text-red-500">*</span>
</Label>
<Input
id="sapRefNo"
placeholder="e.g., SAP-12345"
placeholder="e.g., ZCNS, ZRE"
value={formData.sapRefNo}
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"
/>
<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>
@ -436,7 +449,7 @@ export function ActivityTypeManager() {
</Button>
<Button
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"
>
<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}
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}
taxationType={request?.claimDetails?.taxationType}
/>
{/* Dealer Completion Documents Modal */}
@ -2562,12 +2563,14 @@ export function DealerClaimWorkflowTab({
completionDocuments={completionDocumentsData}
requestTitle={request?.title}
requestNumber={request?.requestNumber || request?.request_number || request?.id}
taxationType={request?.claimDetails?.taxationType}
/>
{/* Credit Note from SAP Modal (Step 8) */}
<CreditNoteSAPModal
isOpen={showCreditNoteModal}
onClose={() => setShowCreditNoteModal(false)}
taxationType={request?.claimDetails?.taxationType}
onDownload={async () => {
toast.info('Download functionality will be implemented');
}}

View File

@ -40,6 +40,7 @@ interface CreditNoteSAPModalProps {
requestNumber?: string;
requestId?: string;
dueDate?: string;
taxationType?: string | null;
}
export function CreditNoteSAPModal({
@ -53,10 +54,13 @@ export function CreditNoteSAPModal({
requestNumber,
requestId: _requestId,
dueDate,
taxationType,
}: CreditNoteSAPModalProps) {
const [downloading, setDownloading] = useState(false);
const [sending, setSending] = useState(false);
const isNonGst = taxationType === 'Non GST' || taxationType === 'Non-GST';
const hasCreditNote = creditNoteData?.creditNoteNumber && creditNoteData?.creditNoteNumber !== '';
const creditNoteNumber = creditNoteData?.creditNoteNumber || '';
const creditNoteDate = creditNoteData?.creditNoteDate
@ -118,9 +122,16 @@ export function CreditNoteSAPModal({
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="sm:max-w-lg lg:max-w-[1000px] max-w-4xl max-h-[90vh] overflow-y-auto">
<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">
<div className="flex items-center gap-2">
<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>
<DialogDescription className="text-base">
Review and send credit note to dealer

View File

@ -85,6 +85,7 @@ interface DMSPushModalProps {
completionDocuments?: CompletionDocuments | null;
requestTitle?: string;
requestNumber?: string;
taxationType?: string | null;
}
export function DMSPushModal({
@ -96,7 +97,12 @@ export function DMSPushModal({
completionDocuments,
requestTitle,
requestNumber,
taxationType,
}: DMSPushModalProps) {
const isNonGst = useMemo(() => {
return taxationType === 'Non GST' || taxationType === 'Non-GST';
}, [taxationType]);
const [comments, setComments] = useState('');
const [submitting, setSubmitting] = useState(false);
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" />
</div>
<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
{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>
<DialogDescription className="text-xs sm:text-sm mt-1">
Review completion details and expenses before generating e-invoice and initiating SAP settlement
@ -402,10 +413,14 @@ export function DMSPushModal({
<CardContent>
{/* 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="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>
{!isNonGst && (
<>
<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>
@ -428,11 +443,13 @@ export function DMSPushModal({
</div>
<div className="sm:hidden flex justify-between w-full text-xs text-gray-500">
<span>Base: {formatCurrency(amount)}</span>
{!isNonGst && (
<span>GST: {gstRate}% ({formatCurrency(gstAmt)})</span>
)}
</div>
{/* 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}>
{expense.description || `Expense ${index + 1}`}
</p>
@ -440,12 +457,16 @@ export function DMSPushModal({
<div className="hidden sm:block col-span-2 text-right text-gray-600">
{formatCurrency(amount)}
</div>
{!isNonGst && (
<>
<div className="hidden sm:block col-span-2 text-right text-gray-600">
{gstRate}%
</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">
{formatCurrency(total)}
</div>

View File

@ -96,6 +96,10 @@ export function DealerCompletionDocumentsModal({
return getActiveTaxComponents(stateCode);
}, [dealerGSTIN]);
const isNonGst = useMemo(() => {
return taxationType === 'Non GST' || taxationType === 'Non-GST';
}, [taxationType]);
const [expenseItems, setExpenseItems] = useState<ExpenseItem[]>([]);
const [completionDocuments, setCompletionDocuments] = useState<File[]>([]);
const [activityPhotos, setActivityPhotos] = useState<File[]>([]);
@ -225,13 +229,13 @@ export function DealerCompletionDocumentsModal({
const hasPhotos = activityPhotos.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);
return !isValid;
});
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
const maxDate = new Date().toISOString().split('T')[0];
@ -524,8 +528,8 @@ export function DealerCompletionDocumentsModal({
(item) => item.description.trim() !== '' && item.amount > 0
);
// Validation: Alert for 0% GST on taxable items
const hasZeroGstItems = validExpenses.some(item =>
// Validation: Alert for 0% GST on taxable items (Skip for Non-GST)
const hasZeroGstItems = !isNonGst && validExpenses.some(item =>
item.description.trim() !== '' && item.amount > 0 && (item.gstRate === 0 || !item.gstRate)
);
@ -616,15 +620,17 @@ export function DealerCompletionDocumentsModal({
<Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="dealer-completion-documents-modal overflow-hidden flex flex-col">
<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">
<div className="flex items-center gap-2">
<Upload className="w-5 h-5 sm:w-6 sm:h-6 text-[--re-green]" />
Activity Completion Documents
</DialogTitle>
</div>
{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'}`}>
<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>
<DialogDescription className="text-sm sm:text-base">
Step 5: Upload completion proof and final documents
</DialogDescription>
@ -666,9 +672,11 @@ export function DealerCompletionDocumentsModal({
<h3 className="font-semibold text-base sm:text-lg">Closed Expenses</h3>
<Badge className="bg-secondary text-secondary-foreground text-xs">Optional</Badge>
</div>
{!isNonGst && (
<div className="text-[10px] text-gray-500 italic mt-0.5">
Tax fields are automatically toggled based on the dealer's state (Inter-state vs Intra-state).
</div>
)}
<Button
type="button"
onClick={handleAddExpense}
@ -683,7 +691,7 @@ export function DealerCompletionDocumentsModal({
{expenseItems.map((item) => (
<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-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>
<Input
placeholder="e.g., Venue rental, Refreshments"
@ -694,6 +702,26 @@ export function DealerCompletionDocumentsModal({
className="w-full bg-white text-sm"
/>
</div>
{isNonGst && (
<div className="w-28 sm:w-36 flex-shrink-0">
<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"
placeholder="0.00"
min="0"
step="0.01"
value={item.amount || ''}
onChange={(e) =>
handleExpenseChange(item.id, 'amount', e.target.value)
}
className="w-full bg-white text-sm pl-8"
/>
</div>
</div>
)}
{!isNonGst && (
<div className="w-28 sm:w-36 flex-shrink-0">
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Amount (Base)</Label>
<div className="relative">
@ -711,6 +739,9 @@ export function DealerCompletionDocumentsModal({
/>
</div>
</div>
)}
{!isNonGst && (
<>
<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>
<Input
@ -804,6 +835,8 @@ export function DealerCompletionDocumentsModal({
className="w-full bg-white text-xs px-1 text-center disabled:bg-gray-100 disabled:text-gray-400"
/>
</div>
</>
)}
<Button
type="button"
variant="ghost"
@ -815,34 +848,26 @@ export function DealerCompletionDocumentsModal({
</Button>
</div>
<div className="grid grid-cols-2 sm:grid-cols-5 gap-3 pt-3 border-t border-dashed border-gray-200">
<div className="flex flex-col">
<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={`grid grid-cols-2 sm:grid-cols-5 gap-3 pt-3 border-t border-dashed border-gray-200 ${isNonGst ? 'items-center' : ''}`}>
{!isNonGst ? (
<>
<div className="flex flex-wrap gap-4 text-gray-500 font-medium">
<span>CGST: <span className="text-gray-900 font-semibold">{(item.cgstAmt || 0).toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span>
{item.sgstAmt > 0 && <span>SGST: <span className="text-gray-900 font-semibold">{(item.sgstAmt || 0).toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span>}
{item.utgstAmt > 0 && <span>UTGST: <span className="text-gray-900 font-semibold">{(item.utgstAmt || 0).toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span>}
<span>IGST: <span className="text-gray-900 font-semibold">{(item.igstAmt || 0).toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span>
</div>
{item.sgstAmt > 0 && (
<div className="flex flex-col">
<span className="text-[10px] text-gray-500 uppercase">SGST</span>
<span className="text-xs font-semibold">{item.sgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 2 })}</span>
<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">
<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>

View File

@ -96,6 +96,10 @@ export function DealerProposalSubmissionModal({
return getActiveTaxComponents(stateCode);
}, [dealerGSTIN]);
const isNonGst = useMemo(() => {
return taxationType === 'Non GST' || taxationType === 'Non-GST';
}, [taxationType]);
const [costItems, setCostItems] = useState<CostItem[]>([
{
id: '1',
@ -260,13 +264,13 @@ export function DealerProposalSubmissionModal({
: numberOfDays !== '' && parseInt(numberOfDays) > 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);
return !isValid;
});
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 file = e.target.files?.[0];
@ -462,8 +466,8 @@ export function DealerProposalSubmissionModal({
try {
setSubmitting(true);
// Validation: Alert for 0% GST on taxable items
const hasZeroGstItems = costItems.some(item =>
// Validation: Alert for 0% GST on taxable items (Skip for Non-GST)
const hasZeroGstItems = !isNonGst && costItems.some(item =>
item.description.trim() !== '' && item.amount > 0 && (item.gstRate === 0 || !item.gstRate)
);
@ -546,15 +550,17 @@ export function DealerProposalSubmissionModal({
<Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="dealer-proposal-modal overflow-hidden flex flex-col">
<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">
<div className="flex items-center gap-2">
<Upload className="w-5 h-5 lg:w-6 lg:h-6 text-[--re-green]" />
Dealer Proposal Submission
</DialogTitle>
</div>
{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'}`}>
<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>
<DialogDescription className="text-sm lg:text-base">
Step 1: Upload proposal and planning details
</DialogDescription>
@ -824,9 +830,11 @@ export function DealerProposalSubmissionModal({
<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>
</div>
{!isNonGst && (
<div className="text-[10px] text-gray-500 italic mt-0.5">
Tax fields are automatically toggled based on the dealer's state (Inter-state vs Intra-state).
</div>
)}
<Button
type="button"
onClick={handleAddCostItem}
@ -844,7 +852,7 @@ export function DealerProposalSubmissionModal({
<div className="flex flex-col gap-4">
{/* Row 1: Description and Close */}
<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>
<Input
placeholder="e.g., Venue branding, Logistics, etc."
@ -853,6 +861,20 @@ export function DealerProposalSubmissionModal({
className="w-full bg-white shadow-sm"
/>
</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
type="button"
variant="ghost"
@ -865,7 +887,7 @@ export function DealerProposalSubmissionModal({
</Button>
</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-1 min-w-[140px]">
<Label className="text-[10px] uppercase text-gray-500 font-bold mb-1 block">Amount (Base)</Label>
@ -939,8 +961,9 @@ export function DealerProposalSubmissionModal({
/>
</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 gap-4 text-gray-500 font-medium">
<span>CGST: <span className="text-gray-900">{item.cgstAmt.toLocaleString('en-IN', { minimumFractionDigits: 1 })}</span></span>
@ -953,6 +976,7 @@ export function DealerProposalSubmissionModal({
<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>
))}
@ -966,7 +990,7 @@ export function DealerProposalSubmissionModal({
</div>
<div>
<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 className="text-3xl font-bold border-l border-white/20 pl-6">

View File

@ -37,6 +37,7 @@ interface DeptLeadIOApprovalModalProps {
preFilledIONumber?: string;
preFilledBlockedAmount?: number;
preFilledRemainingBalance?: number;
taxationType?: string | null;
}
export function DeptLeadIOApprovalModal({
@ -49,11 +50,16 @@ export function DeptLeadIOApprovalModal({
preFilledIONumber,
preFilledBlockedAmount,
preFilledRemainingBalance,
taxationType,
}: DeptLeadIOApprovalModalProps) {
const [actionType, setActionType] = useState<'approve' | 'reject'>('approve');
const [comments, setComments] = useState('');
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)
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" />
</div>
<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
{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>
<DialogDescription className="text-xs lg:text-sm mt-1">
Review IO details and provide your approval comments
@ -174,8 +185,7 @@ export function DeptLeadIOApprovalModal({
<Button
type="button"
onClick={() => setActionType('approve')}
className={`flex-1 text-sm lg:text-base ${
actionType === 'approve'
className={`flex-1 text-sm lg:text-base ${actionType === 'approve'
? 'bg-green-600 text-white shadow-sm'
: 'text-gray-700 hover:bg-gray-200'
}`}
@ -187,8 +197,7 @@ export function DeptLeadIOApprovalModal({
<Button
type="button"
onClick={() => setActionType('reject')}
className={`flex-1 text-sm lg:text-base ${
actionType === 'reject'
className={`flex-1 text-sm lg:text-base ${actionType === 'reject'
? 'bg-red-600 text-white shadow-sm'
: 'text-gray-700 hover:bg-gray-200'
}`}
@ -309,8 +318,7 @@ export function DeptLeadIOApprovalModal({
<Button
onClick={handleSubmit}
disabled={!isFormValid || submitting}
className={`text-sm lg:text-base ${
actionType === 'approve'
className={`text-sm lg:text-base ${actionType === 'approve'
? 'bg-green-600 hover:bg-green-700'
: 'bg-red-600 hover:bg-red-700'
} text-white`}

View File

@ -88,6 +88,10 @@ export function InitiatorProposalApprovalModal({
previousProposalData,
taxationType,
}: InitiatorProposalApprovalModalProps) {
const isNonGst = useMemo(() => {
return taxationType === 'Non GST' || taxationType === 'Non-GST';
}, [taxationType]);
const [comments, setComments] = useState('');
const [submitting, setSubmitting] = useState(false);
const [actionType, setActionType] = useState<'approve' | 'reject' | 'revision' | null>(null);
@ -280,18 +284,20 @@ export function InitiatorProposalApprovalModal({
<Dialog open={isOpen} onOpenChange={handleClose}>
<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">
<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">
<div className="flex items-center gap-2">
<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>
<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>
@ -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="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="text-right">Base</div>
<div className="text-right">GST</div>
{!isNonGst && <div className="text-right">GST</div>}
<div className="text-right">Total</div>
</div>
</div>
<div className="divide-y">
{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">
{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 className="text-xs lg:text-sm text-gray-900 text-right">
{(Number(item?.amount) || 0).toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div>
{!isNonGst && (
<div className="text-xs lg:text-sm text-gray-900 text-right">
{(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">
{(Number(item?.totalAmt || ((item?.amount || 0) * (item?.quantity || 1) + (item?.gstAmt || 0))) || 0).toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div>