added gst non gst lable hided hsn and gst related fields for the non-gst claims
This commit is contained in:
parent
170f9a1788
commit
b04776a5f8
@ -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" />
|
||||
|
||||
@ -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');
|
||||
}}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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`}
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user