Dealer_Onboard_Frontend/src/features/relocation/pages/RelocationRequestPage.tsx

849 lines
36 KiB
TypeScript

import { FileText, Calendar, Building, Eye, MapPin, Navigation, Loader2 } 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 { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Textarea } from '@/components/ui/textarea';
import { Plus } from 'lucide-react';
import { useState, useEffect } from 'react';
import { User } from '@/lib/mock-data';
import { toast } from 'sonner';
import { API } from '@/api/API';
import { SlaBadge } from '@/components/sla/SlaBadge';
import { useSlaBatchStatus } from '@/hooks/useSlaBatchStatus';
import { formatDateTime } from '@/components/ui/utils';
import {
getCurrentStageBadgeClass,
getStatusProgressBarClass,
} from '@/lib/statusProgressTheme';
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
interface RelocationRequestPageProps {
currentUser: User | null;
onViewDetails: (id: string) => void;
}
const getApiErrorMessage = (error: any, fallback: string) =>
error?.response?.data?.message || error?.data?.message || error?.message || fallback;
const getStageBadgeClass = (stage: string, requestStatus?: string) =>
getCurrentStageBadgeClass(stage, requestStatus);
export function RelocationRequestPage({ currentUser, onViewDetails }: RelocationRequestPageProps) {
const [requests, setRequests] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [currentPage, setCurrentPage] = useState(1);
const [paginationMeta, setPaginationMeta] = useState<any>(null);
const [activeTab, setActiveTab] = useState('all');
const itemsPerPage = 10;
// Relocation Creation State (for Super Admin)
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [outlets, setOutlets] = useState<any[]>([]);
const [selectedOutlet, setSelectedOutlet] = useState<any | null>(null);
const [newCity, setNewCity] = useState('');
const [newState, setNewState] = useState('');
const [newAddress, setNewAddress] = useState('');
const [reason, setReason] = useState('');
const [distance, setDistance] = useState('');
const [propertyType, setPropertyType] = useState('');
const [expectedDate, setExpectedDate] = useState('');
const [newLat, setNewLat] = useState('');
const [newLong, setNewLong] = useState('');
const [submitting, setSubmitting] = useState(false);
// Master Data
const [states, setStates] = useState<any[]>([]);
const [districts, setDistricts] = useState<any[]>([]);
const [selectedStateId, setSelectedStateId] = useState('');
const [selectedDistrictId, setSelectedDistrictId] = useState('');
const [masterDataLoading, setMasterDataLoading] = useState(false);
// Constants
const isSuperAdmin = currentUser?.role === 'Super Admin' || currentUser?.roleCode === 'Super Admin';
const slaItems = requests.map((r: any) => ({
entityType: 'relocation',
entityId: r.id || r.requestId
}));
const { get: getSla } = useSlaBatchStatus(slaItems, requests.length > 0);
const isCompletedRequest = (request: any) =>
request.status === 'Completed' || request.status === 'Closed' || request.currentStage === 'Completed';
const isRejectedRequest = (request: any) =>
request.status === 'Rejected' || request.status === 'Revoked' || request.currentStage === 'Rejected';
const isPendingReviewRequest = (request: any) =>
!isCompletedRequest(request) && !isRejectedRequest(request) && String(request.status || '').startsWith('Pending');
useEffect(() => {
fetchRequests();
}, [currentPage, activeTab]);
useEffect(() => {
if (isSuperAdmin) {
fetchOutlets();
fetchMasterData();
}
}, [isSuperAdmin]);
const handleTabChange = (val: string) => {
setActiveTab(val);
setCurrentPage(1);
};
const fetchOutlets = async () => {
try {
const response = await API.getOutlets() as any;
if (response.data.success) {
setOutlets(response.data.outlets || response.data.data?.outlets || response.data.data || []);
}
} catch (error) {
console.error('Fetch outlets error:', error);
}
};
const fetchMasterData = async () => {
try {
setMasterDataLoading(true);
const [statesRes, districtsRes] = await Promise.all([
API.getStates().catch(() => ({ success: false })) as Promise<any>,
API.getDistricts({ limit: 'all' }).catch(() => ({ success: false })) as Promise<any>
]);
const statesData = statesRes?.data?.success ? (statesRes.data.data?.states || statesRes.data.data || []) : [];
const districtsData = districtsRes?.data?.success ? (districtsRes.data.data?.districts || districtsRes.data.data || []) : [];
setStates(statesData);
setDistricts(districtsData);
} catch (error) {
console.error('Fetch master data error:', error);
} finally {
setMasterDataLoading(false);
}
};
const handleStateChange = (stateId: string) => {
setSelectedStateId(stateId);
setSelectedDistrictId('');
const selectedState = states.find(s => s.id === stateId);
if (selectedState) {
setNewState(selectedState.name);
}
};
const handleDistrictChange = (districtId: string) => {
setSelectedDistrictId(districtId);
const selectedDistrict = districts.find(d => d.id === districtId);
if (selectedDistrict) {
setNewCity(selectedDistrict.name);
}
};
const handleSubmitRequest = async (e: React.FormEvent) => {
e.preventDefault();
if (!selectedOutlet || !newCity || !newState || !newAddress || !reason || !distance || !propertyType || !expectedDate) {
toast.error('Please fill all mandatory fields');
return;
}
try {
setSubmitting(true);
const payload = {
outletId: selectedOutlet.id,
relocationType: 'Intercity',
currentAddress: selectedOutlet.address || '',
currentCity: selectedOutlet.city || '',
currentState: selectedOutlet.state || '',
newAddress,
newCity,
newState,
newDistrictId: selectedDistrictId || null,
newStateId: selectedStateId || null,
reason,
distance,
propertyType,
proposedDate: expectedDate,
newLatitude: newLat ? parseFloat(newLat) : null,
newLongitude: newLong ? parseFloat(newLong) : null,
currentLatitude: selectedOutlet.latitude || null,
currentLongitude: selectedOutlet.longitude || null
};
const response = await API.createRelocationRequest(payload) as any;
if (response.data.success) {
toast.success(`Relocation request submitted successfully for ${selectedOutlet.name}`);
setIsDialogOpen(false);
fetchRequests();
// Reset form
setSelectedOutlet(null);
setNewCity('');
setNewState('');
setNewAddress('');
setReason('');
setDistance('');
setPropertyType('');
setExpectedDate('');
setNewLat('');
setNewLong('');
}
} catch (error) {
console.error('Submit relocation error:', error);
toast.error(getApiErrorMessage(error, 'Failed to submit relocation request'));
} finally {
setSubmitting(false);
}
};
const filteredDistricts = selectedStateId
? districts.filter(d => d.stateId === selectedStateId)
: districts;
const fetchRequests = async () => {
try {
setIsLoading(true);
const response = await API.getRelocationRequests({
page: currentPage,
limit: itemsPerPage,
status: activeTab === 'all' ? undefined : activeTab
}) as any;
if (response.data.success) {
setRequests(response.data.requests);
setPaginationMeta(response.meta);
}
} catch (error) {
console.error('Fetch relocation requests error:', error);
toast.error('Failed to fetch relocation requests');
} finally {
setIsLoading(false);
}
};
// Statistics
const stats = [
{
title: 'Total Requests',
value: requests.length,
icon: FileText,
color: 'bg-blue-500',
},
{
title: 'Pending Review',
value: requests.filter((r: any) => isPendingReviewRequest(r)).length,
icon: Calendar,
color: 'bg-re-red',
},
{
title: 'Completed',
value: requests.filter((r: any) => isCompletedRequest(r)).length,
icon: MapPin,
color: 'bg-green-500',
},
{
title: 'Rejected / Revoked',
value: requests.filter((r: any) => isRejectedRequest(r)).length,
icon: Building,
color: 'bg-red-500',
},
];
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-slate-900 mb-2">Relocation Request Management</h1>
<p className="text-slate-600">
Manage dealer relocation requests - Moving dealership to a new location
</p>
<span className="block mt-1 text-slate-500 text-sm">
Note: Relocation requests are initiated by the dealer.
</span>
</div>
{isSuperAdmin && (
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogTrigger asChild>
<Button className="bg-re-red hover:bg-re-red-hover text-white">
<Plus className="w-4 h-4 mr-2" />
New Relocation Request
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>Submit Relocation Request</DialogTitle>
<DialogDescription>
Create a new relocation request on behalf of a dealer
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmitRequest} className="space-y-4">
{/* Select Outlet */}
<div className="space-y-2">
<Label htmlFor="outlet">Select Outlet to Relocate *</Label>
<Select
value={selectedOutlet?.id}
onValueChange={(val) => setSelectedOutlet(outlets.find(o => o.id === val))}
required
>
<SelectTrigger>
<SelectValue placeholder="Select an outlet" />
</SelectTrigger>
<SelectContent>
{outlets.map((outlet) => (
<SelectItem key={outlet.id} value={outlet.id}>
{outlet.name} ({outlet.code})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{selectedOutlet && (
<div className="bg-slate-50 border border-slate-200 rounded-lg p-4 space-y-2 text-sm">
<h3 className="text-slate-900 font-medium">Current Location</h3>
<p className="text-slate-600">{selectedOutlet.address}, {selectedOutlet.city}, {selectedOutlet.state} - {selectedOutlet.pincode}</p>
</div>
)}
{/* New Location Details - State/District Dropdowns */}
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="newState">Proposed State *</Label>
<Select
value={selectedStateId}
onValueChange={handleStateChange}
required
disabled={masterDataLoading}
>
<SelectTrigger id="newState">
<SelectValue placeholder={masterDataLoading ? "Loading..." : "Select state"} />
</SelectTrigger>
<SelectContent>
{states.map((state) => (
<SelectItem key={state.id} value={state.id}>
{state.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="newCity">Proposed City/District *</Label>
<Select
value={selectedDistrictId}
onValueChange={handleDistrictChange}
required
disabled={!selectedStateId || masterDataLoading}
>
<SelectTrigger id="newCity">
<SelectValue placeholder={!selectedStateId ? "Select state first" : masterDataLoading ? "Loading..." : "Select district"} />
</SelectTrigger>
<SelectContent>
{filteredDistricts.map((district) => (
<SelectItem key={district.id} value={district.id}>
{district.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="newAddress">Proposed Full Address *</Label>
<Textarea
id="newAddress"
placeholder="Enter detailed address of the proposed new location..."
value={newAddress}
onChange={(e) => setNewAddress(e.target.value)}
rows={3}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="distance">Estimated Distance from Current Location (in km) *</Label>
<Input
id="distance"
type="text"
placeholder="e.g. 5.5 km"
value={distance}
onChange={(e) => setDistance(e.target.value)}
required
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="propertyType">Property Type *</Label>
<Select value={propertyType} onValueChange={setPropertyType} required>
<SelectTrigger id="propertyType">
<SelectValue placeholder="Select type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Owned">Owned</SelectItem>
<SelectItem value="Leased">Leased</SelectItem>
<SelectItem value="Rented">Rented</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="expectedDate">Expected Relocation Date *</Label>
<Input
id="expectedDate"
type="date"
value={expectedDate}
onChange={(e) => setExpectedDate(e.target.value)}
required
/>
</div>
</div>
{/* Reason */}
<div className="space-y-2">
<Label htmlFor="reason">Reason for Relocation *</Label>
<Textarea
id="reason"
placeholder="Why is this relocation requested?"
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-re-red hover:bg-re-red-hover text-white"
disabled={submitting}
>
{submitting ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Submitting...
</>
) : (
'Submit Relocation 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>Relocation Requests</CardTitle>
<CardDescription>
Track and manage all dealership relocation requests across all stages
</CardDescription>
</CardHeader>
<CardContent>
<Tabs value={activeTab} onValueChange={handleTabChange} className="w-full">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="all">All Requests</TabsTrigger>
<TabsTrigger value="pending">Pending Review</TabsTrigger>
<TabsTrigger value="rejected">Rejected / Revoked</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>Relocation Details</TableHead>
<TableHead>Distance</TableHead>
<TableHead>Current Stage</TableHead>
<TableHead>Progress</TableHead>
<TableHead>Submitted On</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{isLoading ? (
<TableRow>
<TableCell colSpan={8} className="h-32 text-center">
<div className="flex flex-col items-center justify-center space-y-2">
<Loader2 className="w-6 h-6 text-re-red animate-spin" />
<p className="text-slate-500 text-sm">Loading requests...</p>
</div>
</TableCell>
</TableRow>
) : requests.length === 0 ? (
<TableRow>
<TableCell colSpan={8} className="h-32 text-center text-slate-500">
No relocation requests found
</TableCell>
</TableRow>
) : (
requests.map((request: any) => (
<TableRow key={request.id}>
<TableCell>
<div className="font-medium text-slate-900">{request.requestId || request.id}</div>
<div className="text-slate-600 text-sm">{request.outlet?.code || request.dealerCode}</div>
</TableCell>
<TableCell>
<div className="font-medium text-slate-900">{request.outlet?.name || request.dealerName}</div>
</TableCell>
<TableCell>
<div className="space-y-1">
<div className="flex items-center gap-1 text-slate-600 text-sm">
<span className="text-slate-500">From:</span>
<span>{request.currentLocation}</span>
</div>
<div className="flex items-center gap-1 text-slate-900 text-sm">
<Navigation className="w-3 h-3 text-re-red" />
<span className="text-slate-500">To:</span>
<span>{request.proposedLocation}</span>
</div>
</div>
</TableCell>
<TableCell>
<Badge variant="outline" className="border-slate-300 text-slate-700">
{request.distance}
</Badge>
</TableCell>
<TableCell>
<SlaBadge status={getSla('relocation', request.id || request.requestId)} compact />
<Badge className={getStageBadgeClass(request.currentStage, request.status)}>
{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 transition-all duration-300 ${getStatusProgressBarClass(request.status, request.currentStage)}`}
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">{formatDateTime(request.createdAt)}</div>
<div className="text-slate-600 text-sm">By {request.dealer?.fullName || 'Dealer'}</div>
</TableCell>
<TableCell>
<Button
size="sm"
variant="outline"
onClick={() => onViewDetails(request.id)}
>
<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>Relocation Details</TableHead>
<TableHead>Current Stage</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{isLoading ? (
<TableRow>
<TableCell colSpan={5} className="h-32 text-center">
<Loader2 className="w-6 h-6 text-re-red animate-spin mx-auto" />
</TableCell>
</TableRow>
) : (
requests
.filter((r: any) => isPendingReviewRequest(r))
.map((request: any) => (
<TableRow key={request.id}>
<TableCell>
<div className="font-medium text-slate-900">{request.requestId || request.id}</div>
<div className="text-slate-600 text-sm">{request.outlet?.code || request.dealerCode}</div>
</TableCell>
<TableCell>
<div className="font-medium text-slate-900">{request.outlet?.name || request.dealerName}</div>
</TableCell>
<TableCell>
<div className="space-y-1">
<div className="text-slate-600 text-sm">From: {request.currentLocation}</div>
<div className="text-slate-900 text-sm">To: {request.proposedLocation}</div>
</div>
</TableCell>
<TableCell>
<SlaBadge status={getSla('relocation', request.id || request.requestId)} compact />
<Badge className={getStageBadgeClass(request.currentStage, request.status)}>
{request.currentStage}
</Badge>
</TableCell>
<TableCell>
<Button
size="sm"
variant="outline"
onClick={() => onViewDetails(request.id)}
>
<Eye className="w-4 h-4 mr-1" />
View
</Button>
</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>Relocation Details</TableHead>
<TableHead>Progress</TableHead>
<TableHead>Current Stage</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{isLoading ? (
<TableRow>
<TableCell colSpan={6} className="h-32 text-center">
<Loader2 className="w-6 h-6 text-re-red animate-spin mx-auto" />
</TableCell>
</TableRow>
) : (
requests
.filter((r: any) => isRejectedRequest(r))
.map((request: any) => (
<TableRow key={request.id}>
<TableCell>
<div className="font-medium text-slate-900">{request.requestId || request.id}</div>
<div className="text-slate-600 text-sm">{request.outlet?.code || request.dealerCode}</div>
</TableCell>
<TableCell>
<div className="font-medium text-slate-900">{request.outlet?.name || request.dealerName}</div>
</TableCell>
<TableCell>
<div className="space-y-1">
<div className="text-slate-600 text-sm">From: {request.currentLocation}</div>
<div className="text-slate-900 text-sm">To: {request.proposedLocation}</div>
</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 transition-all duration-300 ${getStatusProgressBarClass(request.status, request.currentStage)}`}
style={{ width: `${request.progressPercentage || 0}%` }}
/>
</div>
<span className="text-slate-600 text-sm">{request.progressPercentage || 0}%</span>
</div>
</TableCell>
<TableCell>
<SlaBadge status={getSla('relocation', request.id || request.requestId)} compact />
<Badge className={getStageBadgeClass(request.currentStage, request.status)}>
{request.currentStage}
</Badge>
</TableCell>
<TableCell>
<Button
size="sm"
variant="outline"
onClick={() => onViewDetails(request.id)}
>
<Eye className="w-4 h-4 mr-1" />
View
</Button>
</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>Relocation Details</TableHead>
<TableHead>Submitted On</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{isLoading ? (
<TableRow>
<TableCell colSpan={5} className="h-32 text-center">
<Loader2 className="w-6 h-6 text-re-red animate-spin mx-auto" />
</TableCell>
</TableRow>
) : (
requests
.filter((r: any) => isCompletedRequest(r))
.map((request: any) => (
<TableRow key={request.id}>
<TableCell>
<div className="font-medium text-slate-900">{request.requestId || request.id}</div>
<div className="text-slate-600 text-sm">{request.outlet?.code || request.dealerCode}</div>
</TableCell>
<TableCell>
<div className="font-medium text-slate-900">{request.outlet?.name || request.dealerName}</div>
</TableCell>
<TableCell>
<div className="space-y-1">
<div className="text-slate-600 text-sm">From: {request.currentLocation}</div>
<div className="text-slate-900 text-sm">To: {request.proposedLocation}</div>
</div>
</TableCell>
<TableCell>
<div className="text-slate-900">{formatDateTime(request.createdAt)}</div>
</TableCell>
<TableCell>
<Button
size="sm"
variant="outline"
onClick={() => onViewDetails(request.id)}
>
<Eye className="w-4 h-4 mr-1" />
View
</Button>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</div>
</TabsContent>
</Tabs>
{paginationMeta && paginationMeta.totalPages > 1 && (
<div className="py-4 border-t flex justify-center bg-white rounded-b-lg">
<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>
)}
</CardContent>
</Card>
</div>
);
}