Dealer_Onboard_Frontend/src/components/applications/ConstitutionalChangePage.tsx

711 lines
30 KiB
TypeScript

import { FileText, Calendar, Building, Plus, Eye, ArrowRight, Shield, Loader2 } from 'lucide-react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card';
import { Badge } from '../ui/badge';
import { Button } from '../ui/button';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../ui/table';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '../ui/dialog';
import { Input } from '../ui/input';
import { Label } from '../ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
import { Textarea } from '../ui/textarea';
import { useState, useEffect } from 'react';
import { User as UserType } from '../../lib/mock-data';
import { toast } from 'sonner';
import { API } from '../../api/API';
interface ConstitutionalChangePageProps {
currentUser: UserType | null;
onViewDetails: (id: string) => void;
}
// Document requirements mapping
const documentRequirements: Record<string, number[]> = {
'Partnership': [1, 2, 3, 4, 8, 9, 10, 16],
'LLP': [1, 2, 3, 7, 8, 9, 10, 16],
'Pvt Ltd': [1, 2, 3, 5, 6, 7, 8, 10, 16],
'Proprietorship': [1, 2, 3, 10, 16]
};
// Document names
const documentNames: Record<number, string> = {
1: 'GST',
2: 'Firm Pan Copy',
3: 'Self attested KYC\'s',
4: 'Partnership Agreement (Notarised)',
5: 'MOA (Applicable for Only Pvt.Ltd)',
6: 'AOA (Applicable for Only Pvt.Ltd)',
7: 'COI (Applicable for Only Pvt.Ltd & LLP)',
8: 'BPA - Business Purchase Agreement',
9: 'Firm Registration Certificate (Partnership)',
10: 'Cancelled Cheque',
11: 'LLP Agreement (Notarised)',
12: 'ZBH Approval',
13: 'NBH Approval',
14: 'RBM Approval',
15: 'DD-Lead Approval',
16: 'Declaration / Authorization Letter'
};
const getStatusColor = (status: string) => {
if (status === 'Completed') return 'bg-green-100 text-green-700 border-green-300';
if (status.includes('Review') || status.includes('Pending')) return 'bg-yellow-100 text-yellow-700 border-yellow-300';
if (status.includes('Rejected')) return 'bg-red-100 text-red-700 border-red-300';
if (status.includes('Collection')) return 'bg-blue-100 text-blue-700 border-blue-300';
return 'bg-slate-100 text-slate-700 border-slate-300';
};
const getTypeColor = (type: string) => {
switch(type) {
case 'Proprietorship': return 'bg-purple-100 text-purple-700 border-purple-300';
case 'Partnership': return 'bg-blue-100 text-blue-700 border-blue-300';
case 'LLP': return 'bg-indigo-100 text-indigo-700 border-indigo-300';
case 'Pvt Ltd': return 'bg-cyan-100 text-cyan-700 border-cyan-300';
default: return 'bg-slate-100 text-slate-700 border-slate-300';
}
};
export function ConstitutionalChangePage({ onViewDetails }: ConstitutionalChangePageProps) {
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [dealerCode, setDealerCode] = useState('');
const [dealerData, setDealerData] = useState<any>(null);
const [targetType, setTargetType] = useState('');
const [reason, setReason] = useState('');
const [requiredDocs, setRequiredDocs] = useState<number[]>([]);
const [requests, setRequests] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [isSubmitting, setIsSubmitting] = useState(false);
useEffect(() => {
fetchRequests();
}, []);
const fetchRequests = async () => {
try {
setIsLoading(true);
const response = await API.getConstitutionalChanges() as any;
if (response.data.success) {
setRequests(response.data.requests || []);
}
} catch (error) {
console.error('Fetch requests error:', error);
toast.error('Failed to fetch requests');
} finally {
setIsLoading(false);
}
};
const handleDealerCodeChange = async (code: string) => {
setDealerCode(code);
if (code.length >= 5) {
try {
const response = await API.getOutletByCode(code) as any;
if (response.data.success && response.data.outlet) {
const outlet = response.data.outlet;
setDealerData({
id: outlet.id,
dealerName: outlet.name,
address: outlet.address,
dealershipName: outlet.name,
gst: outlet.gstNumber || 'N/A',
currentType: outlet.type || 'Proprietorship',
region: outlet.region || 'N/A',
zone: outlet.zone || 'N/A'
});
toast.success('Dealer details loaded successfully');
} else {
setDealerData(null);
}
} catch (error) {
setDealerData(null);
}
} else {
setDealerData(null);
}
};
const handleTargetTypeChange = (type: string) => {
setTargetType(type);
setRequiredDocs(documentRequirements[type] || []);
};
const handleSubmitRequest = async (e: React.FormEvent) => {
e.preventDefault();
if (!dealerData) {
toast.error('Please enter a valid dealer code');
return;
}
if (!targetType) {
toast.error('Please select target dealership type');
return;
}
if (!reason.trim()) {
toast.error('Please provide a reason for constitutional change');
return;
}
if (dealerData.currentType === targetType) {
toast.error('Target type cannot be same as current type');
return;
}
try {
setIsSubmitting(true);
const payload = {
outletId: dealerData.id,
changeType: targetType,
description: reason,
newEntityDetails: {}
};
const response = await API.createConstitutionalChange(payload) as any;
if (response.data.success) {
toast.success('Constitutional change request submitted successfully');
setIsDialogOpen(false);
fetchRequests();
// Reset form
setDealerCode('');
setDealerData(null);
setTargetType('');
setReason('');
setRequiredDocs([]);
}
} catch (error) {
console.error('Submit request error:', error);
toast.error('Failed to submit request');
} finally {
setIsSubmitting(false);
}
};
// Statistics
const stats = [
{
title: 'Total Requests',
value: requests.length,
icon: FileText,
color: 'bg-blue-500',
},
{
title: 'In Progress',
value: requests.filter(r => r.status !== 'Completed' && !r.status.includes('Rejected')).length,
icon: Calendar,
color: 'bg-yellow-500',
},
{
title: 'Completed',
value: requests.filter(r => r.status === 'Completed').length,
icon: Shield,
color: 'bg-green-500',
},
{
title: 'Pending Action',
value: requests.filter(r => r.status.includes('Review') || r.status.includes('Pending')).length,
icon: Building,
color: 'bg-amber-500',
},
];
return (
<div className="space-y-6">
{/* Loading Overlay */}
{isLoading && (
<div className="fixed inset-0 bg-slate-900/20 backdrop-blur-sm z-50 flex items-center justify-center">
<Loader2 className="w-8 h-8 text-amber-600 animate-spin" />
</div>
)}
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-slate-900 mb-2">Constitutional Change Management</h1>
<p className="text-slate-600">
Manage dealership constitutional change requests - Adding/Removing partners or changing business structure
</p>
</div>
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogTrigger asChild>
<Button className="bg-amber-600 hover:bg-amber-700">
<Plus className="w-4 h-4 mr-2" />
New Request
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>Create Constitutional Change Request</DialogTitle>
<DialogDescription>
Submit a request for dealership constitutional change. All fields are mandatory.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmitRequest} className="space-y-4">
{/* Dealer Code */}
<div className="space-y-2">
<Label htmlFor="dealerCode">Dealer Code *</Label>
<Input
id="dealerCode"
placeholder="Enter dealer code (e.g., DL-MH-001)"
value={dealerCode}
onChange={(e) => handleDealerCodeChange(e.target.value)}
required
/>
</div>
{/* Auto-populated Dealer Details */}
{dealerData && (
<div className="bg-slate-50 border border-slate-200 rounded-lg p-4 space-y-3">
<h3 className="text-slate-900">Dealer Details</h3>
<div className="grid grid-cols-2 gap-3 text-sm">
<div>
<span className="text-slate-600">Dealer Name:</span>
<p className="text-slate-900">{dealerData.dealerName}</p>
</div>
<div>
<span className="text-slate-600">Dealership Name:</span>
<p className="text-slate-900">{dealerData.dealershipName}</p>
</div>
<div>
<span className="text-slate-600">Location:</span>
<p className="text-slate-900">{dealerData.address}</p>
</div>
<div>
<span className="text-slate-600">GST:</span>
<p className="text-slate-900">{dealerData.gst}</p>
</div>
<div>
<span className="text-slate-600">Current Type:</span>
<Badge className={getTypeColor(dealerData.currentType)}>
{dealerData.currentType}
</Badge>
</div>
<div>
<span className="text-slate-600">Region/Zone:</span>
<p className="text-slate-900">{dealerData.region} / {dealerData.zone}</p>
</div>
</div>
</div>
)}
{/* Target Dealership Type */}
<div className="space-y-2">
<Label htmlFor="targetType">Target Dealership Type *</Label>
<Select value={targetType} onValueChange={handleTargetTypeChange} required>
<SelectTrigger>
<SelectValue placeholder="Select target dealership type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Proprietorship">Proprietorship</SelectItem>
<SelectItem value="Partnership">Partnership</SelectItem>
<SelectItem value="LLP">LLP (Limited Liability Partnership)</SelectItem>
<SelectItem value="Pvt Ltd">Pvt Ltd (Private Limited)</SelectItem>
</SelectContent>
</Select>
{dealerData && targetType && dealerData.currentType === targetType && (
<p className="text-red-600 text-sm">Target type cannot be same as current type</p>
)}
</div>
{/* Required Documents Display */}
{targetType && requiredDocs.length > 0 && (
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 space-y-2">
<h4 className="text-blue-900">Required Documents for {targetType}</h4>
<div className="grid grid-cols-1 gap-2">
{requiredDocs.map((docNum) => (
<div key={docNum} className="flex items-start gap-2 text-sm">
<span className="text-blue-600 font-medium">{docNum}.</span>
<span className="text-blue-800">{documentNames[docNum]}</span>
</div>
))}
</div>
</div>
)}
{/* Reason */}
<div className="space-y-2">
<Label htmlFor="reason">Reason for Constitutional Change *</Label>
<Textarea
id="reason"
placeholder="Provide detailed reason for the constitutional change request..."
value={reason}
onChange={(e) => setReason(e.target.value)}
rows={4}
required
/>
</div>
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => setIsDialogOpen(false)}
>
Cancel
</Button>
<Button
type="submit"
className="bg-amber-600 hover:bg-amber-700"
disabled={!dealerData || !targetType || (dealerData && dealerData.currentType === targetType) || isSubmitting}
>
{isSubmitting ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Submitting...
</>
) : (
'Submit Request'
)}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</div>
{/* Statistics Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{stats.map((stat, index) => {
const Icon = stat.icon;
return (
<Card key={index}>
<CardContent className="p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-slate-600 text-sm">{stat.title}</p>
<p className="text-slate-900 text-2xl mt-1">{stat.value}</p>
</div>
<div className={`${stat.color} w-12 h-12 rounded-lg flex items-center justify-center`}>
<Icon className="w-6 h-6 text-white" />
</div>
</div>
</CardContent>
</Card>
);
})}
</div>
{/* Requests Table */}
<Card>
<CardHeader>
<CardTitle>Constitutional Change Requests</CardTitle>
<CardDescription>
Track and manage all constitutional change requests across all stages
</CardDescription>
</CardHeader>
<CardContent>
<Tabs defaultValue="all" className="w-full">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="all">All Requests</TabsTrigger>
<TabsTrigger value="pending">Pending</TabsTrigger>
<TabsTrigger value="in-progress">In Progress</TabsTrigger>
<TabsTrigger value="completed">Completed</TabsTrigger>
</TabsList>
<TabsContent value="all" className="mt-4">
<div className="border border-slate-200 rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow className="bg-slate-50">
<TableHead>Request ID</TableHead>
<TableHead>Dealer Details</TableHead>
<TableHead>Constitutional Change</TableHead>
<TableHead>Current Stage</TableHead>
<TableHead>Progress</TableHead>
<TableHead>Submitted On</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{requests.length === 0 ? (
<TableRow>
<TableCell colSpan={7} className="text-center py-8 text-slate-500">
No constitutional change requests found
</TableCell>
</TableRow>
) : (
requests.map((request: any) => (
<TableRow key={request.requestId}>
<TableCell>
<div className="font-medium text-slate-900">{request.requestId}</div>
<div className="text-slate-600 text-sm">{request.outlet?.code || 'N/A'}</div>
</TableCell>
<TableCell>
<div className="font-medium text-slate-900">{request.outlet?.name || 'N/A'}</div>
<div className="text-slate-600 text-sm">{request.outlet?.city || request.outlet?.address || 'N/A'}</div>
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Badge className={getTypeColor(request.outlet?.type || 'Proprietorship')}>
{request.outlet?.type || 'Proprietorship'}
</Badge>
<ArrowRight className="w-4 h-4 text-slate-400" />
<Badge className={getTypeColor(request.changeType)}>
{request.changeType}
</Badge>
</div>
</TableCell>
<TableCell>
<Badge variant="outline" className="border-slate-300 text-slate-700">
{request.currentStage}
</Badge>
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<div className="flex-1 h-2 bg-slate-200 rounded-full overflow-hidden">
<div
className="h-full bg-amber-600 transition-all duration-300"
style={{ width: `${request.progressPercentage || 0}%` }}
/>
</div>
<span className="text-slate-600 text-sm">{request.progressPercentage || 0}%</span>
</div>
</TableCell>
<TableCell>
<div className="text-slate-900">{new Date(request.createdAt).toLocaleDateString()}</div>
<div className="text-slate-600 text-sm">By {request.dealer?.fullName || 'Dealer'}</div>
</TableCell>
<TableCell>
<Button
size="sm"
variant="outline"
onClick={() => onViewDetails(request.requestId)}
>
<Eye className="w-4 h-4 mr-1" />
View
</Button>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
</TabsContent>
<TabsContent value="pending" className="mt-4">
<div className="border border-slate-200 rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow className="bg-slate-50">
<TableHead>Request ID</TableHead>
<TableHead>Dealer Details</TableHead>
<TableHead>Constitutional Change</TableHead>
<TableHead>Current Stage</TableHead>
<TableHead>Status</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{requests
.filter((r: any) => r.status.includes('Review') || r.status.includes('Pending'))
.map((request: any) => (
<TableRow key={request.requestId}>
<TableCell>
<div className="font-medium text-slate-900">{request.requestId}</div>
<div className="text-slate-600 text-sm">{request.outlet?.code || 'N/A'}</div>
</TableCell>
<TableCell>
<div className="font-medium text-slate-900">{request.outlet?.name || 'N/A'}</div>
<div className="text-slate-600 text-sm">{request.outlet?.city || 'N/A'}</div>
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Badge className={getTypeColor(request.outlet?.type || 'Proprietorship')}>
{request.outlet?.type || 'Proprietorship'}
</Badge>
<ArrowRight className="w-4 h-4 text-slate-400" />
<Badge className={getTypeColor(request.changeType)}>
{request.changeType}
</Badge>
</div>
</TableCell>
<TableCell>
<Badge variant="outline" className="border-slate-300 text-slate-700">
{request.currentStage}
</Badge>
</TableCell>
<TableCell>
<Badge className={getStatusColor(request.status)}>
{request.status}
</Badge>
</TableCell>
<TableCell>
<Button
size="sm"
variant="outline"
onClick={() => onViewDetails(request.requestId)}
>
<Eye className="w-4 h-4 mr-1" />
View
</Button>
</TableCell>
</TableRow>
))}
{requests.filter((r: any) => r.status.includes('Review') || r.status.includes('Pending')).length === 0 && (
<TableRow>
<TableCell colSpan={6} className="text-center py-8 text-slate-500">
No pending requests found
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</TabsContent>
<TabsContent value="in-progress" className="mt-4">
<div className="border border-slate-200 rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow className="bg-slate-50">
<TableHead>Request ID</TableHead>
<TableHead>Dealer Details</TableHead>
<TableHead>Constitutional Change</TableHead>
<TableHead>Progress</TableHead>
<TableHead>Current Stage</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{requests
.filter((r: any) => r.status !== 'Completed' && !r.status.includes('Rejected'))
.map((request: any) => (
<TableRow key={request.requestId}>
<TableCell>
<div className="font-medium text-slate-900">{request.requestId}</div>
<div className="text-slate-600 text-sm">{request.outlet?.code || 'N/A'}</div>
</TableCell>
<TableCell>
<div className="font-medium text-slate-900">{request.outlet?.name || 'N/A'}</div>
<div className="text-slate-600 text-sm">{request.outlet?.city || 'N/A'}</div>
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Badge className={getTypeColor(request.outlet?.type || 'Proprietorship')}>
{request.outlet?.type || 'Proprietorship'}
</Badge>
<ArrowRight className="w-4 h-4 text-slate-400" />
<Badge className={getTypeColor(request.changeType)}>
{request.changeType}
</Badge>
</div>
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<div className="flex-1 h-2 bg-slate-200 rounded-full overflow-hidden">
<div
className="h-full bg-amber-600 transition-all duration-300"
style={{ width: `${request.progressPercentage || 0}%` }}
/>
</div>
<span className="text-slate-600 text-sm">{request.progressPercentage || 0}%</span>
</div>
</TableCell>
<TableCell>
<Badge variant="outline" className="border-slate-300 text-slate-700">
{request.currentStage}
</Badge>
</TableCell>
<TableCell>
<Button
size="sm"
variant="outline"
onClick={() => onViewDetails(request.requestId)}
>
<Eye className="w-4 h-4 mr-1" />
View
</Button>
</TableCell>
</TableRow>
))}
{requests.filter((r: any) => r.status !== 'Completed' && !r.status.includes('Rejected')).length === 0 && (
<TableRow>
<TableCell colSpan={6} className="text-center py-8 text-slate-500">
No in-progress requests found
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</TabsContent>
<TabsContent value="completed" className="mt-4">
<div className="border border-slate-200 rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow className="bg-slate-50">
<TableHead>Request ID</TableHead>
<TableHead>Dealer Details</TableHead>
<TableHead>Constitutional Change</TableHead>
<TableHead>Status</TableHead>
<TableHead>Submitted On</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{requests
.filter((r: any) => r.status === 'Completed' || r.status === 'Closed')
.map((request: any) => (
<TableRow key={request.requestId}>
<TableCell>
<div className="font-medium text-slate-900">{request.requestId}</div>
<div className="text-slate-600 text-sm">{request.outlet?.code || 'N/A'}</div>
</TableCell>
<TableCell>
<div className="font-medium text-slate-900">{request.outlet?.name || 'N/A'}</div>
<div className="text-slate-600 text-sm">{request.outlet?.city || 'N/A'}</div>
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Badge className={getTypeColor(request.outlet?.type || 'Proprietorship')}>
{request.outlet?.type || 'Proprietorship'}
</Badge>
<ArrowRight className="w-4 h-4 text-slate-400" />
<Badge className={getTypeColor(request.changeType)}>
{request.changeType}
</Badge>
</div>
</TableCell>
<TableCell>
<Badge className={getStatusColor(request.status)}>
{request.status}
</Badge>
</TableCell>
<TableCell>
<div className="text-slate-900">{new Date(request.createdAt).toLocaleDateString()}</div>
</TableCell>
<TableCell>
<Button
size="sm"
variant="outline"
onClick={() => onViewDetails(request.requestId)}
>
<Eye className="w-4 h-4 mr-1" />
View
</Button>
</TableCell>
</TableRow>
))}
{requests.filter((r: any) => r.status === 'Completed' || r.status === 'Closed').length === 0 && (
<TableRow>
<TableCell colSpan={6} className="text-center py-8 text-slate-500">
No completed requests found
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
</div>
);
}