388 lines
18 KiB
TypeScript
388 lines
18 KiB
TypeScript
import { FileText, Calendar, Eye } from 'lucide-react';
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
import { useState, useEffect } from 'react';
|
|
import { API } from '@/api/API';
|
|
import { toast } from 'sonner';
|
|
import { User as UserType } from '@/lib/mock-data';
|
|
import { formatDateTime } from '@/components/ui/utils';
|
|
import {
|
|
Pagination,
|
|
PaginationContent,
|
|
PaginationEllipsis,
|
|
PaginationItem,
|
|
PaginationLink,
|
|
PaginationNext,
|
|
PaginationPrevious,
|
|
} from "@/components/ui/pagination";
|
|
|
|
interface ResignationPageProps {
|
|
currentUser: UserType | null;
|
|
onViewDetails: (id: string) => void;
|
|
}
|
|
|
|
const getStatusColor = (status: string) => {
|
|
if (status.includes('Approved') || status.includes('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-blue-100 text-blue-700 border-blue-300';
|
|
};
|
|
|
|
export function ResignationPage({ currentUser, onViewDetails }: ResignationPageProps) {
|
|
const [resignations, setResignations] = useState<any[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const [statusTab, setStatusTab] = useState('all');
|
|
const [paginationMeta, setPaginationMeta] = useState<any>(null);
|
|
const itemsPerPage = 10;
|
|
|
|
const fetchResignations = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const response = await API.getResignations({
|
|
page: currentPage,
|
|
limit: itemsPerPage,
|
|
status: statusTab === 'all' ? undefined : statusTab === 'open' ? 'open' : 'Completed,Closed'
|
|
});
|
|
const data = response.data as any;
|
|
if (data?.success) {
|
|
setResignations(data.requests || data.resignations?.rows || data.resignations || []);
|
|
setPaginationMeta(data.meta);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching resignations:', error);
|
|
toast.error('Failed to fetch resignation requests');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchResignations();
|
|
}, [currentPage, statusTab]);
|
|
|
|
const handleTabChange = (value: string) => {
|
|
setStatusTab(value);
|
|
setCurrentPage(1);
|
|
};
|
|
|
|
const openRequests = statusTab === 'open' ? resignations : [];
|
|
const completedRequests = statusTab === 'completed' ? resignations : [];
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Header Stats */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardDescription>All Requests</CardDescription>
|
|
<CardTitle className="text-3xl">{paginationMeta?.total || 0}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-slate-600">Total Requests</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardDescription>Open</CardDescription>
|
|
<CardTitle className="text-3xl text-yellow-600">{statusTab === 'open' ? paginationMeta?.total || 0 : '...'}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-slate-600">Requires Your Action</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<CardDescription>Completed</CardDescription>
|
|
<CardTitle className="text-3xl text-green-600">{statusTab === 'completed' ? paginationMeta?.total || 0 : '...'}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-slate-600">Finalized</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Main Content */}
|
|
<Card>
|
|
<CardHeader>
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<CardTitle>Resignation Requests</CardTitle>
|
|
<CardDescription>
|
|
Track and manage dealer resignation requests
|
|
<span className="block mt-1 text-slate-500">
|
|
• Note: Resignation requests are initiated by the dealer or via ASM.
|
|
</span>
|
|
</CardDescription>
|
|
</div>
|
|
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Tabs value={statusTab} onValueChange={handleTabChange} className="w-full">
|
|
<TabsList>
|
|
<TabsTrigger value="all">All Requests</TabsTrigger>
|
|
<TabsTrigger value="open">Open</TabsTrigger>
|
|
<TabsTrigger value="completed">Completed</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="all" className="mt-6">
|
|
<div className="space-y-4 text-center py-1">
|
|
{loading ? (
|
|
<div className="text-center py-12">Loading requests...</div>
|
|
) : resignations.length > 0 ? (
|
|
<>
|
|
{resignations.map((request) => (
|
|
<Card key={request.id} className="border-slate-200">
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex items-start gap-4 flex-1">
|
|
<div className="p-3 bg-amber-100 rounded-lg">
|
|
<FileText className="w-6 h-6 text-amber-600" />
|
|
</div>
|
|
<div className="flex-1 text-left">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<h3 className="text-lg">{request.resignationId}</h3>
|
|
<Badge className={getStatusColor(request.status)}>
|
|
{request.status}
|
|
</Badge>
|
|
</div>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
|
<div>
|
|
<p className="text-slate-600">Dealer Name</p>
|
|
<p>{request.dealer?.dealerProfile?.businessName || request.outlet?.name || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Dealer Code</p>
|
|
<p>{request.dealer?.dealerProfile?.dealerCode?.dealerCode || request.outlet?.code || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Location</p>
|
|
<p>{request.dealer?.dealerProfile?.registeredAddress || (request.outlet?.city && request.outlet?.state ? `${request.outlet.city}, ${request.outlet.state}` : 'N/A')}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Type</p>
|
|
<p>{request.resignationType}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Reason</p>
|
|
<p className="truncate max-w-[200px]">{request.reason}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Current Stage</p>
|
|
<p>{request.currentStage}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Submitted On</p>
|
|
<div className="flex items-center gap-1">
|
|
<Calendar className="w-4 h-4 text-slate-500" />
|
|
<p>{formatDateTime(request.submittedOn)}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => onViewDetails(request.id)}
|
|
className="ml-4"
|
|
>
|
|
<Eye className="w-4 h-4 mr-2" />
|
|
View Details
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
|
|
{paginationMeta && paginationMeta.totalPages > 1 && (
|
|
<div className="py-4 border-t flex justify-center">
|
|
<Pagination>
|
|
<PaginationContent>
|
|
<PaginationItem>
|
|
<PaginationPrevious
|
|
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
|
|
className={currentPage === 1 ? "pointer-events-none opacity-50" : "cursor-pointer"}
|
|
/>
|
|
</PaginationItem>
|
|
|
|
{[...Array(paginationMeta.totalPages)].map((_, i) => {
|
|
const pageNum = i + 1;
|
|
if (
|
|
pageNum === 1 ||
|
|
pageNum === paginationMeta.totalPages ||
|
|
(pageNum >= currentPage - 1 && pageNum <= currentPage + 1)
|
|
) {
|
|
return (
|
|
<PaginationItem key={pageNum}>
|
|
<PaginationLink
|
|
isActive={currentPage === pageNum}
|
|
onClick={() => setCurrentPage(pageNum)}
|
|
className="cursor-pointer"
|
|
>
|
|
{pageNum}
|
|
</PaginationLink>
|
|
</PaginationItem>
|
|
);
|
|
}
|
|
if (
|
|
(pageNum === 2 && currentPage > 3) ||
|
|
(pageNum === paginationMeta.totalPages - 1 && currentPage < paginationMeta.totalPages - 2)
|
|
) {
|
|
return <PaginationItem key={pageNum}><PaginationEllipsis /></PaginationItem>;
|
|
}
|
|
return null;
|
|
})}
|
|
|
|
<PaginationItem>
|
|
<PaginationNext
|
|
onClick={() => setCurrentPage(prev => Math.min(paginationMeta.totalPages, prev + 1))}
|
|
className={currentPage === paginationMeta.totalPages ? "pointer-events-none opacity-50" : "cursor-pointer"}
|
|
/>
|
|
</PaginationItem>
|
|
</PaginationContent>
|
|
</Pagination>
|
|
</div>
|
|
)}
|
|
</>
|
|
) : (
|
|
<div className="text-center py-12 text-slate-500">
|
|
<p>No resignation requests found</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
{/* Open Tab */}
|
|
<TabsContent value="open" className="mt-6">
|
|
<div className="space-y-4">
|
|
{openRequests.length > 0 ? (
|
|
openRequests.map((request) => (
|
|
<Card key={request.id} className="border-slate-200">
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex items-start gap-4 flex-1">
|
|
<div className="p-3 bg-yellow-100 rounded-lg">
|
|
<FileText className="w-6 h-6 text-yellow-600" />
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<h3 className="text-lg">{request.resignationId}</h3>
|
|
<Badge className={getStatusColor(request.status)}>
|
|
{request.status}
|
|
</Badge>
|
|
</div>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
|
<div>
|
|
<p className="text-slate-600">Dealer Name</p>
|
|
<p>{request.dealer?.dealerProfile?.businessName || request.outlet?.name || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Location</p>
|
|
<p>{request.dealer?.dealerProfile?.registeredAddress || (request.outlet?.city && request.outlet?.state ? `${request.outlet.city}, ${request.outlet.state}` : 'N/A')}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Current Stage</p>
|
|
<p>{request.currentStage}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Submitted On</p>
|
|
<p>{formatDateTime(request.submittedOn)}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => onViewDetails(request.id)}
|
|
className="ml-4"
|
|
>
|
|
<Eye className="w-4 h-4 mr-2" />
|
|
View Details
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))
|
|
) : (
|
|
<div className="text-center py-12 text-slate-500">
|
|
<FileText className="w-12 h-12 mx-auto mb-4 text-slate-400" />
|
|
<p>No requests requiring your action</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
{/* Completed Tab */}
|
|
<TabsContent value="completed" className="mt-6">
|
|
<div className="space-y-4">
|
|
{completedRequests.length > 0 ? (
|
|
completedRequests.map((request) => (
|
|
<Card key={request.id} className="border-slate-200">
|
|
<CardContent className="pt-6">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex items-start gap-4 flex-1">
|
|
<div className="p-3 bg-green-100 rounded-lg">
|
|
<FileText className="w-6 h-6 text-green-600" />
|
|
</div>
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-3 mb-2">
|
|
<h3 className="text-lg">{request.resignationId}</h3>
|
|
<Badge className={getStatusColor(request.status)}>
|
|
{request.status}
|
|
</Badge>
|
|
</div>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
|
<div>
|
|
<p className="text-slate-600">Dealer Name</p>
|
|
<p>{request.dealer?.dealerProfile?.businessName || request.outlet?.name || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Location</p>
|
|
<p>{request.dealer?.dealerProfile?.registeredAddress || request.outlet?.city || 'N/A'}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Final Stage</p>
|
|
<p>{request.currentStage}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-slate-600">Submitted On</p>
|
|
<p>{formatDateTime(request.submittedOn)}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => onViewDetails(request.id)}
|
|
className="ml-4"
|
|
>
|
|
<Eye className="w-4 h-4 mr-2" />
|
|
View Details
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))
|
|
) : (
|
|
<div className="text-center py-12 text-slate-500">
|
|
<FileText className="w-12 h-12 mx-auto mb-4 text-slate-400" />
|
|
<p>No completed resignations to display</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|