477 lines
20 KiB
TypeScript
477 lines
20 KiB
TypeScript
import { FileText, Plus, Eye, Calendar, User, Building2, Store, MapPin, CheckCircle, Clock, RefreshCcw, Loader2 } from 'lucide-react';
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card';
|
|
import { Badge } from '../ui/badge';
|
|
import { Button } from '../ui/button';
|
|
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 { dealerService } from '../../services/dealer.service';
|
|
import { resignationService } from '../../services/resignation.service';
|
|
|
|
interface DealerResignationPageProps {
|
|
currentUser: UserType | null;
|
|
onViewDetails?: (id: string) => void;
|
|
}
|
|
|
|
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';
|
|
return 'bg-slate-100 text-slate-700 border-slate-300';
|
|
};
|
|
|
|
export function DealerResignationPage({ currentUser, onViewDetails }: DealerResignationPageProps) {
|
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
|
const [selectedOutlet, setSelectedOutlet] = useState<any | null>(null);
|
|
const [resignationType, setResignationType] = useState('');
|
|
const [lastOperationalDateSales, setLastOperationalDateSales] = useState('');
|
|
const [lastOperationalDateServices, setLastOperationalDateServices] = useState('');
|
|
const [reason, setReason] = useState('');
|
|
const [additionalInfo, setAdditionalInfo] = useState('');
|
|
|
|
const [outlets, setOutlets] = useState<any[]>([]);
|
|
const [resignations, setResignations] = useState<any[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [profile, setProfile] = useState<any>(null);
|
|
|
|
useEffect(() => {
|
|
fetchData();
|
|
}, []);
|
|
|
|
const fetchData = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const dashboard = await dealerService.getDashboardData();
|
|
const resignationsRes = await resignationService.getResignations();
|
|
|
|
setOutlets(dashboard.outlets || []);
|
|
setProfile(dashboard.profile);
|
|
setResignations(resignationsRes.resignations || []);
|
|
} catch (error) {
|
|
console.error('Fetch resignation data error:', error);
|
|
toast.error('Failed to load outlets and requests');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleOpenResignationDialog = (outlet: any) => {
|
|
setSelectedOutlet(outlet);
|
|
setIsDialogOpen(true);
|
|
};
|
|
|
|
const handleSubmitRequest = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
if (!resignationType) {
|
|
toast.error('Please select resignation type');
|
|
return;
|
|
}
|
|
|
|
if (!lastOperationalDateSales || !lastOperationalDateServices) {
|
|
toast.error('Please enter last operational dates');
|
|
return;
|
|
}
|
|
|
|
if (!reason.trim()) {
|
|
toast.error('Please provide a reason for resignation');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setSubmitting(true);
|
|
const payload = {
|
|
outletId: selectedOutlet?.id,
|
|
resignationType,
|
|
lastOperationalDateSales,
|
|
lastOperationalDateServices,
|
|
reason,
|
|
additionalInfo
|
|
};
|
|
|
|
await resignationService.createResignation(payload);
|
|
toast.success(`Resignation request submitted successfully for ${selectedOutlet?.name}`);
|
|
setIsDialogOpen(false);
|
|
fetchData(); // Refresh data
|
|
|
|
// Reset form
|
|
setSelectedOutlet(null);
|
|
setResignationType('');
|
|
setLastOperationalDateSales('');
|
|
setLastOperationalDateServices('');
|
|
setReason('');
|
|
setAdditionalInfo('');
|
|
} catch (error) {
|
|
console.error('Submit resignation error:', error);
|
|
toast.error('Failed to submit resignation request');
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const stats = [
|
|
{
|
|
title: 'Total Outlets',
|
|
value: outlets.length,
|
|
icon: Building2,
|
|
color: 'bg-blue-500',
|
|
},
|
|
{
|
|
title: 'Active Outlets',
|
|
value: outlets.filter(o => o.status === 'Active').length,
|
|
icon: CheckCircle,
|
|
color: 'bg-green-500',
|
|
},
|
|
{
|
|
title: 'Pending Resignations',
|
|
value: outlets.filter(o => o.status === 'Pending Resignation').length,
|
|
icon: Clock,
|
|
color: 'bg-amber-500',
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Loading Overlay */}
|
|
{loading && (
|
|
<div className="min-h-[400px] flex items-center justify-center">
|
|
<Loader2 className="w-8 h-8 text-amber-600 animate-spin" />
|
|
</div>
|
|
)}
|
|
|
|
{!loading && (
|
|
<>
|
|
{/* Header */}
|
|
<div>
|
|
<h1 className="text-slate-900 mb-2">Dealership Resignation Management</h1>
|
|
<p className="text-slate-600">
|
|
Manage resignation requests for your dealerships and studios
|
|
</p>
|
|
</div>
|
|
|
|
{/* Stats */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
{stats.map((stat, index) => {
|
|
const Icon = stat.icon;
|
|
return (
|
|
<Card key={index}>
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm">{stat.title}</CardTitle>
|
|
<div className={`${stat.color} p-2 rounded-lg`}>
|
|
<Icon className="h-4 w-4 text-white" />
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-slate-900 text-2xl">{stat.value}</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{/* My Outlets Section */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>My Outlets</CardTitle>
|
|
<CardDescription>
|
|
Select an outlet to request resignation
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
{outlets.map((outlet) => {
|
|
const OutletIcon = outlet.type === 'Dealership' ? Building2 : Store;
|
|
const hasActiveResignation = outlet.status === 'Pending Resignation';
|
|
const resignation = resignations.find(r => r.outletId === outlet.id && r.status !== 'Completed' && r.status !== 'Rejected');
|
|
|
|
return (
|
|
<div
|
|
key={outlet.id}
|
|
className="border border-slate-200 rounded-lg p-4 hover:shadow-md transition-shadow"
|
|
>
|
|
<div className="flex items-start justify-between mb-3">
|
|
<div className="flex items-start gap-3">
|
|
<div className={`${outlet.type === 'Dealership' ? 'bg-blue-100' : 'bg-purple-100'} p-2 rounded-lg`}>
|
|
<OutletIcon className={`w-5 h-5 ${outlet.type === 'Dealership' ? 'text-blue-600' : 'text-purple-600'}`} />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-slate-900">{outlet.name}</h3>
|
|
<p className="text-slate-600 text-sm">{outlet.code}</p>
|
|
</div>
|
|
</div>
|
|
<Badge
|
|
className={`border ${
|
|
outlet.status === 'Active'
|
|
? 'bg-green-100 text-green-700 border-green-300'
|
|
: outlet.status === 'Pending Resignation'
|
|
? 'bg-amber-100 text-amber-700 border-amber-300'
|
|
: 'bg-slate-100 text-slate-700 border-slate-300'
|
|
}`}
|
|
>
|
|
{outlet.status}
|
|
</Badge>
|
|
</div>
|
|
|
|
<div className="space-y-2 mb-4">
|
|
<div className="flex items-start gap-2 text-sm">
|
|
<MapPin className="w-4 h-4 text-slate-400 mt-0.5 flex-shrink-0" />
|
|
<div>
|
|
<p className="text-slate-600">{outlet.location}</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2 text-sm">
|
|
<Calendar className="w-4 h-4 text-slate-400" />
|
|
<span className="text-slate-600">
|
|
Outlet Code: {outlet.code}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{hasActiveResignation ? (
|
|
<div className="bg-amber-50 border border-amber-200 rounded p-3 text-sm">
|
|
<p className="text-amber-800">
|
|
Resignation in progress - <span className="underline cursor-pointer" onClick={() => onViewDetails && resignation?.resignationId && onViewDetails(resignation.resignationId)}>View Request</span>
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<Button
|
|
className="w-full bg-red-600 hover:bg-red-700"
|
|
onClick={() => handleOpenResignationDialog(outlet)}
|
|
>
|
|
<FileText className="w-4 h-4 mr-2" />
|
|
Request Resignation
|
|
</Button>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Resignation Request Dialog */}
|
|
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
|
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
|
<DialogHeader>
|
|
<DialogTitle>Submit Resignation Request</DialogTitle>
|
|
<DialogDescription>
|
|
Fill in the details for your resignation request. All fields are mandatory.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
|
|
<form onSubmit={handleSubmitRequest} className="space-y-4">
|
|
{/* Outlet Info */}
|
|
<div className="bg-slate-50 border border-slate-200 rounded-lg p-4 space-y-2">
|
|
<h3 className="text-slate-900">Outlet Information</h3>
|
|
<div className="grid grid-cols-2 gap-3 text-sm">
|
|
<div>
|
|
<span className="text-slate-600">Outlet Code:</span>
|
|
<p className="text-slate-900">{selectedOutlet?.code}</p>
|
|
</div>
|
|
<div>
|
|
<span className="text-slate-600">Outlet Name:</span>
|
|
<p className="text-slate-900">{selectedOutlet?.name}</p>
|
|
</div>
|
|
<div>
|
|
<span className="text-slate-600">Dealer Code:</span>
|
|
<p className="text-slate-900">{profile?.dealerCode || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<span className="text-slate-600">City:</span>
|
|
<p className="text-slate-900">{selectedOutlet?.location}</p>
|
|
</div>
|
|
<div className="col-span-2">
|
|
<span className="text-slate-600">Location:</span>
|
|
<p className="text-slate-900">{selectedOutlet?.location}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Resignation Type */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="resignationType">Resignation Type *</Label>
|
|
<Select value={resignationType} onValueChange={setResignationType} required>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select resignation type" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="Voluntary">Voluntary</SelectItem>
|
|
<SelectItem value="Retirement">Retirement</SelectItem>
|
|
<SelectItem value="Health Issues">Health Issues</SelectItem>
|
|
<SelectItem value="Business Closure">Business Closure</SelectItem>
|
|
<SelectItem value="Other">Other</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* Last Operational Dates */}
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="lastOpDateSales">Last Operational Date - Sales *</Label>
|
|
<Input
|
|
id="lastOpDateSales"
|
|
type="date"
|
|
value={lastOperationalDateSales}
|
|
onChange={(e) => setLastOperationalDateSales(e.target.value)}
|
|
required
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="lastOpDateServices">Last Operational Date - Services *</Label>
|
|
<Input
|
|
id="lastOpDateServices"
|
|
type="date"
|
|
value={lastOperationalDateServices}
|
|
onChange={(e) => setLastOperationalDateServices(e.target.value)}
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Reason */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="reason">Reason for Resignation *</Label>
|
|
<Textarea
|
|
id="reason"
|
|
placeholder="Please provide detailed reason for resignation..."
|
|
value={reason}
|
|
onChange={(e) => setReason(e.target.value)}
|
|
rows={4}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
{/* Additional Information */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="additionalInfo">Additional Information (Optional)</Label>
|
|
<Textarea
|
|
id="additionalInfo"
|
|
placeholder="Any additional details..."
|
|
value={additionalInfo}
|
|
onChange={(e) => setAdditionalInfo(e.target.value)}
|
|
rows={3}
|
|
/>
|
|
</div>
|
|
|
|
{/* Important Info */}
|
|
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4">
|
|
<h4 className="text-amber-900 mb-2">Important Information</h4>
|
|
<ul className="text-amber-800 text-sm space-y-1">
|
|
<li>• F&F settlement process will be initiated after submission</li>
|
|
<li>• All department clearances must be obtained</li>
|
|
<li>• Final settlement will be processed after closure</li>
|
|
<li>• Please ensure all documents are ready for submission</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<DialogFooter>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => {
|
|
setIsDialogOpen(false);
|
|
setSelectedOutlet(null);
|
|
}}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
type="submit"
|
|
className="bg-red-600 hover:bg-red-700"
|
|
disabled={submitting}
|
|
>
|
|
{submitting ? (
|
|
<>
|
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
|
Submitting...
|
|
</>
|
|
) : (
|
|
'Submit Resignation Request'
|
|
)}
|
|
</Button>
|
|
</DialogFooter>
|
|
</form>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Requests Table */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>My Resignation Requests</CardTitle>
|
|
<CardDescription>
|
|
Track the progress of your resignation requests
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Request ID</TableHead>
|
|
<TableHead>Outlet</TableHead>
|
|
<TableHead>Type</TableHead>
|
|
<TableHead>Submitted On</TableHead>
|
|
<TableHead>Status</TableHead>
|
|
<TableHead>Progress</TableHead>
|
|
<TableHead>Actions</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{resignations.length === 0 ? (
|
|
<TableRow>
|
|
<TableCell colSpan={7} className="text-center py-4 text-slate-500">
|
|
No resignation requests found
|
|
</TableCell>
|
|
</TableRow>
|
|
) : (
|
|
resignations.map((request) => (
|
|
<TableRow key={request.id}>
|
|
<TableCell className="font-medium text-slate-900">{request.resignationId}</TableCell>
|
|
<TableCell>{request.outlet?.name}</TableCell>
|
|
<TableCell>{request.resignationType}</TableCell>
|
|
<TableCell>{new Date(request.submittedOn).toLocaleDateString()}</TableCell>
|
|
<TableCell>
|
|
<Badge className={`border ${getStatusColor(request.status)}`}>
|
|
{request.status}
|
|
</Badge>
|
|
</TableCell>
|
|
<TableCell>
|
|
<div className="flex items-center gap-2">
|
|
<div className="flex-1 bg-slate-200 rounded-full h-2 min-w-[60px]">
|
|
<div
|
|
className="bg-red-600 h-2 rounded-full"
|
|
style={{ width: `${request.progressPercentage}%` }}
|
|
/>
|
|
</div>
|
|
<span className="text-xs text-slate-600">{request.progressPercentage}%</span>
|
|
</div>
|
|
</TableCell>
|
|
<TableCell>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => onViewDetails && onViewDetails(request.resignationId)}
|
|
>
|
|
<Eye className="w-4 h-4 mr-1" />
|
|
View
|
|
</Button>
|
|
</TableCell>
|
|
</TableRow>
|
|
))
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
</CardContent>
|
|
</Card>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|