Dealer_Onboard_Frontend/src/features/relocation/pages/DealerRelocationPage.tsx
2026-04-27 19:52:56 +05:30

530 lines
21 KiB
TypeScript

import { MapPin, Plus, Eye, Calendar, Building2, Loader2, ArrowRight } 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 { 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 { useState, useEffect } from 'react';
import { User as UserType } from '@/lib/mock-data';
import { toast } from 'sonner';
import { dealerService } from '@/services/dealer.service';
import { masterService } from '@/services/master.service';
import { formatDateTime } from '@/components/ui/utils';
interface DealerRelocationPageProps {
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') || status.includes('Revoked')) return 'bg-red-100 text-red-700 border-red-300';
return 'bg-slate-100 text-slate-700 border-slate-300';
};
const getApiErrorMessage = (error: any, fallback: string) =>
error?.response?.data?.message || error?.data?.message || error?.message || fallback;
export function DealerRelocationPage({ onViewDetails }: DealerRelocationPageProps) {
const [isDialogOpen, setIsDialogOpen] = useState(false);
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('');
// State/District dropdown data
const [states, setStates] = useState<any[]>([]);
const [districts, setDistricts] = useState<any[]>([]);
const [selectedStateId, setSelectedStateId] = useState('');
const [selectedDistrictId, setSelectedDistrictId] = useState('');
const [masterDataLoading, setMasterDataLoading] = useState(false);
const [outlets, setOutlets] = useState<any[]>([]);
const [requests, setRequests] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
useEffect(() => {
fetchData();
fetchMasterData();
}, []);
// Filter districts based on selected state
const filteredDistricts = selectedStateId
? districts.filter(d => d.stateId === selectedStateId)
: districts;
const fetchData = async () => {
try {
setLoading(true);
const dashboard = await dealerService.getDashboardData();
const relocationRes = await dealerService.getRelocationRequests();
setOutlets(dashboard.outlets || []);
setRequests(relocationRes.requests || []);
} catch (error) {
console.error('Fetch relocation data error:', error);
toast.error(getApiErrorMessage(error, 'Failed to load outlets and requests'));
} finally {
setLoading(false);
}
};
const fetchMasterData = async () => {
try {
setMasterDataLoading(true);
const [statesRes, districtsRes] = await Promise.all([
masterService.getStates().catch(() => ({ success: false })) as Promise<any>,
masterService.getDistricts({ limit: 'all' }).catch(() => ({ success: false })) as Promise<any>
]);
const statesData = statesRes?.success ? (statesRes.data?.states || statesRes.data || []) : [];
const districtsData = districtsRes?.success ? (districtsRes.data?.districts || districtsRes.data || []) : [];
setStates(statesData);
setDistricts(districtsData);
} catch (error) {
console.error('Fetch master data error:', error);
toast.error(getApiErrorMessage(error, 'Failed to load master data'));
} finally {
setMasterDataLoading(false);
}
};
const handleStateChange = (stateId: string) => {
setSelectedStateId(stateId);
setSelectedDistrictId(''); // Reset district when state changes
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) {
toast.error('Please select an outlet');
return;
}
if (!newCity.trim() || !newState.trim() || !newAddress.trim()) {
toast.error('Please provide complete relocation details');
return;
}
if (!reason.trim()) {
toast.error('Please provide a reason for relocation');
return;
}
if (!distance.trim() || !propertyType || !expectedDate) {
toast.error('Please fill all mandatory fields (Distance, Property Type, Date)');
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,
distance,
reason,
propertyType,
proposedDate: expectedDate,
proposedLatitude: newLat ? parseFloat(newLat) : null,
proposedLongitude: newLong ? parseFloat(newLong) : null,
currentLatitude: selectedOutlet.latitude || null,
currentLongitude: selectedOutlet.longitude || null
};
await dealerService.submitRelocationRequest(payload);
toast.success(`Relocation request submitted successfully for ${selectedOutlet.name}`);
setIsDialogOpen(false);
fetchData(); // Refresh list
// Reset form
setSelectedOutlet(null);
setNewCity('');
setNewState('');
setNewAddress('');
setReason('');
setDistance('');
setPropertyType('');
setExpectedDate('');
setNewLat('');
setNewLong('');
if (onViewDetails) {
fetchData();
}
} catch (error) {
console.error('Submit relocation error:', error);
toast.error(getApiErrorMessage(error, 'Failed to submit relocation request'));
} finally {
setSubmitting(false);
}
};
const stats = [
{
title: 'Total Requests',
value: requests.length,
icon: MapPin,
color: 'bg-blue-500',
},
{
title: 'Pending',
value: requests.filter(r => r.status !== 'Completed' && r.status !== 'Rejected').length,
icon: Calendar,
color: 'bg-yellow-500',
},
{
title: 'Approved',
value: requests.filter(r => r.status === 'Completed').length,
icon: Building2,
color: 'bg-green-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 className="flex items-center justify-between">
<div>
<h1 className="text-slate-900 mb-2">Relocation Requests</h1>
<p className="text-slate-600">
Request to relocate your existing dealership or studio to a new location
</p>
</div>
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogTrigger asChild>
<Button className="bg-amber-600 hover:bg-amber-700 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>
Provide details about the outlet you want to relocate and its proposed new location
</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">
<h3 className="text-slate-900 text-sm font-medium">Current Location</h3>
<p className="text-slate-600 text-sm">{selectedOutlet.location}</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"
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 do you want to relocate this outlet?"
value={reason}
onChange={(e) => setReason(e.target.value)}
rows={4}
required
/>
</div>
{/* Important Notes */}
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<h4 className="text-blue-900 mb-2 font-medium">Policy Notes</h4>
<ul className="text-blue-800 text-xs space-y-1">
<li> Relocation is subject to feasibility study of the new location</li>
<li> Maximum allowed distance and other policy criteria apply</li>
<li> Site visit will be conducted by RBM/ASM</li>
<li> New outlet code might be generated upon approval</li>
</ul>
</div>
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => setIsDialogOpen(false)}
>
Cancel
</Button>
<Button
type="submit"
className="bg-amber-600 hover:bg-amber-700 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>
{/* 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>
{/* Requests Table */}
<Card>
<CardHeader>
<CardTitle>My Relocation Requests</CardTitle>
<CardDescription>
Track the status of your relocation applications
</CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Request ID</TableHead>
<TableHead>Outlet</TableHead>
<TableHead>Target Location</TableHead>
<TableHead>Submitted On</TableHead>
<TableHead>Current Status</TableHead>
<TableHead>Progress</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{requests.length === 0 ? (
<TableRow>
<TableCell colSpan={7} className="text-center py-4 text-slate-500">
No relocation requests found
</TableCell>
</TableRow>
) : (
requests.map((request) => (
<TableRow key={request.id}>
<TableCell>
<span className="text-slate-900 font-medium">{request.requestId}</span>
</TableCell>
<TableCell>
{request.outlet?.name}
</TableCell>
<TableCell>
<div className="flex items-center gap-1 text-sm">
<span className="text-slate-500">{request.currentCity}</span>
<ArrowRight className="w-3 h-3 text-slate-400" />
<span className="text-slate-900">{request.newCity}</span>
</div>
</TableCell>
<TableCell className="text-slate-600">
{formatDateTime(request.createdAt)}
</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-amber-500 h-2 rounded-full"
style={{ width: `${request.progressPercentage || 0}%` }}
/>
</div>
<span className="text-xs text-slate-600">{request.progressPercentage || 0}%</span>
</div>
</TableCell>
<TableCell>
<Button
variant="ghost"
size="sm"
onClick={() => onViewDetails && onViewDetails(request.requestId)}
>
<Eye className="w-4 h-4 mr-1" />
View
</Button>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</CardContent>
</Card>
</>
)}
</div>
);
}