flow created upto outlet creation
This commit is contained in:
parent
9f13f8056e
commit
2d0a5fce22
@ -80,6 +80,7 @@ export const API = {
|
|||||||
createDealer: (data: any) => client.post('/dealer', data),
|
createDealer: (data: any) => client.post('/dealer', data),
|
||||||
getDealerById: (id: string) => client.get(`/dealer/${id}`),
|
getDealerById: (id: string) => client.get(`/dealer/${id}`),
|
||||||
updateDealer: (id: string, data: any) => client.put(`/dealer/${id}`, data),
|
updateDealer: (id: string, data: any) => client.put(`/dealer/${id}`, data),
|
||||||
|
getDealerDashboard: () => client.get('/dealer/dashboard'),
|
||||||
|
|
||||||
// Email Templates
|
// Email Templates
|
||||||
getEmailTemplates: () => client.get('/admin/email-templates'),
|
getEmailTemplates: () => client.get('/admin/email-templates'),
|
||||||
@ -91,13 +92,14 @@ export const API = {
|
|||||||
|
|
||||||
// Audit Trail
|
// Audit Trail
|
||||||
getAuditLogs: (entityType: string, entityId: string, page: number = 1, limit: number = 50) =>
|
getAuditLogs: (entityType: string, entityId: string, page: number = 1, limit: number = 50) =>
|
||||||
client.get('/audit/logs', { entityType, entityId, page, limit }),
|
client.get('/audit/logs', { params: { entityType, entityId, page, limit } }),
|
||||||
getAuditSummary: (entityType: string, entityId: string) =>
|
getAuditSummary: (entityType: string, entityId: string) =>
|
||||||
client.get('/audit/summary', { entityType, entityId }),
|
client.get('/audit/summary', { params: { entityType, entityId } }),
|
||||||
|
|
||||||
// Prospective Login
|
// Prospective Login
|
||||||
sendOtp: (phone: string) => client.post('/prospective-login/send-otp', { phone }),
|
sendOtp: (phone: string) => client.post('/prospective-login/send-otp', { phone }),
|
||||||
verifyOtp: (phone: string, otp: string) => client.post('/prospective-login/verify-otp', { phone, otp }),
|
verifyOtp: (phone: string, otp: string) => client.post('/prospective-login/verify-otp', { phone, otp }),
|
||||||
|
|
||||||
// Resignation
|
// Resignation
|
||||||
getResignationById: (id: string) => client.get(`/resignation/${id}`),
|
getResignationById: (id: string) => client.get(`/resignation/${id}`),
|
||||||
updateClearance: (id: string, data: any) => client.post(`/resignation/${id}/clearance`, data),
|
updateClearance: (id: string, data: any) => client.post(`/resignation/${id}/clearance`, data),
|
||||||
|
|||||||
@ -2409,14 +2409,14 @@ export function ApplicationDetails() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Dedicated Onboarding Button - Appears when logic is ready to onboard as a dealer */}
|
{/* Dedicated Onboarding Button - Appears when logic is ready to onboard as a dealer */}
|
||||||
{isAdmin && ['Dealer Code Generation', 'Architecture Team Completion', 'LOA Pending', 'EOR Complete', 'Inauguration'].includes(application.status) && (
|
{isAdmin && ['Dealer Code Generation', 'Architecture Team Completion', 'LOA Pending', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) && !application.dealer && (
|
||||||
<Button
|
<Button
|
||||||
className="w-full bg-amber-600 hover:bg-amber-700 font-bold"
|
className="w-full bg-amber-600 hover:bg-amber-700 font-bold"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (window.confirm('Do you want to finalize and onboard this dealer?')) {
|
if (window.confirm('Do you want to finalize and onboard this dealer?')) {
|
||||||
try {
|
try {
|
||||||
await onboardingService.createDealer({ applicationId });
|
await onboardingService.createDealer({ applicationId });
|
||||||
toast.success('Dealer record created successfully!');
|
toast.success('Dealer profile and login account created successfully!');
|
||||||
fetchApplication();
|
fetchApplication();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Failed to create dealer profile');
|
toast.error('Failed to create dealer profile');
|
||||||
@ -2429,6 +2429,32 @@ export function ApplicationDetails() {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Dealer Onboarded Status & Link */}
|
||||||
|
{application.dealer && (
|
||||||
|
<div className="p-4 bg-green-50 border border-green-200 rounded-lg space-y-3">
|
||||||
|
<div className="flex items-center gap-2 text-green-800 font-semibold">
|
||||||
|
<CheckCircle className="w-5 h-5 text-green-600" />
|
||||||
|
Dealer Profile Active
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-green-700">
|
||||||
|
This application has been successfully onboarded as a dealer. A user account has been created for the dealer.
|
||||||
|
</div>
|
||||||
|
{application.dealerCode && (
|
||||||
|
<div className="flex items-center justify-between text-xs font-mono bg-white p-2 rounded border border-green-100">
|
||||||
|
<span className="text-slate-500">Dealer Code:</span>
|
||||||
|
<span className="font-bold text-slate-900">{application.dealerCode.code}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
className="w-full bg-green-600 hover:bg-green-700 text-white"
|
||||||
|
onClick={() => navigate('/dashboard')}
|
||||||
|
>
|
||||||
|
<Zap className="w-4 h-4 mr-2" />
|
||||||
|
Go to Dealer Dashboard
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{currentUser && ['DD Admin', 'Super Admin'].includes(currentUser.role) && (
|
{currentUser && ['DD Admin', 'Super Admin'].includes(currentUser.role) && (
|
||||||
<Dialog open={showAssignModal} onOpenChange={setShowAssignModal}>
|
<Dialog open={showAssignModal} onOpenChange={setShowAssignModal}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import { FileText, RefreshCcw, MapPin, Users, TrendingUp, Clock, CheckCircle, AlertCircle } from 'lucide-react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { RefreshCcw, MapPin, Users, TrendingUp, Clock, CheckCircle, AlertCircle, ShoppingBag, Loader2 } from 'lucide-react';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card';
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
import { Badge } from '../ui/badge';
|
import { Badge } from '../ui/badge';
|
||||||
import { User } from '../../lib/mock-data';
|
import { User } from '../../lib/mock-data';
|
||||||
|
import { dealerService } from '../../services/dealer.service';
|
||||||
|
|
||||||
interface DealerDashboardProps {
|
interface DealerDashboardProps {
|
||||||
currentUser: User | null;
|
currentUser: User | null;
|
||||||
@ -10,27 +12,67 @@ interface DealerDashboardProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function DealerDashboard({ currentUser, onNavigate }: DealerDashboardProps) {
|
export function DealerDashboard({ currentUser, onNavigate }: DealerDashboardProps) {
|
||||||
// Dealer stats - showing their own requests
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [data, setData] = useState<any>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchDashboard = async () => {
|
||||||
|
try {
|
||||||
|
const dashboardData = await dealerService.getDashboardData();
|
||||||
|
setData(dashboardData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch dashboard:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchDashboard();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center min-h-[400px]">
|
||||||
|
<Loader2 className="w-10 h-10 text-amber-600 animate-spin mb-4" />
|
||||||
|
<p className="text-slate-600">Loading your dashboard...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dashboardData = data || {};
|
||||||
|
const profile = dashboardData.profile || {};
|
||||||
|
const statsSummary = dashboardData.stats || { constitutional: 0, relocation: 0, resignation: 0, total: 0 };
|
||||||
|
const recentRequests = dashboardData.recentRequests || [];
|
||||||
|
const primaryOutlet = dashboardData.outlets?.[0] || {};
|
||||||
|
|
||||||
|
// Dealer stats
|
||||||
const stats = [
|
const stats = [
|
||||||
{
|
{
|
||||||
title: 'Constitutional Changes',
|
title: 'Constitutional Changes',
|
||||||
value: 1,
|
value: statsSummary.constitutional,
|
||||||
icon: RefreshCcw,
|
icon: RefreshCcw,
|
||||||
color: 'bg-blue-500',
|
color: 'bg-blue-500',
|
||||||
change: 'In Review',
|
change: 'Active Requests',
|
||||||
onClick: () => onNavigate('dealer-constitutional')
|
onClick: () => onNavigate('dealer-constitutional')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Relocation Requests',
|
title: 'Relocation Requests',
|
||||||
value: 1,
|
value: statsSummary.relocation,
|
||||||
icon: MapPin,
|
icon: MapPin,
|
||||||
color: 'bg-amber-500',
|
color: 'bg-amber-500',
|
||||||
change: 'Pending Approval',
|
change: 'Active Requests',
|
||||||
onClick: () => onNavigate('dealer-relocation')
|
onClick: () => onNavigate('dealer-relocation')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'My Outlets',
|
||||||
|
value: dashboardData.outlets?.length || 0,
|
||||||
|
icon: ShoppingBag,
|
||||||
|
color: 'bg-purple-500',
|
||||||
|
change: 'Registered',
|
||||||
|
onClick: () => {}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Total Requests',
|
title: 'Total Requests',
|
||||||
value: 2,
|
value: statsSummary.total,
|
||||||
icon: TrendingUp,
|
icon: TrendingUp,
|
||||||
color: 'bg-green-500',
|
color: 'bg-green-500',
|
||||||
change: 'All time',
|
change: 'All time',
|
||||||
@ -39,24 +81,6 @@ export function DealerDashboard({ currentUser, onNavigate }: DealerDashboardProp
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Recent requests by the dealer
|
// Recent requests by the dealer
|
||||||
const recentRequests = [
|
|
||||||
{
|
|
||||||
id: 'CON-001',
|
|
||||||
type: 'Constitutional Change',
|
|
||||||
title: 'Change from Proprietorship to Partnership',
|
|
||||||
status: 'RBM Review',
|
|
||||||
date: '2025-12-15',
|
|
||||||
color: 'bg-blue-100 text-blue-700 border-blue-300'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'RLO-001',
|
|
||||||
type: 'Relocation',
|
|
||||||
title: 'Moving to Andheri East, Mumbai',
|
|
||||||
status: 'DD ZM Review',
|
|
||||||
date: '2025-12-10',
|
|
||||||
color: 'bg-amber-100 text-amber-700 border-amber-300'
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Quick actions for dealer
|
// Quick actions for dealer
|
||||||
const quickActions = [
|
const quickActions = [
|
||||||
@ -84,12 +108,12 @@ export function DealerDashboard({ currentUser, onNavigate }: DealerDashboardProp
|
|||||||
<div className="bg-gradient-to-r from-amber-500 to-amber-600 rounded-lg p-6 text-white">
|
<div className="bg-gradient-to-r from-amber-500 to-amber-600 rounded-lg p-6 text-white">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-white mb-2">Welcome back, {currentUser?.name}!</h1>
|
<h1 className="text-white mb-2">Welcome back, {profile.name || currentUser?.name}!</h1>
|
||||||
<p className="text-amber-100">
|
<p className="text-amber-100">
|
||||||
Dealer Code: DL-MH-001 • Royal Enfield Mumbai
|
Dealer Code: {profile.dealerCode} • {profile.businessName}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-amber-100 text-sm mt-1">
|
<p className="text-amber-100 text-sm mt-1">
|
||||||
Bandra West, Mumbai, Maharashtra
|
{primaryOutlet.name} • {primaryOutlet.location}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
@ -167,7 +191,7 @@ export function DealerDashboard({ currentUser, onNavigate }: DealerDashboardProp
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{recentRequests.map((request) => (
|
{recentRequests.map((request: any) => (
|
||||||
<div
|
<div
|
||||||
key={request.id}
|
key={request.id}
|
||||||
className="flex items-center justify-between p-4 border border-slate-200 rounded-lg hover:bg-slate-50 transition-colors"
|
className="flex items-center justify-between p-4 border border-slate-200 rounded-lg hover:bg-slate-50 transition-colors"
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { RefreshCcw, Plus, Eye, Calendar, FileText } from 'lucide-react';
|
import { RefreshCcw, Plus, Eye, Calendar, FileText, Loader2 } from 'lucide-react';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card';
|
||||||
import { Badge } from '../ui/badge';
|
import { Badge } from '../ui/badge';
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
@ -8,31 +8,16 @@ import { Input } from '../ui/input';
|
|||||||
import { Label } from '../ui/label';
|
import { Label } from '../ui/label';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
|
||||||
import { Textarea } from '../ui/textarea';
|
import { Textarea } from '../ui/textarea';
|
||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { User as UserType } from '../../lib/mock-data';
|
import { User as UserType } from '../../lib/mock-data';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import { dealerService } from '../../services/dealer.service';
|
||||||
|
|
||||||
interface DealerConstitutionalChangePageProps {
|
interface DealerConstitutionalChangePageProps {
|
||||||
currentUser: UserType | null;
|
currentUser: UserType | null;
|
||||||
onViewDetails?: (id: string) => void;
|
onViewDetails?: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock constitutional change requests for this dealer
|
|
||||||
const mockDealerConstitutionalChanges = [
|
|
||||||
{
|
|
||||||
id: 'CON-001',
|
|
||||||
dealerCode: 'DL-MH-001',
|
|
||||||
dealerName: 'Amit Sharma Motors',
|
|
||||||
currentConstitution: 'Proprietorship',
|
|
||||||
proposedConstitution: 'Partnership',
|
|
||||||
reason: 'Adding family members as partners',
|
|
||||||
status: 'RBM Review',
|
|
||||||
submittedOn: '2025-12-15',
|
|
||||||
currentStage: 'RBM',
|
|
||||||
progressPercentage: 25
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const getStatusColor = (status: string) => {
|
const getStatusColor = (status: string) => {
|
||||||
if (status === 'Completed') return 'bg-green-100 text-green-700 border-green-300';
|
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('Review') || status.includes('Pending')) return 'bg-yellow-100 text-yellow-700 border-yellow-300';
|
||||||
@ -44,13 +29,39 @@ const constitutionTypes = ['Proprietorship', 'Partnership', 'LLP', 'Pvt Ltd'];
|
|||||||
|
|
||||||
export function DealerConstitutionalChangePage({ currentUser, onViewDetails }: DealerConstitutionalChangePageProps) {
|
export function DealerConstitutionalChangePage({ currentUser, onViewDetails }: DealerConstitutionalChangePageProps) {
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
const [currentConstitution, setCurrentConstitution] = useState('Proprietorship'); // Pre-filled
|
const [currentConstitution, setCurrentConstitution] = useState('');
|
||||||
const [proposedConstitution, setProposedConstitution] = useState('');
|
const [proposedConstitution, setProposedConstitution] = useState('');
|
||||||
const [reason, setReason] = useState('');
|
const [reason, setReason] = useState('');
|
||||||
const [newPartners, setNewPartners] = useState('');
|
const [newPartners, setNewPartners] = useState('');
|
||||||
const [shareholdingPattern, setShareholdingPattern] = useState('');
|
const [shareholdingPattern, setShareholdingPattern] = useState('');
|
||||||
|
|
||||||
|
const [requests, setRequests] = useState<any[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
const [profile, setProfile] = useState<any>(null);
|
||||||
|
|
||||||
const handleSubmitRequest = (e: React.FormEvent) => {
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const dashboard = await dealerService.getDashboardData();
|
||||||
|
const constitutionalRes = await dealerService.getConstitutionalChanges();
|
||||||
|
|
||||||
|
setProfile(dashboard.profile);
|
||||||
|
setCurrentConstitution(dashboard.profile?.constitutionType || 'Proprietorship');
|
||||||
|
setRequests(constitutionalRes.requests || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fetch constitutional data error:', error);
|
||||||
|
toast.error('Failed to load requests');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmitRequest = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!proposedConstitution) {
|
if (!proposedConstitution) {
|
||||||
@ -68,32 +79,50 @@ export function DealerConstitutionalChangePage({ currentUser, onViewDetails }: D
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success('Constitutional change request submitted successfully');
|
try {
|
||||||
setIsDialogOpen(false);
|
setSubmitting(true);
|
||||||
|
const payload = {
|
||||||
// Reset form
|
currentConstitution,
|
||||||
setProposedConstitution('');
|
changeType: proposedConstitution,
|
||||||
setReason('');
|
reason,
|
||||||
setNewPartners('');
|
newPartnersDetails: newPartners,
|
||||||
setShareholdingPattern('');
|
shareholdingPattern
|
||||||
|
};
|
||||||
|
|
||||||
|
await dealerService.submitConstitutionalChange(payload);
|
||||||
|
toast.success('Constitutional change request submitted successfully');
|
||||||
|
setIsDialogOpen(false);
|
||||||
|
fetchData(); // Refresh list
|
||||||
|
|
||||||
|
// Reset form
|
||||||
|
setProposedConstitution('');
|
||||||
|
setReason('');
|
||||||
|
setNewPartners('');
|
||||||
|
setShareholdingPattern('');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Submit constitutional change error:', error);
|
||||||
|
toast.error('Failed to submit constitutional change request');
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const stats = [
|
const stats = [
|
||||||
{
|
{
|
||||||
title: 'Total Requests',
|
title: 'Total Requests',
|
||||||
value: mockDealerConstitutionalChanges.length,
|
value: requests.length,
|
||||||
icon: RefreshCcw,
|
icon: RefreshCcw,
|
||||||
color: 'bg-blue-500',
|
color: 'bg-blue-500',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Pending',
|
title: 'Pending',
|
||||||
value: mockDealerConstitutionalChanges.filter(r => r.status !== 'Completed' && r.status !== 'Rejected').length,
|
value: requests.filter(r => r.status !== 'Completed' && r.status !== 'Rejected').length,
|
||||||
icon: Calendar,
|
icon: Calendar,
|
||||||
color: 'bg-yellow-500',
|
color: 'bg-yellow-500',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Completed',
|
title: 'Completed',
|
||||||
value: mockDealerConstitutionalChanges.filter(r => r.status === 'Completed').length,
|
value: requests.filter(r => r.status === 'Completed').length,
|
||||||
icon: FileText,
|
icon: FileText,
|
||||||
color: 'bg-green-500',
|
color: 'bg-green-500',
|
||||||
},
|
},
|
||||||
@ -101,244 +130,271 @@ export function DealerConstitutionalChangePage({ currentUser, onViewDetails }: D
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Header */}
|
{/* Loading Overlay */}
|
||||||
<div className="flex items-center justify-between">
|
{loading && (
|
||||||
<div>
|
<div className="min-h-[400px] flex items-center justify-center">
|
||||||
<h1 className="text-slate-900 mb-2">My Constitutional Change Requests</h1>
|
<Loader2 className="w-8 h-8 text-blue-600 animate-spin" />
|
||||||
<p className="text-slate-600">
|
|
||||||
Submit and track requests for changing your business constitution
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
|
||||||
<DialogTrigger asChild>
|
{!loading && (
|
||||||
<Button className="bg-blue-600 hover:bg-blue-700">
|
<>
|
||||||
<Plus className="w-4 h-4 mr-2" />
|
{/* Header */}
|
||||||
New Constitutional Change
|
<div className="flex items-center justify-between">
|
||||||
</Button>
|
<div>
|
||||||
</DialogTrigger>
|
<h1 className="text-slate-900 mb-2">My Constitutional Change Requests</h1>
|
||||||
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
<p className="text-slate-600">
|
||||||
<DialogHeader>
|
Submit and track requests for changing your business constitution
|
||||||
<DialogTitle>Submit Constitutional Change Request</DialogTitle>
|
</p>
|
||||||
<DialogDescription>
|
</div>
|
||||||
Request to change your dealership's business constitution structure
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmitRequest} className="space-y-4">
|
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||||
{/* Dealer Info */}
|
<DialogTrigger asChild>
|
||||||
<div className="bg-slate-50 border border-slate-200 rounded-lg p-4 space-y-2">
|
<Button className="bg-blue-600 hover:bg-blue-700">
|
||||||
<h3 className="text-slate-900">Current Dealership Information</h3>
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
New Constitutional Change
|
||||||
<div>
|
|
||||||
<span className="text-slate-600">Dealer Code:</span>
|
|
||||||
<p className="text-slate-900">DL-MH-001</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="text-slate-600">Dealer Name:</span>
|
|
||||||
<p className="text-slate-900">Amit Sharma Motors</p>
|
|
||||||
</div>
|
|
||||||
<div className="col-span-2">
|
|
||||||
<span className="text-slate-600">Current Constitution:</span>
|
|
||||||
<p className="text-slate-900">Proprietorship</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Constitution Change */}
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="currentConstitution">Current Constitution *</Label>
|
|
||||||
<Input
|
|
||||||
id="currentConstitution"
|
|
||||||
value={currentConstitution}
|
|
||||||
disabled
|
|
||||||
className="bg-slate-100"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="proposedConstitution">Proposed Constitution *</Label>
|
|
||||||
<Select value={proposedConstitution} onValueChange={setProposedConstitution} required>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select new constitution" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{constitutionTypes
|
|
||||||
.filter(type => type !== currentConstitution)
|
|
||||||
.map(type => (
|
|
||||||
<SelectItem key={type} value={type}>{type}</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Reason */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="reason">Reason for Change *</Label>
|
|
||||||
<Textarea
|
|
||||||
id="reason"
|
|
||||||
placeholder="Please provide detailed reason for constitutional change..."
|
|
||||||
value={reason}
|
|
||||||
onChange={(e) => setReason(e.target.value)}
|
|
||||||
rows={4}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* New Partners (if applicable) */}
|
|
||||||
{(proposedConstitution === 'Partnership' || proposedConstitution === 'LLP') && (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="newPartners">Details of New Partners/Members</Label>
|
|
||||||
<Textarea
|
|
||||||
id="newPartners"
|
|
||||||
placeholder="Name, relationship, and experience of new partners..."
|
|
||||||
value={newPartners}
|
|
||||||
onChange={(e) => setNewPartners(e.target.value)}
|
|
||||||
rows={3}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Shareholding Pattern */}
|
|
||||||
{(proposedConstitution === 'Pvt Ltd' || proposedConstitution === 'LLP') && (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="shareholdingPattern">Proposed Shareholding Pattern</Label>
|
|
||||||
<Textarea
|
|
||||||
id="shareholdingPattern"
|
|
||||||
placeholder="Details of share distribution among partners/directors..."
|
|
||||||
value={shareholdingPattern}
|
|
||||||
onChange={(e) => setShareholdingPattern(e.target.value)}
|
|
||||||
rows={3}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Document Requirements */}
|
|
||||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
|
||||||
<h4 className="text-blue-900 mb-2">Documents Required (to be uploaded later)</h4>
|
|
||||||
<ul className="text-blue-800 text-sm space-y-1">
|
|
||||||
<li>• GST Registration Certificate</li>
|
|
||||||
<li>• Firm PAN Copy</li>
|
|
||||||
<li>• Partnership Deed (if applicable)</li>
|
|
||||||
<li>• LLP Agreement (if applicable)</li>
|
|
||||||
<li>• Certificate of Incorporation (if applicable)</li>
|
|
||||||
<li>• MOA & AOA (if applicable)</li>
|
|
||||||
<li>• Board Resolution</li>
|
|
||||||
<li>• Aadhaar & PAN of all partners/directors</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<DialogFooter>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => setIsDialogOpen(false)}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
</DialogTrigger>
|
||||||
type="submit"
|
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||||
className="bg-blue-600 hover:bg-blue-700"
|
<DialogHeader>
|
||||||
>
|
<DialogTitle>Submit Constitutional Change Request</DialogTitle>
|
||||||
Submit Request
|
<DialogDescription>
|
||||||
</Button>
|
Request to change your dealership's business constitution structure
|
||||||
</DialogFooter>
|
</DialogDescription>
|
||||||
</form>
|
</DialogHeader>
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
<form onSubmit={handleSubmitRequest} className="space-y-4">
|
||||||
</div>
|
{/* Dealer Info */}
|
||||||
|
<div className="bg-slate-50 border border-slate-200 rounded-lg p-4 space-y-2">
|
||||||
{/* Stats */}
|
<h3 className="text-slate-900">Current Dealership Information</h3>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||||
{stats.map((stat, index) => {
|
<div>
|
||||||
const Icon = stat.icon;
|
<span className="text-slate-600">Dealer Code:</span>
|
||||||
return (
|
<p className="text-slate-900">{profile?.dealerCode || 'N/A'}</p>
|
||||||
<Card key={index}>
|
</div>
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
<div>
|
||||||
<CardTitle className="text-sm">{stat.title}</CardTitle>
|
<span className="text-slate-600">Dealer Name:</span>
|
||||||
<div className={`${stat.color} p-2 rounded-lg`}>
|
<p className="text-slate-900">{profile?.businessName || 'N/A'}</p>
|
||||||
<Icon className="h-4 w-4 text-white" />
|
</div>
|
||||||
</div>
|
<div className="col-span-2">
|
||||||
</CardHeader>
|
<span className="text-slate-600">Current Constitution:</span>
|
||||||
<CardContent>
|
<p className="text-slate-900">{currentConstitution}</p>
|
||||||
<div className="text-slate-900 text-2xl">{stat.value}</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Requests Table */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>My Constitutional Change Requests</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
View and track all your constitutional change requests
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>Request ID</TableHead>
|
|
||||||
<TableHead>Current</TableHead>
|
|
||||||
<TableHead>Proposed</TableHead>
|
|
||||||
<TableHead>Submitted On</TableHead>
|
|
||||||
<TableHead>Current Status</TableHead>
|
|
||||||
<TableHead>Progress</TableHead>
|
|
||||||
<TableHead>Actions</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{mockDealerConstitutionalChanges.map((request) => (
|
|
||||||
<TableRow key={request.id}>
|
|
||||||
<TableCell>
|
|
||||||
<span className="text-slate-900">{request.id}</span>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Badge variant="outline">{request.currentConstitution}</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Badge className="bg-blue-100 text-blue-700 border-blue-300">
|
|
||||||
{request.proposedConstitution}
|
|
||||||
</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-slate-600">
|
|
||||||
{request.submittedOn}
|
|
||||||
</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">
|
|
||||||
<div
|
|
||||||
className="bg-blue-600 h-2 rounded-full"
|
|
||||||
style={{ width: `${request.progressPercentage}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs text-slate-600">{request.progressPercentage}%</span>
|
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</div>
|
||||||
<TableCell>
|
|
||||||
|
{/* Constitution Change */}
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="currentConstitution">Current Constitution *</Label>
|
||||||
|
<Input
|
||||||
|
id="currentConstitution"
|
||||||
|
value={currentConstitution}
|
||||||
|
disabled
|
||||||
|
className="bg-slate-100"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="proposedConstitution">Proposed Constitution *</Label>
|
||||||
|
<Select value={proposedConstitution} onValueChange={setProposedConstitution} required>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select new constitution" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{constitutionTypes
|
||||||
|
.filter(type => type !== currentConstitution)
|
||||||
|
.map(type => (
|
||||||
|
<SelectItem key={type} value={type}>{type}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Reason */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="reason">Reason for Change *</Label>
|
||||||
|
<Textarea
|
||||||
|
id="reason"
|
||||||
|
placeholder="Please provide detailed reason for constitutional change..."
|
||||||
|
value={reason}
|
||||||
|
onChange={(e) => setReason(e.target.value)}
|
||||||
|
rows={4}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* New Partners (if applicable) */}
|
||||||
|
{(proposedConstitution === 'Partnership' || proposedConstitution === 'LLP') && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="newPartners">Details of New Partners/Members</Label>
|
||||||
|
<Textarea
|
||||||
|
id="newPartners"
|
||||||
|
placeholder="Name, relationship, and experience of new partners..."
|
||||||
|
value={newPartners}
|
||||||
|
onChange={(e) => setNewPartners(e.target.value)}
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Shareholding Pattern */}
|
||||||
|
{(proposedConstitution === 'Pvt Ltd' || proposedConstitution === 'LLP') && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="shareholdingPattern">Proposed Shareholding Pattern</Label>
|
||||||
|
<Textarea
|
||||||
|
id="shareholdingPattern"
|
||||||
|
placeholder="Details of share distribution among partners/directors..."
|
||||||
|
value={shareholdingPattern}
|
||||||
|
onChange={(e) => setShareholdingPattern(e.target.value)}
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Document Requirements */}
|
||||||
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||||
|
<h4 className="text-blue-900 mb-2">Documents Required (to be uploaded later)</h4>
|
||||||
|
<ul className="text-blue-800 text-sm space-y-1">
|
||||||
|
<li>• GST Registration Certificate</li>
|
||||||
|
<li>• Firm PAN Copy</li>
|
||||||
|
<li>• Partnership Deed (if applicable)</li>
|
||||||
|
<li>• LLP Agreement (if applicable)</li>
|
||||||
|
<li>• Certificate of Incorporation (if applicable)</li>
|
||||||
|
<li>• MOA & AOA (if applicable)</li>
|
||||||
|
<li>• Board Resolution</li>
|
||||||
|
<li>• Aadhaar & PAN of all partners/directors</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
type="button"
|
||||||
size="sm"
|
variant="outline"
|
||||||
onClick={() => onViewDetails && onViewDetails(request.id)}
|
onClick={() => setIsDialogOpen(false)}
|
||||||
>
|
>
|
||||||
<Eye className="w-4 h-4 mr-1" />
|
Cancel
|
||||||
View
|
|
||||||
</Button>
|
</Button>
|
||||||
</TableCell>
|
<Button
|
||||||
</TableRow>
|
type="submit"
|
||||||
))}
|
className="bg-blue-600 hover:bg-blue-700"
|
||||||
</TableBody>
|
disabled={submitting}
|
||||||
</Table>
|
>
|
||||||
</CardContent>
|
{submitting ? (
|
||||||
</Card>
|
<>
|
||||||
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||||
|
Submitting...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
'Submit 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 Constitutional Change Requests</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
View and track all your constitutional change requests
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Request ID</TableHead>
|
||||||
|
<TableHead>Current</TableHead>
|
||||||
|
<TableHead>Proposed</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 constitutional change requests found
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
requests.map((request) => (
|
||||||
|
<TableRow key={request.id}>
|
||||||
|
<TableCell>
|
||||||
|
<span className="text-slate-900">{request.requestId}</span>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Badge variant="outline">{request.currentConstitution}</Badge>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Badge className="bg-blue-100 text-blue-700 border-blue-300">
|
||||||
|
{request.changeType}
|
||||||
|
</Badge>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-slate-600">
|
||||||
|
{new Date(request.createdAt).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-blue-600 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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { MapPin, Plus, Eye, Calendar, FileText, Building, Navigation } from 'lucide-react';
|
import { MapPin, Plus, Eye, Calendar, Building2, Loader2, ArrowRight } from 'lucide-react';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card';
|
||||||
import { Badge } from '../ui/badge';
|
import { Badge } from '../ui/badge';
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
@ -8,32 +8,16 @@ import { Input } from '../ui/input';
|
|||||||
import { Label } from '../ui/label';
|
import { Label } from '../ui/label';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
|
||||||
import { Textarea } from '../ui/textarea';
|
import { Textarea } from '../ui/textarea';
|
||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { User as UserType } from '../../lib/mock-data';
|
import { User as UserType } from '../../lib/mock-data';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import { dealerService } from '../../services/dealer.service';
|
||||||
|
|
||||||
interface DealerRelocationPageProps {
|
interface DealerRelocationPageProps {
|
||||||
currentUser: UserType | null;
|
currentUser: UserType | null;
|
||||||
onViewDetails?: (id: string) => void;
|
onViewDetails?: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock relocation requests for this dealer
|
|
||||||
const mockDealerRelocations = [
|
|
||||||
{
|
|
||||||
id: 'RLO-001',
|
|
||||||
dealerCode: 'DL-MH-001',
|
|
||||||
dealerName: 'Amit Sharma Motors',
|
|
||||||
currentLocation: 'Bandra West, Mumbai',
|
|
||||||
proposedLocation: 'Andheri East, Mumbai',
|
|
||||||
distance: '12 km',
|
|
||||||
reason: 'Better connectivity and higher footfall area',
|
|
||||||
status: 'DD ZM Review',
|
|
||||||
submittedOn: '2025-12-20',
|
|
||||||
currentStage: 'DD-ZM',
|
|
||||||
progressPercentage: 25
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const getStatusColor = (status: string) => {
|
const getStatusColor = (status: string) => {
|
||||||
if (status === 'Completed') return 'bg-green-100 text-green-700 border-green-300';
|
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('Review') || status.includes('Pending')) return 'bg-yellow-100 text-yellow-700 border-yellow-300';
|
||||||
@ -43,58 +27,54 @@ const getStatusColor = (status: string) => {
|
|||||||
|
|
||||||
export function DealerRelocationPage({ currentUser, onViewDetails }: DealerRelocationPageProps) {
|
export function DealerRelocationPage({ currentUser, onViewDetails }: DealerRelocationPageProps) {
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
const [proposedAddress, setProposedAddress] = useState('');
|
const [selectedOutlet, setSelectedOutlet] = useState<any | null>(null);
|
||||||
const [proposedCity, setProposedCity] = useState('');
|
const [newCity, setNewCity] = useState('');
|
||||||
const [proposedState, setProposedState] = useState('');
|
const [newState, setNewState] = useState('');
|
||||||
const [proposedPincode, setProposedPincode] = useState('');
|
const [newAddress, setNewAddress] = useState('');
|
||||||
const [distance, setDistance] = useState('');
|
|
||||||
const [propertyType, setPropertyType] = useState('');
|
|
||||||
const [expectedDate, setExpectedDate] = useState('');
|
|
||||||
const [reason, setReason] = useState('');
|
const [reason, setReason] = useState('');
|
||||||
const [locationMode, setLocationMode] = useState<'manual' | 'map'>('manual');
|
|
||||||
const [mapCoordinates, setMapCoordinates] = useState({ lat: 19.0760, lng: 72.8777 });
|
const [outlets, setOutlets] = useState<any[]>([]);
|
||||||
const [selectedLocation, setSelectedLocation] = useState<{ lat: number; lng: number } | null>(null);
|
const [requests, setRequests] = useState<any[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
const [profile, setProfile] = useState<any>(null);
|
||||||
|
|
||||||
const handleMapClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
useEffect(() => {
|
||||||
const rect = e.currentTarget.getBoundingClientRect();
|
fetchData();
|
||||||
const x = e.clientX - rect.left;
|
}, []);
|
||||||
const y = e.clientY - rect.top;
|
|
||||||
|
const fetchData = async () => {
|
||||||
const lat = mapCoordinates.lat + (y - rect.height / 2) / 1000;
|
try {
|
||||||
const lng = mapCoordinates.lng + (x - rect.width / 2) / 1000;
|
setLoading(true);
|
||||||
|
const dashboard = await dealerService.getDashboardData();
|
||||||
setSelectedLocation({ lat, lng });
|
const relocationRes = await dealerService.getRelocationRequests();
|
||||||
|
|
||||||
const mockLocations = [
|
setOutlets(dashboard.outlets || []);
|
||||||
{ city: 'Mumbai', state: 'Maharashtra', pincode: '400001', address: 'Nariman Point, South Mumbai' },
|
setProfile(dashboard.profile);
|
||||||
{ city: 'Mumbai', state: 'Maharashtra', pincode: '400051', address: 'Andheri East, Mumbai' },
|
setRequests(relocationRes.requests || []);
|
||||||
{ city: 'Mumbai', state: 'Maharashtra', pincode: '400070', address: 'Powai, Mumbai' },
|
} catch (error) {
|
||||||
];
|
console.error('Fetch relocation data error:', error);
|
||||||
|
toast.error('Failed to load outlets and requests');
|
||||||
const randomLocation = mockLocations[Math.floor(Math.random() * mockLocations.length)];
|
} finally {
|
||||||
setProposedAddress(randomLocation.address);
|
setLoading(false);
|
||||||
setProposedCity(randomLocation.city);
|
}
|
||||||
setProposedState(randomLocation.state);
|
|
||||||
setProposedPincode(randomLocation.pincode);
|
|
||||||
|
|
||||||
toast.success('Location selected from map');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmitRequest = (e: React.FormEvent) => {
|
const handleOpenRelocationDialog = (outlet: any) => {
|
||||||
|
setSelectedOutlet(outlet);
|
||||||
|
setIsDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmitRequest = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!proposedAddress.trim() || !proposedCity.trim() || !proposedState.trim() || !proposedPincode.trim()) {
|
if (!selectedOutlet) {
|
||||||
toast.error('Please enter complete proposed location details');
|
toast.error('Please select an outlet');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!distance.trim()) {
|
if (!newCity.trim() || !newState.trim() || !newAddress.trim()) {
|
||||||
toast.error('Please enter distance from current location');
|
toast.error('Please provide complete relocation details');
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!propertyType) {
|
|
||||||
toast.error('Please select property type');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,422 +83,311 @@ export function DealerRelocationPage({ currentUser, onViewDetails }: DealerReloc
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success('Relocation request submitted successfully');
|
try {
|
||||||
setIsDialogOpen(false);
|
setSubmitting(true);
|
||||||
|
const payload = {
|
||||||
// Reset form
|
outletId: selectedOutlet.id,
|
||||||
setProposedAddress('');
|
currentLocation: selectedOutlet.location,
|
||||||
setProposedCity('');
|
newCity,
|
||||||
setProposedState('');
|
newState,
|
||||||
setProposedPincode('');
|
newAddress,
|
||||||
setDistance('');
|
reason
|
||||||
setPropertyType('');
|
};
|
||||||
setExpectedDate('');
|
|
||||||
setReason('');
|
await dealerService.submitRelocationRequest(payload);
|
||||||
setLocationMode('manual');
|
toast.success(`Relocation request submitted successfully for ${selectedOutlet.name}`);
|
||||||
setSelectedLocation(null);
|
setIsDialogOpen(false);
|
||||||
|
fetchData(); // Refresh list
|
||||||
|
|
||||||
|
// Reset form
|
||||||
|
setSelectedOutlet(null);
|
||||||
|
setNewCity('');
|
||||||
|
setNewState('');
|
||||||
|
setNewAddress('');
|
||||||
|
setReason('');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Submit relocation error:', error);
|
||||||
|
toast.error('Failed to submit relocation request');
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const stats = [
|
const stats = [
|
||||||
{
|
{
|
||||||
title: 'Total Requests',
|
title: 'Total Requests',
|
||||||
value: mockDealerRelocations.length,
|
value: requests.length,
|
||||||
icon: MapPin,
|
icon: MapPin,
|
||||||
color: 'bg-amber-500',
|
color: 'bg-blue-500',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Pending',
|
title: 'Pending',
|
||||||
value: mockDealerRelocations.filter(r => r.status !== 'Completed' && r.status !== 'Rejected').length,
|
value: requests.filter(r => r.status !== 'Completed' && r.status !== 'Rejected').length,
|
||||||
icon: Calendar,
|
icon: Calendar,
|
||||||
color: 'bg-yellow-500',
|
color: 'bg-yellow-500',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Completed',
|
title: 'Approved',
|
||||||
value: mockDealerRelocations.filter(r => r.status === 'Completed').length,
|
value: requests.filter(r => r.status === 'Completed').length,
|
||||||
icon: FileText,
|
icon: Building2,
|
||||||
color: 'bg-green-500',
|
color: 'bg-green-500',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Header */}
|
{/* Loading Overlay */}
|
||||||
<div className="flex items-center justify-between">
|
{loading && (
|
||||||
<div>
|
<div className="min-h-[400px] flex items-center justify-center">
|
||||||
<h1 className="text-slate-900 mb-2">My Relocation Requests</h1>
|
<Loader2 className="w-8 h-8 text-amber-600 animate-spin" />
|
||||||
<p className="text-slate-600">
|
|
||||||
Submit and track requests for relocating your dealership
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
|
||||||
<DialogTrigger asChild>
|
{!loading && (
|
||||||
<Button className="bg-amber-600 hover:bg-amber-700">
|
<>
|
||||||
<Plus className="w-4 h-4 mr-2" />
|
{/* Header */}
|
||||||
New Relocation Request
|
<div className="flex items-center justify-between">
|
||||||
</Button>
|
<div>
|
||||||
</DialogTrigger>
|
<h1 className="text-slate-900 mb-2">Relocation Requests</h1>
|
||||||
<DialogContent className="max-w-3xl max-h-[90vh] overflow-y-auto">
|
<p className="text-slate-600">
|
||||||
<DialogHeader>
|
Request to relocate your existing dealership or studio to a new location
|
||||||
<DialogTitle>Submit Relocation Request</DialogTitle>
|
</p>
|
||||||
<DialogDescription>
|
</div>
|
||||||
Request to relocate your dealership to a new location
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
|
|
||||||
<form onSubmit={handleSubmitRequest} className="space-y-4">
|
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||||
{/* Current Dealer Info */}
|
<DialogTrigger asChild>
|
||||||
<div className="bg-slate-50 border border-slate-200 rounded-lg p-4 space-y-3">
|
<Button className="bg-amber-600 hover:bg-amber-700 text-white">
|
||||||
<h3 className="text-slate-900">Current Dealership Details</h3>
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
New Relocation Request
|
||||||
<div>
|
</Button>
|
||||||
<span className="text-slate-600">Dealer Code:</span>
|
</DialogTrigger>
|
||||||
<p className="text-slate-900">DL-MH-001</p>
|
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||||
</div>
|
<DialogHeader>
|
||||||
<div>
|
<DialogTitle>Submit Relocation Request</DialogTitle>
|
||||||
<span className="text-slate-600">Dealer Name:</span>
|
<DialogDescription>
|
||||||
<p className="text-slate-900">Amit Sharma Motors</p>
|
Provide details about the outlet you want to relocate and its proposed new location
|
||||||
</div>
|
</DialogDescription>
|
||||||
<div className="col-span-2">
|
</DialogHeader>
|
||||||
<span className="text-slate-600">Current Location:</span>
|
|
||||||
<p className="text-slate-900">123, MG Road, Bandra West, Mumbai, Maharashtra - 400050</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Proposed New Location */}
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<h3 className="text-slate-900">Proposed New Location *</h3>
|
|
||||||
|
|
||||||
{/* Location Mode Toggle */}
|
|
||||||
<div className="flex items-center gap-2 bg-slate-100 rounded-lg p-1">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setLocationMode('manual')}
|
|
||||||
className={`px-3 py-1 rounded text-sm transition-colors ${
|
|
||||||
locationMode === 'manual'
|
|
||||||
? 'bg-white text-slate-900 shadow-sm'
|
|
||||||
: 'text-slate-600 hover:text-slate-900'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Manual Entry
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setLocationMode('map')}
|
|
||||||
className={`px-3 py-1 rounded text-sm transition-colors flex items-center gap-1 ${
|
|
||||||
locationMode === 'map'
|
|
||||||
? 'bg-white text-slate-900 shadow-sm'
|
|
||||||
: 'text-slate-600 hover:text-slate-900'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<MapPin className="w-3 h-3" />
|
|
||||||
Map Location
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Map Mode */}
|
|
||||||
{locationMode === 'map' && (
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="border-2 border-amber-300 rounded-lg overflow-hidden">
|
|
||||||
<div
|
|
||||||
onClick={handleMapClick}
|
|
||||||
className="relative h-64 bg-gradient-to-br from-green-100 via-blue-50 to-amber-50 cursor-crosshair"
|
|
||||||
style={{
|
|
||||||
backgroundImage: `
|
|
||||||
linear-gradient(to right, rgba(148, 163, 184, 0.1) 1px, transparent 1px),
|
|
||||||
linear-gradient(to bottom, rgba(148, 163, 184, 0.1) 1px, transparent 1px)
|
|
||||||
`,
|
|
||||||
backgroundSize: '20px 20px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="absolute inset-0">
|
|
||||||
<div className="absolute top-1/4 left-0 right-0 h-1 bg-slate-300 opacity-30" />
|
|
||||||
<div className="absolute top-1/2 left-0 right-0 h-2 bg-slate-400 opacity-40" />
|
|
||||||
<div className="absolute top-3/4 left-0 right-0 h-1 bg-slate-300 opacity-30" />
|
|
||||||
<div className="absolute left-1/4 top-0 bottom-0 w-1 bg-slate-300 opacity-30" />
|
|
||||||
<div className="absolute left-1/2 top-0 bottom-0 w-2 bg-slate-400 opacity-40" />
|
|
||||||
<div className="absolute left-3/4 top-0 bottom-0 w-1 bg-slate-300 opacity-30" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
|
|
||||||
<div className="flex flex-col items-center">
|
|
||||||
<Building className="w-6 h-6 text-blue-600" />
|
|
||||||
<div className="text-xs text-blue-900 bg-white px-2 py-1 rounded shadow-sm mt-1">
|
|
||||||
Current Location
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedLocation && (
|
|
||||||
<div className="absolute top-1/3 left-2/3 transform -translate-x-1/2 -translate-y-full">
|
|
||||||
<div className="flex flex-col items-center animate-bounce">
|
|
||||||
<MapPin className="w-8 h-8 text-amber-600 drop-shadow-lg" />
|
|
||||||
<div className="text-xs text-amber-900 bg-amber-100 px-2 py-1 rounded shadow-md border border-amber-300">
|
|
||||||
New Location
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="absolute bottom-2 left-2 bg-white/90 px-3 py-2 rounded shadow-sm border border-slate-200">
|
|
||||||
<p className="text-xs text-slate-700">
|
|
||||||
<MapPin className="w-3 h-3 inline mr-1" />
|
|
||||||
Click anywhere on the map to select new location
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedLocation && (
|
|
||||||
<div className="absolute top-2 right-2 bg-amber-600 text-white px-3 py-2 rounded shadow-md text-xs">
|
|
||||||
Lat: {selectedLocation.lat.toFixed(4)}, Lng: {selectedLocation.lng.toFixed(4)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedLocation && (
|
|
||||||
<div className="bg-green-50 border border-green-200 rounded-lg p-3 text-sm text-green-800">
|
|
||||||
✓ Location selected! Address details auto-filled below.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Address Fields */}
|
<form onSubmit={handleSubmitRequest} className="space-y-4">
|
||||||
<div className="space-y-2">
|
{/* Select Outlet */}
|
||||||
<Label htmlFor="proposedAddress">Complete Address *</Label>
|
|
||||||
<Input
|
|
||||||
id="proposedAddress"
|
|
||||||
placeholder="Building/Shop number, Street, Locality"
|
|
||||||
value={proposedAddress}
|
|
||||||
onChange={(e) => setProposedAddress(e.target.value)}
|
|
||||||
required
|
|
||||||
readOnly={locationMode === 'map' && !!selectedLocation}
|
|
||||||
className={locationMode === 'map' && selectedLocation ? 'bg-green-50' : ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-3 gap-3">
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="proposedCity">City *</Label>
|
<Label htmlFor="outlet">Select Outlet to Relocate *</Label>
|
||||||
<Input
|
<Select
|
||||||
id="proposedCity"
|
value={selectedOutlet?.id}
|
||||||
placeholder="City"
|
onValueChange={(val) => setSelectedOutlet(outlets.find(o => o.id === val))}
|
||||||
value={proposedCity}
|
|
||||||
onChange={(e) => setProposedCity(e.target.value)}
|
|
||||||
required
|
required
|
||||||
readOnly={locationMode === 'map' && !!selectedLocation}
|
|
||||||
className={locationMode === 'map' && selectedLocation ? 'bg-green-50' : ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="proposedState">State *</Label>
|
|
||||||
<Input
|
|
||||||
id="proposedState"
|
|
||||||
placeholder="State"
|
|
||||||
value={proposedState}
|
|
||||||
onChange={(e) => setProposedState(e.target.value)}
|
|
||||||
required
|
|
||||||
readOnly={locationMode === 'map' && !!selectedLocation}
|
|
||||||
className={locationMode === 'map' && selectedLocation ? 'bg-green-50' : ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="proposedPincode">Pincode *</Label>
|
|
||||||
<Input
|
|
||||||
id="proposedPincode"
|
|
||||||
placeholder="Pincode"
|
|
||||||
value={proposedPincode}
|
|
||||||
onChange={(e) => setProposedPincode(e.target.value)}
|
|
||||||
required
|
|
||||||
readOnly={locationMode === 'map' && !!selectedLocation}
|
|
||||||
className={locationMode === 'map' && selectedLocation ? 'bg-green-50' : ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Distance & Property Details */}
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="distance">Distance from Current Location *</Label>
|
|
||||||
<Input
|
|
||||||
id="distance"
|
|
||||||
placeholder="e.g., 12 km"
|
|
||||||
value={distance}
|
|
||||||
onChange={(e) => setDistance(e.target.value)}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="propertyType">Property Type *</Label>
|
|
||||||
<Select value={propertyType} onValueChange={setPropertyType} required>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder="Select property type" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="Owned">Owned</SelectItem>
|
|
||||||
<SelectItem value="Leased">Leased</SelectItem>
|
|
||||||
<SelectItem value="Rented">Rented</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Expected Relocation Date */}
|
|
||||||
<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)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Reason */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="reason">Reason for Relocation *</Label>
|
|
||||||
<Textarea
|
|
||||||
id="reason"
|
|
||||||
placeholder="Provide detailed reason for relocation request..."
|
|
||||||
value={reason}
|
|
||||||
onChange={(e) => setReason(e.target.value)}
|
|
||||||
rows={4}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Required Documents Info */}
|
|
||||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
|
||||||
<h4 className="text-blue-900 mb-2">Documents Required (to be uploaded later)</h4>
|
|
||||||
<ul className="text-blue-800 text-sm space-y-1">
|
|
||||||
<li>• Property documents for new location</li>
|
|
||||||
<li>• Lease/Rental agreement for new location</li>
|
|
||||||
<li>• NOC from current landlord</li>
|
|
||||||
<li>• Municipal approvals</li>
|
|
||||||
<li>• Fire safety certificate</li>
|
|
||||||
<li>• Pollution clearance</li>
|
|
||||||
<li>• Layout/Floor plan of new location</li>
|
|
||||||
<li>• Photos of new location</li>
|
|
||||||
<li>• Locality map</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"
|
|
||||||
>
|
|
||||||
Submit 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>
|
|
||||||
View and track all your relocation requests
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>Request ID</TableHead>
|
|
||||||
<TableHead>Current Location</TableHead>
|
|
||||||
<TableHead>Proposed Location</TableHead>
|
|
||||||
<TableHead>Distance</TableHead>
|
|
||||||
<TableHead>Submitted On</TableHead>
|
|
||||||
<TableHead>Status</TableHead>
|
|
||||||
<TableHead>Progress</TableHead>
|
|
||||||
<TableHead>Actions</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{mockDealerRelocations.map((request) => (
|
|
||||||
<TableRow key={request.id}>
|
|
||||||
<TableCell>
|
|
||||||
<span className="text-slate-900">{request.id}</span>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-slate-600">
|
|
||||||
{request.currentLocation}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-slate-900">
|
|
||||||
{request.proposedLocation}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-slate-600">
|
|
||||||
{request.distance}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-slate-600">
|
|
||||||
{request.submittedOn}
|
|
||||||
</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">
|
|
||||||
<div
|
|
||||||
className="bg-amber-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.id)}
|
|
||||||
>
|
>
|
||||||
<Eye className="w-4 h-4 mr-1" />
|
<SelectTrigger>
|
||||||
View
|
<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 */}
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="newCity">Proposed City *</Label>
|
||||||
|
<Input
|
||||||
|
id="newCity"
|
||||||
|
placeholder="Enter new city"
|
||||||
|
value={newCity}
|
||||||
|
onChange={(e) => setNewCity(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="newState">Proposed State *</Label>
|
||||||
|
<Input
|
||||||
|
id="newState"
|
||||||
|
placeholder="Enter new state"
|
||||||
|
value={newState}
|
||||||
|
onChange={(e) => setNewState(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
{/* 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>
|
||||||
</TableCell>
|
<Button
|
||||||
</TableRow>
|
type="submit"
|
||||||
))}
|
className="bg-amber-600 hover:bg-amber-700 text-white"
|
||||||
</TableBody>
|
disabled={submitting}
|
||||||
</Table>
|
>
|
||||||
</CardContent>
|
{submitting ? (
|
||||||
</Card>
|
<>
|
||||||
|
<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">
|
||||||
|
{new Date(request.createdAt).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-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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { FileText, Plus, Eye, Calendar, User, Building2, Store, MapPin, CheckCircle, Clock } from 'lucide-react';
|
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 { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card';
|
||||||
import { Badge } from '../ui/badge';
|
import { Badge } from '../ui/badge';
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
@ -8,107 +8,17 @@ import { Input } from '../ui/input';
|
|||||||
import { Label } from '../ui/label';
|
import { Label } from '../ui/label';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
|
||||||
import { Textarea } from '../ui/textarea';
|
import { Textarea } from '../ui/textarea';
|
||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { User as UserType } from '../../lib/mock-data';
|
import { User as UserType } from '../../lib/mock-data';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import { dealerService } from '../../services/dealer.service';
|
||||||
|
import { resignationService } from '../../services/resignation.service';
|
||||||
|
|
||||||
interface DealerResignationPageProps {
|
interface DealerResignationPageProps {
|
||||||
currentUser: UserType | null;
|
currentUser: UserType | null;
|
||||||
onViewDetails?: (id: string) => void;
|
onViewDetails?: (id: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock outlets owned by dealer
|
|
||||||
interface Outlet {
|
|
||||||
id: string;
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
type: 'Dealership' | 'Studio';
|
|
||||||
address: string;
|
|
||||||
city: string;
|
|
||||||
state: string;
|
|
||||||
status: 'Active' | 'Pending Resignation' | 'Closed';
|
|
||||||
establishedDate: string;
|
|
||||||
hasActiveResignation?: boolean;
|
|
||||||
resignationId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockOutlets: Outlet[] = [
|
|
||||||
{
|
|
||||||
id: 'OUT-001',
|
|
||||||
code: 'DL-MH-001',
|
|
||||||
name: 'Royal Enfield Mumbai',
|
|
||||||
type: 'Dealership',
|
|
||||||
address: 'Plot No. 45, Linking Road, Bandra West',
|
|
||||||
city: 'Mumbai',
|
|
||||||
state: 'Maharashtra',
|
|
||||||
status: 'Active',
|
|
||||||
establishedDate: '2018-06-15',
|
|
||||||
hasActiveResignation: true,
|
|
||||||
resignationId: 'RES-001'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'OUT-002',
|
|
||||||
code: 'ST-MH-002',
|
|
||||||
name: 'Royal Enfield Andheri Studio',
|
|
||||||
type: 'Studio',
|
|
||||||
address: 'Shop 12, Phoenix Market City, Kurla',
|
|
||||||
city: 'Mumbai',
|
|
||||||
state: 'Maharashtra',
|
|
||||||
status: 'Active',
|
|
||||||
establishedDate: '2020-03-20'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'OUT-003',
|
|
||||||
code: 'DL-MH-003',
|
|
||||||
name: 'Royal Enfield Thane Dealership',
|
|
||||||
type: 'Dealership',
|
|
||||||
address: 'Eastern Express Highway, Thane West',
|
|
||||||
city: 'Thane',
|
|
||||||
state: 'Maharashtra',
|
|
||||||
status: 'Active',
|
|
||||||
establishedDate: '2019-09-10'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'OUT-004',
|
|
||||||
code: 'ST-MH-004',
|
|
||||||
name: 'Royal Enfield Pune Studio',
|
|
||||||
type: 'Studio',
|
|
||||||
address: 'FC Road, Deccan Gymkhana',
|
|
||||||
city: 'Pune',
|
|
||||||
state: 'Maharashtra',
|
|
||||||
status: 'Active',
|
|
||||||
establishedDate: '2021-01-05'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Mock resignation requests for this dealer
|
|
||||||
const mockDealerResignations = [
|
|
||||||
{
|
|
||||||
id: 'RES-001',
|
|
||||||
dealerCode: 'DL-MH-001',
|
|
||||||
dealerName: 'Amit Sharma Motors',
|
|
||||||
resignationType: 'Voluntary',
|
|
||||||
lastOperationalDate: '2026-02-28',
|
|
||||||
reason: 'Personal health issues',
|
|
||||||
status: 'ASM Review',
|
|
||||||
submittedOn: '2025-12-20',
|
|
||||||
currentStage: 'ASM',
|
|
||||||
progressPercentage: 15
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'RES-005',
|
|
||||||
dealerCode: 'DL-MH-001',
|
|
||||||
dealerName: 'Amit Sharma Motors',
|
|
||||||
resignationType: 'Voluntary',
|
|
||||||
lastOperationalDate: '2025-06-30',
|
|
||||||
reason: 'Relocation to different city',
|
|
||||||
status: 'Completed',
|
|
||||||
submittedOn: '2025-04-15',
|
|
||||||
currentStage: 'Closed',
|
|
||||||
progressPercentage: 100
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const getStatusColor = (status: string) => {
|
const getStatusColor = (status: string) => {
|
||||||
if (status === 'Completed') return 'bg-green-100 text-green-700 border-green-300';
|
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('Review') || status.includes('Pending')) return 'bg-yellow-100 text-yellow-700 border-yellow-300';
|
||||||
@ -118,19 +28,46 @@ const getStatusColor = (status: string) => {
|
|||||||
|
|
||||||
export function DealerResignationPage({ currentUser, onViewDetails }: DealerResignationPageProps) {
|
export function DealerResignationPage({ currentUser, onViewDetails }: DealerResignationPageProps) {
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
const [selectedOutlet, setSelectedOutlet] = useState<Outlet | null>(null);
|
const [selectedOutlet, setSelectedOutlet] = useState<any | null>(null);
|
||||||
const [resignationType, setResignationType] = useState('');
|
const [resignationType, setResignationType] = useState('');
|
||||||
const [lastOperationalDateSales, setLastOperationalDateSales] = useState('');
|
const [lastOperationalDateSales, setLastOperationalDateSales] = useState('');
|
||||||
const [lastOperationalDateServices, setLastOperationalDateServices] = useState('');
|
const [lastOperationalDateServices, setLastOperationalDateServices] = useState('');
|
||||||
const [reason, setReason] = useState('');
|
const [reason, setReason] = useState('');
|
||||||
const [additionalInfo, setAdditionalInfo] = 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);
|
||||||
|
|
||||||
const handleOpenResignationDialog = (outlet: Outlet) => {
|
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);
|
setSelectedOutlet(outlet);
|
||||||
setIsDialogOpen(true);
|
setIsDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmitRequest = (e: React.FormEvent) => {
|
const handleSubmitRequest = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!resignationType) {
|
if (!resignationType) {
|
||||||
@ -148,34 +85,53 @@ export function DealerResignationPage({ currentUser, onViewDetails }: DealerResi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success(`Resignation request submitted successfully for ${selectedOutlet?.name}`);
|
try {
|
||||||
setIsDialogOpen(false);
|
setSubmitting(true);
|
||||||
|
const payload = {
|
||||||
// Reset form
|
outletId: selectedOutlet?.id,
|
||||||
setSelectedOutlet(null);
|
resignationType,
|
||||||
setResignationType('');
|
lastOperationalDateSales,
|
||||||
setLastOperationalDateSales('');
|
lastOperationalDateServices,
|
||||||
setLastOperationalDateServices('');
|
reason,
|
||||||
setReason('');
|
additionalInfo
|
||||||
setAdditionalInfo('');
|
};
|
||||||
|
|
||||||
|
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 = [
|
const stats = [
|
||||||
{
|
{
|
||||||
title: 'Total Outlets',
|
title: 'Total Outlets',
|
||||||
value: mockOutlets.length,
|
value: outlets.length,
|
||||||
icon: Building2,
|
icon: Building2,
|
||||||
color: 'bg-blue-500',
|
color: 'bg-blue-500',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Active Outlets',
|
title: 'Active Outlets',
|
||||||
value: mockOutlets.filter(o => o.status === 'Active').length,
|
value: outlets.filter(o => o.status === 'Active').length,
|
||||||
icon: CheckCircle,
|
icon: CheckCircle,
|
||||||
color: 'bg-green-500',
|
color: 'bg-green-500',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Pending Resignations',
|
title: 'Pending Resignations',
|
||||||
value: mockOutlets.filter(o => o.hasActiveResignation).length,
|
value: outlets.filter(o => o.status === 'Pending Resignation').length,
|
||||||
icon: Clock,
|
icon: Clock,
|
||||||
color: 'bg-amber-500',
|
color: 'bg-amber-500',
|
||||||
},
|
},
|
||||||
@ -183,254 +139,338 @@ export function DealerResignationPage({ currentUser, onViewDetails }: DealerResi
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Header */}
|
{/* Loading Overlay */}
|
||||||
<div>
|
{loading && (
|
||||||
<h1 className="text-slate-900 mb-2">Dealership Resignation Management</h1>
|
<div className="min-h-[400px] flex items-center justify-center">
|
||||||
<p className="text-slate-600">
|
<Loader2 className="w-8 h-8 text-amber-600 animate-spin" />
|
||||||
Manage resignation requests for your dealerships and studios
|
</div>
|
||||||
</p>
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Stats */}
|
{!loading && (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<>
|
||||||
{stats.map((stat, index) => {
|
{/* Header */}
|
||||||
const Icon = stat.icon;
|
<div>
|
||||||
return (
|
<h1 className="text-slate-900 mb-2">Dealership Resignation Management</h1>
|
||||||
<Card key={index}>
|
<p className="text-slate-600">
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
Manage resignation requests for your dealerships and studios
|
||||||
<CardTitle className="text-sm">{stat.title}</CardTitle>
|
</p>
|
||||||
<div className={`${stat.color} p-2 rounded-lg`}>
|
</div>
|
||||||
<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 */}
|
{/* Stats */}
|
||||||
<Card>
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<CardHeader>
|
{stats.map((stat, index) => {
|
||||||
<CardTitle>My Outlets</CardTitle>
|
const Icon = stat.icon;
|
||||||
<CardDescription>
|
|
||||||
Select an outlet to request resignation
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
{mockOutlets.map((outlet) => {
|
|
||||||
const OutletIcon = outlet.type === 'Dealership' ? Building2 : Store;
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Card key={index}>
|
||||||
key={outlet.id}
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
className="border border-slate-200 rounded-lg p-4 hover:shadow-md transition-shadow"
|
<CardTitle className="text-sm">{stat.title}</CardTitle>
|
||||||
>
|
<div className={`${stat.color} p-2 rounded-lg`}>
|
||||||
<div className="flex items-start justify-between mb-3">
|
<Icon className="h-4 w-4 text-white" />
|
||||||
<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>
|
</div>
|
||||||
<Badge
|
</CardHeader>
|
||||||
className={`border ${
|
<CardContent>
|
||||||
outlet.status === 'Active'
|
<div className="text-slate-900 text-2xl">{stat.value}</div>
|
||||||
? 'bg-green-100 text-green-700 border-green-300'
|
</CardContent>
|
||||||
: outlet.status === 'Pending Resignation'
|
</Card>
|
||||||
? '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.address}</p>
|
|
||||||
<p className="text-slate-500">{outlet.city}, {outlet.state}</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">
|
|
||||||
Established: {new Date(outlet.establishedDate).toLocaleDateString('en-GB', {
|
|
||||||
day: '2-digit',
|
|
||||||
month: 'short',
|
|
||||||
year: 'numeric'
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{outlet.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 && outlet.resignationId && onViewDetails(outlet.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>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Resignation Request Dialog */}
|
{/* My Outlets Section */}
|
||||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
<Card>
|
||||||
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
<CardHeader>
|
||||||
<DialogHeader>
|
<CardTitle>My Outlets</CardTitle>
|
||||||
<DialogTitle>Submit Resignation Request</DialogTitle>
|
<CardDescription>
|
||||||
<DialogDescription>
|
Select an outlet to request resignation
|
||||||
Fill in the details for your resignation request. All fields are mandatory.
|
</CardDescription>
|
||||||
</DialogDescription>
|
</CardHeader>
|
||||||
</DialogHeader>
|
<CardContent>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<form onSubmit={handleSubmitRequest} className="space-y-4">
|
{outlets.map((outlet) => {
|
||||||
{/* Outlet Info */}
|
const OutletIcon = outlet.type === 'Dealership' ? Building2 : Store;
|
||||||
<div className="bg-slate-50 border border-slate-200 rounded-lg p-4 space-y-2">
|
const hasActiveResignation = outlet.status === 'Pending Resignation';
|
||||||
<h3 className="text-slate-900">Outlet Information</h3>
|
const resignation = resignations.find(r => r.outletId === outlet.id && r.status !== 'Completed' && r.status !== 'Rejected');
|
||||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
|
||||||
<div>
|
return (
|
||||||
<span className="text-slate-600">Outlet Code:</span>
|
<div
|
||||||
<p className="text-slate-900">{selectedOutlet?.code}</p>
|
key={outlet.id}
|
||||||
</div>
|
className="border border-slate-200 rounded-lg p-4 hover:shadow-md transition-shadow"
|
||||||
<div>
|
>
|
||||||
<span className="text-slate-600">Outlet Name:</span>
|
<div className="flex items-start justify-between mb-3">
|
||||||
<p className="text-slate-900">{selectedOutlet?.name}</p>
|
<div className="flex items-start gap-3">
|
||||||
</div>
|
<div className={`${outlet.type === 'Dealership' ? 'bg-blue-100' : 'bg-purple-100'} p-2 rounded-lg`}>
|
||||||
<div>
|
<OutletIcon className={`w-5 h-5 ${outlet.type === 'Dealership' ? 'text-blue-600' : 'text-purple-600'}`} />
|
||||||
<span className="text-slate-600">Type:</span>
|
</div>
|
||||||
<p className="text-slate-900">{selectedOutlet?.type}</p>
|
<div>
|
||||||
</div>
|
<h3 className="text-slate-900">{outlet.name}</h3>
|
||||||
<div>
|
<p className="text-slate-600 text-sm">{outlet.code}</p>
|
||||||
<span className="text-slate-600">City:</span>
|
</div>
|
||||||
<p className="text-slate-900">{selectedOutlet?.city}, {selectedOutlet?.state}</p>
|
</div>
|
||||||
</div>
|
<Badge
|
||||||
<div className="col-span-2">
|
className={`border ${
|
||||||
<span className="text-slate-600">Address:</span>
|
outlet.status === 'Active'
|
||||||
<p className="text-slate-900">{selectedOutlet?.address}</p>
|
? 'bg-green-100 text-green-700 border-green-300'
|
||||||
</div>
|
: 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>
|
</div>
|
||||||
</div>
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Resignation Type */}
|
{/* Resignation Request Dialog */}
|
||||||
<div className="space-y-2">
|
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||||
<Label htmlFor="resignationType">Resignation Type *</Label>
|
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||||
<Select value={resignationType} onValueChange={setResignationType} required>
|
<DialogHeader>
|
||||||
<SelectTrigger>
|
<DialogTitle>Submit Resignation Request</DialogTitle>
|
||||||
<SelectValue placeholder="Select resignation type" />
|
<DialogDescription>
|
||||||
</SelectTrigger>
|
Fill in the details for your resignation request. All fields are mandatory.
|
||||||
<SelectContent>
|
</DialogDescription>
|
||||||
<SelectItem value="Voluntary">Voluntary</SelectItem>
|
</DialogHeader>
|
||||||
<SelectItem value="Retirement">Retirement</SelectItem>
|
|
||||||
<SelectItem value="Health Issues">Health Issues</SelectItem>
|
<form onSubmit={handleSubmitRequest} className="space-y-4">
|
||||||
<SelectItem value="Business Closure">Business Closure</SelectItem>
|
{/* Outlet Info */}
|
||||||
<SelectItem value="Other">Other</SelectItem>
|
<div className="bg-slate-50 border border-slate-200 rounded-lg p-4 space-y-2">
|
||||||
</SelectContent>
|
<h3 className="text-slate-900">Outlet Information</h3>
|
||||||
</Select>
|
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||||
</div>
|
<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>
|
||||||
|
|
||||||
{/* Last Operational Dates */}
|
{/* Resignation Type */}
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="space-y-2">
|
||||||
<div className="space-y-2">
|
<Label htmlFor="resignationType">Resignation Type *</Label>
|
||||||
<Label htmlFor="lastOpDateSales">Last Operational Date - Sales *</Label>
|
<Select value={resignationType} onValueChange={setResignationType} required>
|
||||||
<Input
|
<SelectTrigger>
|
||||||
id="lastOpDateSales"
|
<SelectValue placeholder="Select resignation type" />
|
||||||
type="date"
|
</SelectTrigger>
|
||||||
value={lastOperationalDateSales}
|
<SelectContent>
|
||||||
onChange={(e) => setLastOperationalDateSales(e.target.value)}
|
<SelectItem value="Voluntary">Voluntary</SelectItem>
|
||||||
required
|
<SelectItem value="Retirement">Retirement</SelectItem>
|
||||||
/>
|
<SelectItem value="Health Issues">Health Issues</SelectItem>
|
||||||
</div>
|
<SelectItem value="Business Closure">Business Closure</SelectItem>
|
||||||
<div className="space-y-2">
|
<SelectItem value="Other">Other</SelectItem>
|
||||||
<Label htmlFor="lastOpDateServices">Last Operational Date - Services *</Label>
|
</SelectContent>
|
||||||
<Input
|
</Select>
|
||||||
id="lastOpDateServices"
|
</div>
|
||||||
type="date"
|
|
||||||
value={lastOperationalDateServices}
|
|
||||||
onChange={(e) => setLastOperationalDateServices(e.target.value)}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Reason */}
|
{/* Last Operational Dates */}
|
||||||
<div className="space-y-2">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<Label htmlFor="reason">Reason for Resignation *</Label>
|
<div className="space-y-2">
|
||||||
<Textarea
|
<Label htmlFor="lastOpDateSales">Last Operational Date - Sales *</Label>
|
||||||
id="reason"
|
<Input
|
||||||
placeholder="Please provide detailed reason for resignation..."
|
id="lastOpDateSales"
|
||||||
value={reason}
|
type="date"
|
||||||
onChange={(e) => setReason(e.target.value)}
|
value={lastOperationalDateSales}
|
||||||
rows={4}
|
onChange={(e) => setLastOperationalDateSales(e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
{/* Additional Information */}
|
{/* Reason */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="additionalInfo">Additional Information (Optional)</Label>
|
<Label htmlFor="reason">Reason for Resignation *</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="additionalInfo"
|
id="reason"
|
||||||
placeholder="Any additional details..."
|
placeholder="Please provide detailed reason for resignation..."
|
||||||
value={additionalInfo}
|
value={reason}
|
||||||
onChange={(e) => setAdditionalInfo(e.target.value)}
|
onChange={(e) => setReason(e.target.value)}
|
||||||
rows={3}
|
rows={4}
|
||||||
/>
|
required
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Important Info */}
|
{/* Additional Information */}
|
||||||
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4">
|
<div className="space-y-2">
|
||||||
<h4 className="text-amber-900 mb-2">Important Information</h4>
|
<Label htmlFor="additionalInfo">Additional Information (Optional)</Label>
|
||||||
<ul className="text-amber-800 text-sm space-y-1">
|
<Textarea
|
||||||
<li>• F&F settlement process will be initiated after submission</li>
|
id="additionalInfo"
|
||||||
<li>• All department clearances must be obtained</li>
|
placeholder="Any additional details..."
|
||||||
<li>• Final settlement will be processed after closure</li>
|
value={additionalInfo}
|
||||||
<li>• Please ensure all documents are ready for submission</li>
|
onChange={(e) => setAdditionalInfo(e.target.value)}
|
||||||
</ul>
|
rows={3}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<DialogFooter>
|
{/* Important Info */}
|
||||||
<Button
|
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4">
|
||||||
type="button"
|
<h4 className="text-amber-900 mb-2">Important Information</h4>
|
||||||
variant="outline"
|
<ul className="text-amber-800 text-sm space-y-1">
|
||||||
onClick={() => {
|
<li>• F&F settlement process will be initiated after submission</li>
|
||||||
setIsDialogOpen(false);
|
<li>• All department clearances must be obtained</li>
|
||||||
setSelectedOutlet(null);
|
<li>• Final settlement will be processed after closure</li>
|
||||||
}}
|
<li>• Please ensure all documents are ready for submission</li>
|
||||||
>
|
</ul>
|
||||||
Cancel
|
</div>
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
className="bg-red-600 hover:bg-red-700"
|
|
||||||
>
|
|
||||||
Submit Resignation Request
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</form>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
{/* Requests Table - Removed */}
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -112,6 +112,7 @@ export interface Application {
|
|||||||
architectureAssignedTo?: string;
|
architectureAssignedTo?: string;
|
||||||
architectureStatus?: string;
|
architectureStatus?: string;
|
||||||
dealerCode?: any;
|
dealerCode?: any;
|
||||||
|
dealer?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Participant {
|
export interface Participant {
|
||||||
|
|||||||
49
src/services/dealer.service.ts
Normal file
49
src/services/dealer.service.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { API } from '../api/API';
|
||||||
|
|
||||||
|
export const dealerService = {
|
||||||
|
getDashboardData: async () => {
|
||||||
|
try {
|
||||||
|
const response: any = await API.getDealerDashboard();
|
||||||
|
return response.data?.data || response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Get dealer dashboard error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getConstitutionalChanges: async () => {
|
||||||
|
try {
|
||||||
|
const response: any = await API.getConstitutionalChanges();
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Get constitutional changes error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitConstitutionalChange: async (data: any) => {
|
||||||
|
try {
|
||||||
|
const response: any = await API.createConstitutionalChange(data);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Submit constitutional change error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getRelocationRequests: async () => {
|
||||||
|
try {
|
||||||
|
const response: any = await API.getRelocationRequests();
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Get relocation requests error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitRelocationRequest: async (data: any) => {
|
||||||
|
try {
|
||||||
|
const response: any = await API.createRelocationRequest(data);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Submit relocation request error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,6 +1,24 @@
|
|||||||
import { API } from '../api/API';
|
import { API } from '../api/API';
|
||||||
|
|
||||||
export const resignationService = {
|
export const resignationService = {
|
||||||
|
getResignations: async () => {
|
||||||
|
try {
|
||||||
|
const response: any = await API.getResignations();
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Get resignations error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createResignation: async (data: any) => {
|
||||||
|
try {
|
||||||
|
const response: any = await API.createResignation(data);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Create resignation error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
getResignationById: async (id: string) => {
|
getResignationById: async (id: string) => {
|
||||||
try {
|
try {
|
||||||
const response: any = await API.getResignationById(id);
|
const response: any = await API.getResignationById(id);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user