Dealer_Onboard_Frontend/src/components/applications/ApplicationsPage.tsx

396 lines
14 KiB
TypeScript

import { useState, useEffect } from 'react';
import { mockApplications, locations, ApplicationStatus, Application } from '../../lib/mock-data';
import { onboardingService } from '../../services/onboarding.service';
import { Button } from '../ui/button';
import { Input } from '../ui/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '../ui/select';
import {
Search,
Filter,
Download,
Mail,
Plus
} from 'lucide-react';
import { Badge } from '../ui/badge';
import { Checkbox } from '../ui/checkbox';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '../ui/table';
import { Progress } from '../ui/progress';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../ui/dialog';
import { Label } from '../ui/label';
interface ApplicationsPageProps {
onViewDetails: (id: string) => void;
initialFilter?: string;
}
export function ApplicationsPage({ onViewDetails, initialFilter }: ApplicationsPageProps) {
const [searchQuery, setSearchQuery] = useState('');
const [locationFilter, setLocationFilter] = useState<string>('all');
const [statusFilter, setStatusFilter] = useState<string>(initialFilter || 'all');
const [selectedIds, setSelectedIds] = useState<string[]>([]);
const [sortBy, setSortBy] = useState<'date'>('date');
const [showNewApplicationModal, setShowNewApplicationModal] = useState(false);
// Real Data Integration
const [applications, setApplications] = useState<Application[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchApplications = async () => {
try {
setLoading(true);
const response = await onboardingService.getApplications();
// Check if response is array or wrapped in data property
const applicationsData = response.data || (Array.isArray(response) ? response : []);
// Map backend data to frontend Application interface
const mappedApps = applicationsData.map((app: any) => ({
id: app.id,
registrationNumber: app.applicationId || 'N/A',
name: app.applicantName,
email: app.email,
phone: app.phone,
age: app.age,
education: app.education,
residentialAddress: app.address || app.city || '',
businessAddress: app.address || '',
preferredLocation: app.preferredLocation,
state: app.state,
ownsBike: app.ownRoyalEnfield === 'yes',
pastExperience: app.experienceYears ? `${app.experienceYears} years` : (app.description || ''),
status: app.overallStatus as ApplicationStatus,
questionnaireMarks: 0,
rank: 0,
totalApplicantsAtLocation: 0,
submissionDate: app.createdAt,
assignedUsers: [],
progress: app.progressPercentage || 0,
isShortlisted: true, // Show all for admin view
// Add other fields to match interface
companyName: app.companyName,
source: app.source,
existingDealer: app.existingDealer,
royalEnfieldModel: app.royalEnfieldModel,
description: app.description,
pincode: app.pincode,
locationType: app.locationType,
ownRoyalEnfield: app.ownRoyalEnfield,
address: app.address
}));
setApplications(mappedApps);
} catch (error) {
console.error('Failed to fetch applications', error);
} finally {
setLoading(false);
}
};
fetchApplications();
}, []);
// Filter and sort applications - ONLY show shortlisted applications
// Exclude specific applications (APP-005, APP-006, APP-007, APP-008) from Dealership Requests page
const excludedApplicationIds = ['5', '6', '7', '8'];
const filteredApplications = applications
.filter((app) => {
const matchesSearch =
app.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
app.registrationNumber.toLowerCase().includes(searchQuery.toLowerCase()) ||
app.email.toLowerCase().includes(searchQuery.toLowerCase());
const matchesLocation = locationFilter === 'all' || app.preferredLocation === locationFilter;
const matchesStatus = statusFilter === 'all' || app.status === statusFilter;
const isShortlisted = app.isShortlisted === true; // Only show shortlisted applications
const notExcluded = !excludedApplicationIds.includes(app.id); // Exclude APP-005, 006, 007, 008
return matchesSearch && matchesLocation && matchesStatus && isShortlisted && notExcluded;
})
.sort((a, b) => {
if (sortBy === 'date') {
return new Date(b.submissionDate).getTime() - new Date(a.submissionDate).getTime();
}
return 0;
});
const toggleSelection = (id: string) => {
setSelectedIds(prev =>
prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id]
);
};
const toggleSelectAll = () => {
if (selectedIds.length === filteredApplications.length) {
setSelectedIds([]);
} else {
setSelectedIds(filteredApplications.map(app => app.id));
}
};
const handleBulkReminders = () => {
alert(`Sending reminders to ${selectedIds.length} applicants`);
setSelectedIds([]);
};
const handleExport = () => {
alert('Exporting applications to CSV...');
};
const getStatusColor = (status: string) => {
const statusColors: Record<string, string> = {
'Submitted': 'bg-slate-500',
'Questionnaire Pending': 'bg-orange-500',
'Questionnaire Completed': 'bg-blue-500',
'Shortlisted': 'bg-cyan-500',
'Level 1 Pending': 'bg-amber-500',
'Level 1 Approved': 'bg-green-500',
'Level 2 Pending': 'bg-purple-500',
'Level 2 Approved': 'bg-green-600',
'Level 2 Recommended': 'bg-teal-500',
'Level 3 Pending': 'bg-indigo-500',
'FDD Verification': 'bg-violet-500',
'Payment Pending': 'bg-yellow-500',
'LOI Issued': 'bg-lime-500',
'Dealer Code Generation': 'bg-fuchsia-500',
'Architecture Team Assigned': 'bg-blue-500',
'Architecture Document Upload': 'bg-blue-500',
'Architecture Team Completion': 'bg-blue-500',
'Statutory GST': 'bg-emerald-500',
'Statutory PAN': 'bg-emerald-500',
'Statutory Nodal': 'bg-emerald-500',
'Statutory Check': 'bg-emerald-500',
'Statutory Partnership': 'bg-emerald-500',
'Statutory Firm Reg': 'bg-emerald-500',
'Statutory Virtual Code': 'bg-emerald-500',
'Statutory Domain': 'bg-emerald-500',
'Statutory MSD': 'bg-emerald-500',
'Statutory LOI Ack': 'bg-emerald-500',
'EOR In Progress': 'bg-sky-500',
'LOA Pending': 'bg-emerald-500',
'Approved': 'bg-green-700',
'Rejected': 'bg-red-500',
'Disqualified': 'bg-red-700'
};
return statusColors[status] || 'bg-slate-500';
};
return (
<div className="space-y-6">
{/* Info Banner - Only visible for DD users */}
{/* Note: This page shows only applications that have been shortlisted */}
{/* Filters and Actions Bar */}
<div className="bg-white rounded-lg border border-slate-200 p-4">
<div className="flex flex-col lg:flex-row gap-4">
{/* Search */}
<div className="flex-1 relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<Input
type="text"
placeholder="Search by name, ID, or email..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10"
/>
</div>
{/* Location Filter */}
<Select value={locationFilter} onValueChange={setLocationFilter}>
<SelectTrigger className="w-full lg:w-48">
<SelectValue placeholder="All Locations" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Locations</SelectItem>
{locations.map((location) => (
<SelectItem key={location} value={location}>
{location}
</SelectItem>
))}
</SelectContent>
</Select>
{/* Status Filter */}
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-full lg:w-48">
<SelectValue placeholder="All Statuses" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Statuses</SelectItem>
<SelectItem value="Questionnaire Pending">Questionnaire Pending</SelectItem>
<SelectItem value="Shortlisted">Shortlisted</SelectItem>
<SelectItem value="Level 1 Pending">Level 1 Pending</SelectItem>
<SelectItem value="Level 2 Pending">Level 2 Pending</SelectItem>
<SelectItem value="Level 3 Pending">Level 3 Pending</SelectItem>
<SelectItem value="EOR In Progress">EOR In Progress</SelectItem>
<SelectItem value="Approved">Approved</SelectItem>
<SelectItem value="Rejected">Rejected</SelectItem>
</SelectContent>
</Select>
{/* Sort By */}
<Select value={sortBy} onValueChange={(v) => setSortBy(v as any)}>
<SelectTrigger className="w-full lg:w-40">
<SelectValue placeholder="Sort By" />
</SelectTrigger>
<SelectContent>
<SelectItem value="date">Date</SelectItem>
</SelectContent>
</Select>
</div>
{/* Action Buttons */}
<div className="flex flex-wrap items-center gap-3 mt-4">
<Button
variant="outline"
size="sm"
onClick={handleExport}
>
<Download className="w-4 h-4 mr-2" />
Export
</Button>
{selectedIds.length > 0 && (
<Button
variant="outline"
size="sm"
onClick={handleBulkReminders}
>
<Mail className="w-4 h-4 mr-2" />
Send Reminders ({selectedIds.length})
</Button>
)}
<div className="ml-auto text-slate-600">
{filteredApplications.length} application{filteredApplications.length !== 1 ? 's' : ''}
</div>
</div>
</div>
{/* Applications Table */}
<div className="bg-white rounded-lg border border-slate-200">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-12">
<Checkbox
checked={selectedIds.length === filteredApplications.length}
onCheckedChange={toggleSelectAll}
/>
</TableHead>
<TableHead>ID</TableHead>
<TableHead>Name</TableHead>
<TableHead>Preferred Location</TableHead>
<TableHead>Status</TableHead>
<TableHead>Applicant Location</TableHead>
<TableHead>Progress</TableHead>
<TableHead>Applied On</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredApplications.map((app) => (
<TableRow key={app.id}>
<TableCell>
<Checkbox
checked={selectedIds.includes(app.id)}
onCheckedChange={() => toggleSelection(app.id)}
/>
</TableCell>
<TableCell>{app.registrationNumber}</TableCell>
<TableCell>{app.name}</TableCell>
<TableCell>{app.preferredLocation}</TableCell>
<TableCell>
<Badge className={getStatusColor(app.status)}>
{app.status}
</Badge>
</TableCell>
<TableCell className="text-slate-600 max-w-xs truncate">
{app.residentialAddress}
</TableCell>
<TableCell>
<div className="flex items-center gap-2">
<Progress value={app.progress} className="h-2 w-20" />
<span className="text-slate-600">{app.progress}%</span>
</div>
</TableCell>
<TableCell>
{new Date(app.submissionDate).toLocaleDateString()}
</TableCell>
<TableCell>
<Button
size="sm"
variant="outline"
onClick={() => onViewDetails(app.id)}
>
View
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
{/* New Application Modal */}
<Dialog open={showNewApplicationModal} onOpenChange={setShowNewApplicationModal}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Add New Application (Admin)</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<p className="text-slate-600">This form allows administrators to manually add applications to the system.</p>
<div className="grid grid-cols-2 gap-4">
<div>
<Label>Name</Label>
<Input placeholder="Full Name" />
</div>
<div>
<Label>Email</Label>
<Input type="email" placeholder="email@example.com" />
</div>
<div>
<Label>Phone</Label>
<Input placeholder="+91 XXXXX XXXXX" />
</div>
<div>
<Label>Preferred Location</Label>
<Select>
<SelectTrigger>
<SelectValue placeholder="Select location" />
</SelectTrigger>
<SelectContent>
{locations.map(loc => (
<SelectItem key={loc} value={loc}>{loc}</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="flex justify-end gap-3">
<Button variant="outline" onClick={() => setShowNewApplicationModal(false)}>
Cancel
</Button>
<Button className="bg-amber-600 hover:bg-amber-700">
Create Application
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</div>
);
}