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,7 +12,14 @@ import {
|
|||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import {
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import {
|
||||||
FileText,
|
FileText,
|
||||||
Plus,
|
Plus,
|
||||||
Trash2,
|
Trash2,
|
||||||
@ -21,12 +28,12 @@ import {
|
|||||||
AlertCircle,
|
AlertCircle,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
getAllActivityTypes,
|
getAllActivityTypes,
|
||||||
createActivityType,
|
createActivityType,
|
||||||
updateActivityType,
|
updateActivityType,
|
||||||
deleteActivityType,
|
deleteActivityType,
|
||||||
ActivityType
|
ActivityType
|
||||||
} from '@/services/adminApi';
|
} from '@/services/adminApi';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
@ -88,17 +95,18 @@ export function ActivityTypeManager() {
|
|||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
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) {
|
||||||
@ -165,9 +173,9 @@ export function ActivityTypeManager() {
|
|||||||
<AlertCircle className="w-4 h-4 text-white shrink-0" />
|
<AlertCircle className="w-4 h-4 text-white shrink-0" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm font-medium text-red-900">{error}</p>
|
<p className="text-sm font-medium text-red-900">{error}</p>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => setError(null)}
|
onClick={() => setError(null)}
|
||||||
className="ml-auto hover:bg-red-100"
|
className="ml-auto hover:bg-red-100"
|
||||||
>
|
>
|
||||||
@ -191,8 +199,8 @@ export function ActivityTypeManager() {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleAdd}
|
onClick={handleAdd}
|
||||||
className="gap-2 bg-re-green hover:bg-re-green/90 text-white shadow-sm"
|
className="gap-2 bg-re-green hover:bg-re-green/90 text-white shadow-sm"
|
||||||
>
|
>
|
||||||
<Plus className="w-4 h-4" />
|
<Plus className="w-4 h-4" />
|
||||||
@ -216,9 +224,9 @@ export function ActivityTypeManager() {
|
|||||||
</div>
|
</div>
|
||||||
<p className="text-slate-700 font-medium text-lg">No activity types found</p>
|
<p className="text-slate-700 font-medium text-lg">No activity types found</p>
|
||||||
<p className="text-sm text-slate-500 mt-2 mb-6">Add activity types for dealer claim management</p>
|
<p className="text-sm text-slate-500 mt-2 mb-6">Add activity types for dealer claim management</p>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleAdd}
|
onClick={handleAdd}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="gap-2 border-slate-300 hover:bg-slate-50 hover:border-slate-400 shadow-sm"
|
className="gap-2 border-slate-300 hover:bg-slate-50 hover:border-slate-400 shadow-sm"
|
||||||
>
|
>
|
||||||
<Plus className="w-4 h-4" />
|
<Plus className="w-4 h-4" />
|
||||||
@ -245,7 +253,7 @@ export function ActivityTypeManager() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3 pt-4">
|
<CardContent className="space-y-3 pt-4">
|
||||||
{activeActivityTypes.map(activityType => (
|
{activeActivityTypes.map(activityType => (
|
||||||
<div
|
<div
|
||||||
key={activityType.activityTypeId}
|
key={activityType.activityTypeId}
|
||||||
className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 sm:gap-4 p-3 sm:p-4 bg-slate-50 border border-slate-200 rounded-md hover:bg-slate-100 hover:border-slate-300 transition-all shadow-sm"
|
className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 sm:gap-4 p-3 sm:p-4 bg-slate-50 border border-slate-200 rounded-md hover:bg-slate-100 hover:border-slate-300 transition-all shadow-sm"
|
||||||
>
|
>
|
||||||
@ -314,7 +322,7 @@ export function ActivityTypeManager() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3 pt-4">
|
<CardContent className="space-y-3 pt-4">
|
||||||
{inactiveActivityTypes.map(activityType => (
|
{inactiveActivityTypes.map(activityType => (
|
||||||
<div
|
<div
|
||||||
key={activityType.activityTypeId}
|
key={activityType.activityTypeId}
|
||||||
className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 sm:gap-4 p-3 sm:p-4 bg-amber-50/50 border border-amber-200 rounded-md hover:bg-amber-50 hover:border-amber-300 transition-all shadow-sm"
|
className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 sm:gap-4 p-3 sm:p-4 bg-amber-50/50 border border-amber-200 rounded-md hover:bg-amber-50 hover:border-amber-300 transition-all shadow-sm"
|
||||||
>
|
>
|
||||||
@ -397,46 +405,51 @@ 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>
|
||||||
|
|
||||||
<DialogFooter className="gap-3 pt-4 border-t border-slate-100 px-6 pb-6 flex-shrink-0">
|
<DialogFooter className="gap-3 pt-4 border-t border-slate-100 px-6 pb-6 flex-shrink-0">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => setShowAddDialog(false)}
|
onClick={() => setShowAddDialog(false)}
|
||||||
className="h-11 border-slate-300 hover:bg-slate-50 hover:border-slate-400 shadow-sm"
|
className="h-11 border-slate-300 hover:bg-slate-50 hover:border-slate-400 shadow-sm"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</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" />
|
||||||
|
|||||||
@ -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');
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -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,13 +54,16 @@ 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
|
||||||
? formatDateTime(creditNoteData.creditNoteDate, { includeTime: false, format: 'short' })
|
? formatDateTime(creditNoteData.creditNoteDate, { includeTime: false, format: 'short' })
|
||||||
: '';
|
: '';
|
||||||
const creditNoteAmount = creditNoteData?.creditNoteAmount || 0;
|
const creditNoteAmount = creditNoteData?.creditNoteAmount || 0;
|
||||||
@ -69,7 +73,7 @@ export function CreditNoteSAPModal({
|
|||||||
const dealerCode = dealerInfo?.dealerCode || 'RE-JP-009';
|
const dealerCode = dealerInfo?.dealerCode || 'RE-JP-009';
|
||||||
const activity = activityName || 'Activity';
|
const activity = activityName || 'Activity';
|
||||||
const requestIdDisplay = requestNumber || 'RE-REQ-2024-CM-101';
|
const requestIdDisplay = requestNumber || 'RE-REQ-2024-CM-101';
|
||||||
const dueDateDisplay = dueDate
|
const dueDateDisplay = dueDate
|
||||||
? formatDateTime(dueDate, { includeTime: false, format: 'short' })
|
? formatDateTime(dueDate, { includeTime: false, format: 'short' })
|
||||||
: 'Jan 4, 2026';
|
: 'Jan 4, 2026';
|
||||||
|
|
||||||
@ -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
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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 */}
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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 || '';
|
||||||
|
|
||||||
@ -97,7 +103,7 @@ export function DeptLeadIOApprovalModal({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
|
|
||||||
if (actionType === 'approve') {
|
if (actionType === 'approve') {
|
||||||
await onApprove({
|
await onApprove({
|
||||||
ioNumber: ioNumber.trim(),
|
ioNumber: ioNumber.trim(),
|
||||||
@ -106,7 +112,7 @@ export function DeptLeadIOApprovalModal({
|
|||||||
} else {
|
} else {
|
||||||
await onReject(comments.trim());
|
await onReject(comments.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReset();
|
handleReset();
|
||||||
onClose();
|
onClose();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -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" />
|
||||||
@ -208,7 +217,7 @@ export function DeptLeadIOApprovalModal({
|
|||||||
<Receipt className="w-4 h-4 lg:w-5 lg:h-5 text-blue-600" />
|
<Receipt className="w-4 h-4 lg:w-5 lg:h-5 text-blue-600" />
|
||||||
<h4 className="font-semibold text-sm lg:text-base text-blue-900">IO Organisation Details</h4>
|
<h4 className="font-semibold text-sm lg:text-base text-blue-900">IO Organisation Details</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* IO Number - Read-only from IO table */}
|
{/* IO Number - Read-only from IO table */}
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label htmlFor="ioNumber" className="text-xs lg:text-sm font-semibold text-gray-900 flex items-center gap-2">
|
<Label htmlFor="ioNumber" className="text-xs lg:text-sm font-semibold text-gray-900 flex items-center gap-2">
|
||||||
@ -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'}...`
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user