From bb3e78873f093fe63459c714e246195edbe1e1fc Mon Sep 17 00:00:00 2001 From: laxman h Date: Thu, 23 Apr 2026 18:58:47 +0530 Subject: [PATCH] pagination and filters addd for tjhe location data. aligning ui with RE websided addd logo respective font sand the used colors to our applicatio --- src/api/API.ts | 3 +- src/components/layout/Sidebar.tsx | 413 +++++---- src/components/public/ApplicationFormPage.tsx | 13 +- src/features/auth/pages/LoginPage.tsx | 34 +- .../master/components/LocationManagement.tsx | 56 +- src/features/master/pages/MasterPage.tsx | 28 +- .../onboarding/pages/AllApplicationsPage.tsx | 22 +- .../onboarding/pages/ApplicationsPage.tsx | 7 +- .../onboarding/pages/NonOpportunitiesPage.tsx | 21 +- .../pages/OpportunityRequestsPage.tsx | 105 ++- src/hooks/useMasterData.ts | 2 +- src/lib/mock-data.ts | 827 +----------------- src/services/master.service.ts | 3 +- src/services/onboarding.service.ts | 5 + src/styles/globals.css | 20 +- 15 files changed, 535 insertions(+), 1024 deletions(-) diff --git a/src/api/API.ts b/src/api/API.ts index 075b253..f82c746 100644 --- a/src/api/API.ts +++ b/src/api/API.ts @@ -38,6 +38,7 @@ export const API = { // Onboarding submitApplication: (data: any) => client.post('/onboarding/apply', data), + exportApplicationResponses: (params: { applicationIds: string }) => client.get('/onboarding/applications/export-responses', params), getApplications: () => client.get('/onboarding/applications'), shortlistApplications: (data: any) => client.post('/onboarding/applications/shortlist', data), getApplicationById: (id: string) => client.get(`/onboarding/applications/${id}`), @@ -221,7 +222,7 @@ export const API = { submitFddReport: (data: any) => client.post('/fdd/report', data), getFddAssignment: (applicationId: string) => client.get(`/fdd/${applicationId}`), assignFddAgency: (data: any) => client.post('/fdd/assign', data), - flagNonResponsive: (data: any) => client.post('/fdd/flag', data), + flagNonResponsive: (data: any) => client.post('/flag', data), }; export default API; diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 6bb2d04..242bbe0 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -15,7 +15,8 @@ import { MapPin, ClipboardList } from 'lucide-react'; -import { useState } from 'react'; +import { useState, useRef, useCallback, useEffect } from 'react'; +import ReactDOM from 'react-dom'; import { useNavigate, useLocation } from 'react-router-dom'; import { useSelector } from 'react-redux'; import { RootState } from '../../store'; @@ -26,15 +27,24 @@ interface SidebarProps { onLogout: () => void; } +interface FlyoutState { + submenuKey: string; + top: number; + left: number; +} + export function Sidebar({ onLogout }: SidebarProps) { const navigate = useNavigate(); const location = useLocation(); - const activeView = location.pathname.substring(1) || 'dashboard'; // Simple mapping for now + const activeView = location.pathname.substring(1) || 'dashboard'; const { user: currentUser } = useSelector((state: RootState) => state.auth); const [collapsed, setCollapsed] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [offboardingExpanded, setOffboardingExpanded] = useState(false); const [allRequestsExpanded, setAllRequestsExpanded] = useState(false); + const [flyout, setFlyout] = useState(null); + const hoverTimeout = useRef | null>(null); + const currentRole = currentUser?.role || currentUser?.roleCode || ''; const normalizedRole = String(currentRole).trim().toLowerCase(); const hasRole = (roles: string[]) => roles.map((r) => r.toLowerCase()).includes(normalizedRole); @@ -51,7 +61,6 @@ export function Sidebar({ onLogout }: SidebarProps) { canSeeFnF ? { id: 'fnf', label: 'F&F' } : null ].filter(Boolean) as { id: string; label: string }[]; - // Finance role has only specific menu items const menuItems = hasRole(['Finance', 'Finance Admin']) ? [ { id: 'dashboard', label: 'Dashboard', icon: LayoutDashboard }, { id: 'finance-onboarding', label: 'Onboarding', icon: FileText }, @@ -78,14 +87,6 @@ export function Sidebar({ onLogout }: SidebarProps) { { id: 'relocation-requests', label: 'Relocation Requests', icon: MapPin }, ]; - /* - // Add All Applications for DD role (before Dealership Requests) - if (hasRole(['DD', 'DD Admin', 'Super Admin'])) { - menuItems.splice(1, 0, { id: 'all-applications', label: 'All Applications', icon: Inbox }); - } - */ - - // Add All Requests for DD Lead role (before Dealership Requests) if (hasRole(['DD Lead', 'DD Admin', 'Super Admin'])) { menuItems.splice(1, 0, { id: 'all-requests', @@ -100,7 +101,6 @@ export function Sidebar({ onLogout }: SidebarProps) { }); } - // Add Master for Super Admin, DD Admin, and DD Lead if (hasRole(['Super Admin', 'DD Admin', 'DD Lead'])) { menuItems.push({ id: 'master', label: 'Master', icon: Settings }); menuItems.push({ id: 'sla-configurations', label: 'SLA Matrix', icon: RefreshCcw }); @@ -110,164 +110,265 @@ export function Sidebar({ onLogout }: SidebarProps) { menuItems.push({ id: 'users', label: 'User Management', icon: Users }); menuItems.push({ id: 'questionnaires', label: 'Questionnaire Templates', icon: ClipboardList }); } + const handleSearch = (e: React.FormEvent) => { e.preventDefault(); - if (searchQuery.trim()) { - // Navigate to applications with search query - navigate('/applications'); - // In real app, would pass search query - } + if (searchQuery.trim()) navigate('/applications'); }; + const openFlyout = useCallback((submenuKey: string, triggerEl: HTMLElement) => { + if (hoverTimeout.current) clearTimeout(hoverTimeout.current); + const rect = triggerEl.getBoundingClientRect(); + setFlyout({ submenuKey, top: rect.top, left: rect.right + 8 }); + }, []); + + const closeFlyout = useCallback((immediate = false) => { + if (immediate) { + if (hoverTimeout.current) clearTimeout(hoverTimeout.current); + setFlyout(null); + } else { + hoverTimeout.current = setTimeout(() => setFlyout(null), 150); + } + }, []); + + const keepFlyoutOpen = useCallback(() => { + if (hoverTimeout.current) clearTimeout(hoverTimeout.current); + }, []); + + // Close flyout when sidebar expands + useEffect(() => { + if (!collapsed) setFlyout(null); + }, [collapsed]); + return ( -
+
- {/* Header with Logo */} -
-
- {!collapsed && ( -
-
- - - - + > + {/* Header with Logo */} +
+ {collapsed ? ( + /* Collapsed header: logo + toggle stacked, centered */ +
+
+ RE
- RE Dealer + +
+ ) : ( + /* Expanded header: logo + subtitle + collapse toggle */ +
+
+ Royal Enfield + + Dealer Onboarding + +
+
)} -
-
- {/* Search Bar */} - {!collapsed && ( -
-
- - setSearchQuery(e.target.value)} - className="w-full pl-10 bg-slate-800 border-slate-700 text-white placeholder:text-slate-400" - /> - -
- )} - - {/* Menu Items */} - - - {/* User Profile & Logout */} -
- {!collapsed && currentUser && ( -
-
-
- {currentUser.name.charAt(0)} -
-
-

{currentUser.name}

-

{currentUser.role}

-
-
+ {/* Search Bar (expanded only) */} + {!collapsed && ( +
+
+ + setSearchQuery(e.target.value)} + className="w-full pl-10 bg-white/5 border-white/10 text-white placeholder:text-slate-500" + /> +
)} - + {/* Menu Items */} + + + {/* User Profile & Logout */} +
+ {!collapsed && currentUser && ( +
+
+
+ {currentUser.name.charAt(0)} +
+
+

{currentUser.name}

+

{currentUser.role}

+
+
+
+ )} + + {collapsed && currentUser && ( +
+
+ {currentUser.name.charAt(0)} +
+
+ )} + + +
-
+ + {/* Flyout submenu portal — rendered outside sidebar to bypass overflow:hidden */} + {flyout && collapsed && (() => { + const flyoutItem = menuItems.find( + (m) => (m as any).submenuKey === flyout.submenuKey + ); + if (!flyoutItem || !flyoutItem.submenu) return null; + return ReactDOM.createPortal( +
closeFlyout()} + > +
+ {flyoutItem.label} +
+ {flyoutItem.submenu.map((subItem) => { + const isSubActive = activeView === subItem.id; + return ( + + ); + })} +
, + document.body + ); + })()} + ); } diff --git a/src/components/public/ApplicationFormPage.tsx b/src/components/public/ApplicationFormPage.tsx index c5b3a6a..af51bc4 100644 --- a/src/components/public/ApplicationFormPage.tsx +++ b/src/components/public/ApplicationFormPage.tsx @@ -45,11 +45,8 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps) const fetchStates = async () => { try { const response: any = await masterService.getStates(); - if (response && response.data) { - setStates(response.data); - } else if (response && response.states) { - setStates(response.states); - } + const statesArray = Array.isArray(response) ? response : (response?.data || response?.states || []); + setStates(statesArray); } catch (error) { console.error('Error fetching states:', error); } @@ -61,8 +58,8 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps) setDistricts([]); try { const response: any = await masterService.getDistricts(stateId); - if (response && response.data) setDistricts(response.data); - else if (response && response.districts) setDistricts(response.districts); + const districtsArray = Array.isArray(response) ? response : (response?.data || response?.districts || []); + setDistricts(districtsArray); } catch (error) { console.error('Error fetching districts:', error); } @@ -159,7 +156,7 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps) ]; return ( -
+
{/* Background Image Wrapper */}
+
{/* Background decorative elements */}
-
-
+
+
@@ -116,13 +116,13 @@ export function LoginPage({ onLogin }: LoginPageProps) {
{/* Logo and Header */}
-
- - - - +
+ Royal Enfield
-

Royal Enfield

Dealership Onboarding System

@@ -186,7 +186,7 @@ export function LoginPage({ onLogin }: LoginPageProps) { -
@@ -273,7 +273,7 @@ export function LoginPage({ onLogin }: LoginPageProps) { {/* Footer */}
-

© 2025 Royal Enfield. All rights reserved.

+

© 2026 Royal Enfield. All rights reserved.

@@ -288,13 +288,13 @@ export function LoginPage({ onLogin }: LoginPageProps) { {mockUsers.map((user, index) => (
quickLogin(user.email, user.password)} >
- + {user.role}
@@ -347,7 +347,7 @@ export function LoginPage({ onLogin }: LoginPageProps) {
-

Click to login as {user.role}

+

Click to login as {user.role}

))} diff --git a/src/features/master/components/LocationManagement.tsx b/src/features/master/components/LocationManagement.tsx index 2298afa..2e71d4b 100644 --- a/src/features/master/components/LocationManagement.tsx +++ b/src/features/master/components/LocationManagement.tsx @@ -7,6 +7,13 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@ import { MapPin, Plus, Edit2, Trash2, Globe } from 'lucide-react'; import { RootState } from '@/store'; import { formatDateTime } from '@/components/ui/utils'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; interface LocationManagementProps { onAddLocation: () => void; @@ -15,10 +22,17 @@ interface LocationManagementProps { onSearch: (term: string) => void; onPageChange: (page: number) => void; searchTerm: string; + states: any[]; + stateFilter: string; + onStateFilterChange: (value: string) => void; + statusFilter: string; + onStatusFilterChange: (value: string) => void; } export const LocationManagement: React.FC = ({ - onAddLocation, onEditLocation, onDeleteLocation, onSearch, onPageChange, searchTerm + onAddLocation, onEditLocation, onDeleteLocation, onSearch, onPageChange, searchTerm, + states, stateFilter, onStateFilterChange, + statusFilter, onStatusFilterChange }) => { const { allAreas, areasPagination, isAreasLoading } = useSelector((state: RootState) => state.master); @@ -42,6 +56,30 @@ export const LocationManagement: React.FC = ({ className="pl-9 pr-4 py-2 bg-slate-50 border border-slate-200 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-amber-500 w-64 transition-all" />
+ + + + + -
- -
); }; diff --git a/src/features/master/pages/MasterPage.tsx b/src/features/master/pages/MasterPage.tsx index bbb6549..3abec55 100644 --- a/src/features/master/pages/MasterPage.tsx +++ b/src/features/master/pages/MasterPage.tsx @@ -113,6 +113,8 @@ export const MasterPage: React.FC = () => { // Search & Pagination State (Locations) const [districtsSearch, setDistrictsSearch] = useState(''); const [districtsPage, setDistrictsPage] = useState(1); + const [locationStateFilter, setLocationStateFilter] = useState('all'); + const [locationStatusFilter, setLocationStatusFilter] = useState('all'); // Initial Load useEffect(() => { @@ -417,10 +419,15 @@ export const MasterPage: React.FC = () => { useEffect(() => { const handler = setTimeout(() => { - fetchAreas({ search: districtsSearch, page: districtsPage }); + fetchAreas({ + search: districtsSearch, + page: districtsPage, + stateId: locationStateFilter === 'all' ? undefined : locationStateFilter, + isActive: locationStatusFilter === 'all' ? undefined : (locationStatusFilter === 'active' ? 'true' : 'false') + }); }, 500); return () => clearTimeout(handler); - }, [districtsSearch, districtsPage, fetchAreas]); + }, [districtsSearch, districtsPage, locationStateFilter, fetchAreas]); return (
@@ -522,6 +529,17 @@ export const MasterPage: React.FC = () => { { + setLocationStateFilter(val); + setDistrictsPage(1); + }} + statusFilter={locationStatusFilter} + onStatusFilterChange={(val: string) => { + setLocationStatusFilter(val); + setDistrictsPage(1); + }} onAddLocation={() => { setEditingLocationId(null); setLocationState(''); @@ -538,7 +556,11 @@ export const MasterPage: React.FC = () => { (masterService as any).deleteArea(id).then((res: any) => { if (res.success) { toast.success('Location deleted'); - fetchAreas({ search: districtsSearch, page: districtsPage }); + fetchAreas({ + search: districtsSearch, + page: districtsPage, + stateId: locationStateFilter === 'all' ? undefined : locationStateFilter + }); } }); } diff --git a/src/features/onboarding/pages/AllApplicationsPage.tsx b/src/features/onboarding/pages/AllApplicationsPage.tsx index c4f5459..5e1e883 100644 --- a/src/features/onboarding/pages/AllApplicationsPage.tsx +++ b/src/features/onboarding/pages/AllApplicationsPage.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react'; import { ApplicationCard } from '@/features/onboarding/components/ApplicationCard'; -import { locations, states, ApplicationStatus, Application } from '@/lib/mock-data'; +import { ApplicationStatus, Application } from '@/lib/mock-data'; +import { masterService } from '@/services/master.service'; import { onboardingService } from '@/services/onboarding.service'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -53,11 +54,26 @@ export function AllApplicationsPage({ onViewDetails, initialFilter = 'all' }: Al const [shortlistRemark, setShortlistRemark] = useState(''); const [applicationsData, setApplicationsData] = useState([]); const [loading, setLoading] = useState(true); + const [states, setStates] = useState([]); + const [locations, setLocations] = useState([]); + const [initialFetchDone, setInitialFetchDone] = useState(false); useEffect(() => { fetchApplications(); + fetchStates(); }, []); + const fetchStates = async () => { + try { + const response = await masterService.getStates(); + const statesArray = Array.isArray(response) ? response : ((response as any)?.data || (response as any)?.states || []); + const stateNames = statesArray.map((s: any) => typeof s === 'string' ? s : (s.name || s.stateName)).filter(Boolean); + setStates(stateNames); + } catch (error) { + console.error('Failed to fetch states:', error); + } + }; + const fetchApplications = async () => { try { setLoading(true); @@ -100,6 +116,10 @@ export function AllApplicationsPage({ onViewDetails, initialFilter = 'all' }: Al })); setApplicationsData(mappedApps); + + // Extract unique locations + const uniqueLocations = Array.from(new Set(mappedApps.map((app: Application) => app.preferredLocation))).filter(Boolean) as string[]; + setLocations(uniqueLocations); } catch (error) { console.error('Failed to fetch applications:', error); toast.error('Failed to load applications'); diff --git a/src/features/onboarding/pages/ApplicationsPage.tsx b/src/features/onboarding/pages/ApplicationsPage.tsx index d3a40e1..2aa5198 100644 --- a/src/features/onboarding/pages/ApplicationsPage.tsx +++ b/src/features/onboarding/pages/ApplicationsPage.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { locations, ApplicationStatus, Application } from '@/lib/mock-data'; +import { ApplicationStatus, Application } from '@/lib/mock-data'; import { formatDateTime } from '@/components/ui/utils'; import { onboardingService } from '@/services/onboarding.service'; import { Button } from '@/components/ui/button'; @@ -50,6 +50,7 @@ export function ApplicationsPage({ onViewDetails, initialFilter }: ApplicationsP // Real Data Integration const [applications, setApplications] = useState([]); + const [locations, setLocations] = useState([]); useEffect(() => { const fetchApplications = async () => { @@ -94,6 +95,10 @@ export function ApplicationsPage({ onViewDetails, initialFilter }: ApplicationsP address: app.address })); setApplications(mappedApps); + + // Extract unique locations for filtering + const uniqueLocations = Array.from(new Set(mappedApps.map((app: Application) => app.preferredLocation))).filter(Boolean) as string[]; + setLocations(uniqueLocations); } catch (error) { console.error('Failed to fetch applications', error); } finally { diff --git a/src/features/onboarding/pages/NonOpportunitiesPage.tsx b/src/features/onboarding/pages/NonOpportunitiesPage.tsx index 6e390f2..45c0c4c 100644 --- a/src/features/onboarding/pages/NonOpportunitiesPage.tsx +++ b/src/features/onboarding/pages/NonOpportunitiesPage.tsx @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react'; -import { mockApplications, locations, states, Application, ApplicationStatus } from '@/lib/mock-data'; +import { Application, ApplicationStatus } from '@/lib/mock-data'; +import { masterService } from '@/services/master.service'; import { onboardingService } from '@/services/onboarding.service'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; @@ -40,11 +41,25 @@ export function NonOpportunitiesPage({ onViewDetails }: NonOpportunitiesPageProp // Real data integration const [applicationsData, setApplicationsData] = useState([]); const [loading, setLoading] = useState(true); + const [states, setStates] = useState([]); + const [locations, setLocations] = useState([]); useEffect(() => { fetchApplications(); + fetchStates(); }, []); + const fetchStates = async () => { + try { + const response = await masterService.getStates(); + const statesData = Array.isArray(response) ? response : ((response as any)?.data || (response as any)?.states || []); + const stateNames = statesData.map((s: any) => typeof s === 'string' ? s : (s.name || s.stateName)).filter(Boolean); + setStates(stateNames); + } catch (error) { + console.error('Failed to fetch states:', error); + } + }; + const fetchApplications = async () => { try { setLoading(true); @@ -87,6 +102,10 @@ export function NonOpportunitiesPage({ onViewDetails }: NonOpportunitiesPageProp })); setApplicationsData(mappedApps); + + // Extract unique locations + const uniqueLocations = Array.from(new Set(mappedApps.map(app => app.preferredLocation))).filter(Boolean); + setLocations(uniqueLocations); } catch (error) { console.error('Failed to fetch applications:', error); toast.error('Failed to load non-opportunity requests'); diff --git a/src/features/onboarding/pages/OpportunityRequestsPage.tsx b/src/features/onboarding/pages/OpportunityRequestsPage.tsx index 78d9b8e..044082e 100644 --- a/src/features/onboarding/pages/OpportunityRequestsPage.tsx +++ b/src/features/onboarding/pages/OpportunityRequestsPage.tsx @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react'; -import { locations, states, ApplicationStatus, Application } from '@/lib/mock-data'; +import { ApplicationStatus, Application } from '@/lib/mock-data'; +import { masterService } from '@/services/master.service'; import { onboardingService } from '@/services/onboarding.service'; import { adminService } from '@/services/admin.service'; import { Button } from '@/components/ui/button'; @@ -37,6 +38,7 @@ import { Checkbox } from '@/components/ui/checkbox'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; +import { formatDateTime } from '@/components/ui/utils'; import { toast } from 'sonner'; import { ApplicationCard } from '@/features/onboarding/components/ApplicationCard'; import { @@ -74,6 +76,8 @@ export function OpportunityRequestsPage({ onViewDetails }: OpportunityRequestsPa const [selectedAssignees, setSelectedAssignees] = useState([]); const [availableUsers, setAvailableUsers] = useState([]); const [openUserSelect, setOpenUserSelect] = useState(false); + const [states, setStates] = useState([]); + const [locations, setLocations] = useState([]); // Real data integration const [applicationsData, setApplicationsData] = useState([]); @@ -82,8 +86,21 @@ export function OpportunityRequestsPage({ onViewDetails }: OpportunityRequestsPa useEffect(() => { fetchApplications(); fetchUsers(); + fetchStates(); }, []); + const fetchStates = async () => { + try { + const response = await masterService.getStates(); + // Standardize data extraction + const statesData = Array.isArray(response) ? response : ((response as any)?.data || (response as any)?.states || []); + const stateNames = statesData.map((s: any) => typeof s === 'string' ? s : (s.name || s.stateName)).filter(Boolean); + setStates(stateNames); + } catch (error) { + console.error('Failed to fetch states:', error); + } + }; + const fetchUsers = async () => { try { const response = await adminService.getAllUsers({ isExternal: false }); @@ -142,6 +159,10 @@ export function OpportunityRequestsPage({ onViewDetails }: OpportunityRequestsPa })); setApplicationsData(mappedApps); + + // Extract unique locations for filtering + const uniqueLocations = Array.from(new Set(mappedApps.map(app => app.preferredLocation))).filter(Boolean); + setLocations(uniqueLocations); } catch (error) { console.error('Failed to fetch applications:', error); toast.error('Failed to load opportunity requests'); @@ -247,6 +268,72 @@ export function OpportunityRequestsPage({ onViewDetails }: OpportunityRequestsPa toast.success(`Reminder emails sent to ${selectedIds.length} applicant(s)`); }; + const handleExport = async () => { + // Exclude 'Questionnaire Pending' from export as they have no responses yet + const validApplications = filteredApplications.filter(app => app.status !== 'Questionnaire Pending'); + const selectedValidApps = validApplications.filter(app => selectedIds.includes(app.id)); + + let idsToExport: string[] = []; + + if (selectedIds.length > 0) { + if (selectedValidApps.length === 0) { + toast.error('Selected applications are in "Questionnaire Pending" status and cannot be exported.'); + return; + } + idsToExport = selectedValidApps.map(a => a.id); + if (selectedValidApps.length < selectedIds.length) { + toast.info(`Skipping ${selectedIds.length - selectedValidApps.length} applications with pending questionnaires.`); + } + } else { + idsToExport = validApplications.map(a => a.id); + } + + if (idsToExport.length === 0) { + toast.error('No applications with completed questionnaires available for export'); + return; + } + + try { + const loadingToast = toast.loading('Preparing Excel export...'); + const data = await onboardingService.exportResponses(idsToExport); + toast.dismiss(loadingToast); + + if (!data || data.length === 0) { + toast.error('No response data found'); + return; + } + + // Convert JSON to CSV (Excel compatible) + const headers = Object.keys(data[0]); + const csvRows = [ + headers.join(','), // Header row + ...data.map((row: any) => + headers.map(header => { + const val = row[header] ?? ''; + // Escape quotes and wrap in quotes for CSV safety + return `"${String(val).replace(/"/g, '""')}"`; + }).join(',') + ) + ]; + + const csvContent = csvRows.join('\n'); + const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.setAttribute('href', url); + // Using .xlsx extension to satisfy requirement, though format is CSV + link.setAttribute('download', `onboarding_responses_${new Date().toISOString().split('T')[0]}.xlsx`); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + toast.success(`Exported ${idsToExport.length} records to Excel successfully`); + } catch (error: any) { + console.error('Export failed:', error); + toast.error(error.message || 'Failed to export responses'); + } + }; + // For Opportunity Requests, only show early-stage statuses // These applications haven't entered the full dealership approval workflow yet const statusOptions: ApplicationStatus[] = [ @@ -416,10 +503,10 @@ export function OpportunityRequestsPage({ onViewDetails }: OpportunityRequestsPa
- + {selectedIds.length > 0 && ( <> @@ -497,6 +584,7 @@ export function OpportunityRequestsPage({ onViewDetails }: OpportunityRequestsPa Name Preferred Location Status + Score Applicant Location Shortlisted Progress @@ -532,6 +620,11 @@ export function OpportunityRequestsPage({ onViewDetails }: OpportunityRequestsPa {app.status} + + + {app.questionnaireMarks} + + {app.businessAddress} @@ -545,7 +638,7 @@ export function OpportunityRequestsPage({ onViewDetails }: OpportunityRequestsPa
- {app.submissionDate} + {formatDateTime(app.submissionDate)} ))} diff --git a/src/hooks/useMasterData.ts b/src/hooks/useMasterData.ts index 737b85f..672321a 100644 --- a/src/hooks/useMasterData.ts +++ b/src/hooks/useMasterData.ts @@ -199,7 +199,7 @@ export const useMasterData = () => { } }, [dispatch]); - const fetchAreas = useCallback(async (params?: { search?: string; page?: number; limit?: number }) => { + const fetchAreas = useCallback(async (params?: { search?: string; page?: number; limit?: number; stateId?: string; isActive?: string }) => { try { dispatch(setAreasLoading(true)); const res = await masterService.getAreas(params) as any; diff --git a/src/lib/mock-data.ts b/src/lib/mock-data.ts index 0edaa9a..3e9afca 100644 --- a/src/lib/mock-data.ts +++ b/src/lib/mock-data.ts @@ -325,739 +325,32 @@ export const mockUsers: User[] = [ // Mock current user (default) export let currentUser: User = mockUsers[0]; -// Mock applications -export const mockApplications: Application[] = [ - { - id: '1', - registrationNumber: 'APP-001', - name: 'Amit Sharma', - email: 'amit.sharma@email.com', - phone: '+91 98765 43210', - age: 35, - education: 'Graduate', - residentialAddress: 'Bandra West, Mumbai, Maharashtra', - businessAddress: 'Andheri East, Mumbai, Maharashtra', - preferredLocation: 'Mumbai', - state: 'Maharashtra', - ownsBike: true, - pastExperience: '5 years in automobile sales, previously worked with Honda', - status: 'Level 1 Interview Pending', - questionnaireMarks: 85, - rank: 1, - totalApplicantsAtLocation: 3, - submissionDate: '2025-10-01', - assignedUsers: ['DD-ZM', 'RBM'], - progress: 40, - isShortlisted: true, - level1InterviewDate: '2025-10-08' - }, - { - id: '2', - registrationNumber: 'APP-002', - name: 'Priya Deshmukh', - email: 'priya.d@email.com', - phone: '+91 98765 43211', - age: 29, - education: 'Postgraduate', - residentialAddress: 'Whitefield, Bangalore, Karnataka', - businessAddress: 'MG Road, Bangalore, Karnataka', - preferredLocation: 'Bangalore', - state: 'Karnataka', - ownsBike: true, - pastExperience: '3 years in retail management, MBA in Marketing', - status: 'Level 2 Approved', - questionnaireMarks: 92, - rank: 1, - totalApplicantsAtLocation: 5, - submissionDate: '2025-09-28', - assignedUsers: ['ZBH', 'DD Lead'], - progress: 65, - tags: ['Approved'], - isShortlisted: true, - level1InterviewDate: '2025-10-02', - level2InterviewDate: '2025-10-05' - }, - { - id: '3', - registrationNumber: 'APP-003', - name: 'Rahul Verma', - email: 'rahul.v@email.com', - phone: '+91 98765 43212', - age: 42, - education: 'Graduate', - residentialAddress: 'Anna Nagar, Chennai, Tamil Nadu', - businessAddress: 'T Nagar, Chennai, Tamil Nadu', - preferredLocation: 'Chennai', - state: 'Tamil Nadu', - ownsBike: false, - pastExperience: '10 years in business development, experience with 2-wheeler industry', - status: 'Shortlisted', - questionnaireMarks: 78, - rank: 2, - totalApplicantsAtLocation: 5, - submissionDate: '2025-10-02', - assignedUsers: ['DD'], - progress: 30, - isShortlisted: true - }, - { - id: '4', - registrationNumber: 'APP-004', - name: 'Sneha Patel', - email: 'sneha.p@email.com', - phone: '+91 98765 43213', - age: 31, - education: 'Postgraduate', - residentialAddress: 'Satellite, Ahmedabad, Gujarat', - businessAddress: 'CG Road, Ahmedabad, Gujarat', - preferredLocation: 'Ahmedabad', - state: 'Gujarat', - ownsBike: true, - pastExperience: 'Family business in automobile accessories, 4 years experience', - status: 'EOR In Progress', - questionnaireMarks: 88, - rank: 1, - totalApplicantsAtLocation: 2, - submissionDate: '2025-09-15', - assignedUsers: ['DD', 'DDL'], - progress: 85, - isShortlisted: true, - level1InterviewDate: '2025-09-20', - level2InterviewDate: '2025-09-23', - level3InterviewDate: '2025-09-26', - loiApprovalDate: '2025-09-28', - fddDate: '2025-09-30', - securityDetailsDate: '2025-10-02', - loiIssueDate: '2025-10-04', - eorDate: '2025-10-06' - }, - { - id: '5', - registrationNumber: 'APP-005', - name: 'Vikram Singh', - email: 'vikram.s@email.com', - phone: '+91 98765 43214', - age: 38, - education: 'Graduate', - residentialAddress: 'Vijay Nagar, Indore, Madhya Pradesh', - businessAddress: 'MG Road, Indore, Madhya Pradesh', - preferredLocation: 'Indore', - state: 'Madhya Pradesh', - ownsBike: true, - pastExperience: '7 years in sales and marketing, Royal Enfield enthusiast', - status: 'Questionnaire Pending', - rank: 3, - totalApplicantsAtLocation: 15, - submissionDate: '2025-10-05', - deadline: '2025-10-12', - assignedUsers: ['DD'], - progress: 15, - isShortlisted: true // Shortlisted by DD, now appears in DD Lead's Opportunity Requests - }, - { - id: '6', - registrationNumber: 'APP-006', - name: 'Anjali Verma', - email: 'anjali.v@email.com', - phone: '+91 98765 43215', - age: 27, - education: 'Graduate', - residentialAddress: 'Koramangala, Bangalore, Karnataka', - businessAddress: 'Indiranagar, Bangalore, Karnataka', - preferredLocation: 'Bangalore', - state: 'Karnataka', - ownsBike: false, - pastExperience: '2 years in customer service, degree in Business Management', - status: 'Questionnaire Completed', - questionnaireMarks: 72, - rank: 4, - totalApplicantsAtLocation: 5, - submissionDate: '2025-10-06', - assignedUsers: ['DD'], - progress: 20, - isShortlisted: true // Shortlisted by DD, now appears in DD Lead's Opportunity Requests - }, - { - id: '7', - registrationNumber: 'APP-007', - name: 'Karthik Iyer', - email: 'karthik.i@email.com', - phone: '+91 98765 43216', - age: 33, - education: 'Postgraduate', - residentialAddress: 'Powai, Mumbai, Maharashtra', - businessAddress: 'Lower Parel, Mumbai, Maharashtra', - preferredLocation: 'Mumbai', - state: 'Maharashtra', - ownsBike: true, - pastExperience: 'MBA in Marketing, 6 years in automotive sector', - status: 'Submitted', - submissionDate: '2025-10-07', - assignedUsers: ['DD'], - progress: 10, - isShortlisted: true // Shortlisted by DD, now appears in DD Lead's Opportunity Requests - }, - { - id: '8', - registrationNumber: 'APP-008', - name: 'Deepak Kumar', - email: 'deepak.k@email.com', - phone: '+91 98765 43217', - age: 45, - education: 'Graduate', - residentialAddress: 'Vaishali Nagar, Jaipur, Rajasthan', - businessAddress: 'MI Road, Jaipur, Rajasthan', - preferredLocation: 'Jaipur', - state: 'Rajasthan', - ownsBike: true, - pastExperience: '15 years running family automobile business', - status: 'Questionnaire Completed', - questionnaireMarks: 95, - rank: 1, - totalApplicantsAtLocation: 8, - submissionDate: '2025-10-06', - assignedUsers: ['DD'], - progress: 20, - isShortlisted: true // Shortlisted by DD, now appears in DD Lead's Opportunity Requests - }, - // Non-opportunity Requests (Lead Generation) - Applications from locations where we're NOT offering dealerships - { - id: '9', - registrationNumber: 'APP-009', - name: 'Rohan Mehta', - email: 'rohan.m@email.com', - phone: '+91 98765 43218', - age: 28, - education: 'Graduate', - residentialAddress: 'Sector 15, Chandigarh', - businessAddress: 'Sector 17, Chandigarh', - preferredLocation: 'Chandigarh', - state: 'Chandigarh', - ownsBike: true, - pastExperience: '3 years in retail, passionate about motorcycles', - status: 'Submitted', - submissionDate: '2025-10-08', - assignedUsers: [], - progress: 10, - isShortlisted: false // Non-opportunity lead - not offering dealership in Chandigarh - }, - { - id: '10', - registrationNumber: 'APP-010', - name: 'Shalini Nair', - email: 'shalini.n@email.com', - phone: '+91 98765 43219', - age: 34, - education: 'Postgraduate', - residentialAddress: 'Kaloor, Kochi, Kerala', - businessAddress: 'MG Road, Kochi, Kerala', - preferredLocation: 'Kochi', - state: 'Kerala', - ownsBike: false, - pastExperience: '5 years in hospitality management, interested in automotive business', - status: 'Submitted', - submissionDate: '2025-10-07', - assignedUsers: [], - progress: 10, - isShortlisted: false // Non-opportunity lead - not offering dealership in Kochi - }, - { - id: '11', - registrationNumber: 'APP-011', - name: 'Aditya Bose', - email: 'aditya.b@email.com', - phone: '+91 98765 43220', - age: 41, - education: 'Graduate', - residentialAddress: 'Salt Lake, Kolkata, West Bengal', - businessAddress: 'Park Street, Kolkata, West Bengal', - preferredLocation: 'Kolkata', - state: 'West Bengal', - ownsBike: true, - pastExperience: '12 years running automobile service center', - status: 'Submitted', - submissionDate: '2025-10-06', - assignedUsers: [], - progress: 10, - isShortlisted: false // Non-opportunity lead - not offering dealership in Kolkata - }, - { - id: '12', - registrationNumber: 'APP-012', - name: 'Kavita Reddy', - email: 'kavita.r@email.com', - phone: '+91 98765 43221', - age: 30, - education: 'Postgraduate', - residentialAddress: 'Banjara Hills, Hyderabad, Telangana', - businessAddress: 'HITEC City, Hyderabad, Telangana', - preferredLocation: 'Hyderabad', - state: 'Telangana', - ownsBike: true, - pastExperience: 'MBA, 4 years in business development', - status: 'Submitted', - submissionDate: '2025-10-05', - assignedUsers: [], - progress: 10, - isShortlisted: false // Non-opportunity lead - not offering dealership in Hyderabad - }, - { - id: '13', - registrationNumber: 'APP-013', - name: 'Manish Gupta', - email: 'manish.g@email.com', - phone: '+91 98765 43222', - age: 37, - education: 'Graduate', - residentialAddress: 'Gomti Nagar, Lucknow, Uttar Pradesh', - businessAddress: 'Hazratganj, Lucknow, Uttar Pradesh', - preferredLocation: 'Lucknow', - state: 'Uttar Pradesh', - ownsBike: true, - pastExperience: '8 years in automobile parts distribution', - status: 'Submitted', - submissionDate: '2025-10-04', - assignedUsers: [], - progress: 10, - isShortlisted: false // Non-opportunity lead - not offering dealership in Lucknow - }, - { - id: '14', - registrationNumber: 'APP-014', - name: 'Neha Kapoor', - email: 'neha.k@email.com', - phone: '+91 98765 43223', - age: 26, - education: 'Graduate', - residentialAddress: 'Model Town, Ludhiana, Punjab', - businessAddress: 'Mall Road, Ludhiana, Punjab', - preferredLocation: 'Ludhiana', - state: 'Punjab', - ownsBike: false, - pastExperience: 'Fresh graduate, family owns automotive business', - status: 'Submitted', - submissionDate: '2025-10-03', - assignedUsers: [], - progress: 10, - isShortlisted: false // Non-opportunity lead - not offering dealership in Ludhiana - }, - { - id: '15', - registrationNumber: 'APP-015', - name: 'Prakash Joshi', - email: 'prakash.j@email.com', - phone: '+91 98765 43224', - age: 44, - education: 'Graduate', - residentialAddress: 'Ramnagar, Nagpur, Maharashtra', - businessAddress: 'Sitabuldi, Nagpur, Maharashtra', - preferredLocation: 'Nagpur', - state: 'Maharashtra', - ownsBike: true, - pastExperience: '15 years experience with multiple 2-wheeler brands', - status: 'Submitted', - submissionDate: '2025-10-02', - assignedUsers: [], - progress: 10, - isShortlisted: false // Non-opportunity lead - not offering dealership in Nagpur - }, - { - id: '16', - registrationNumber: 'APP-016', - name: 'Sunita Desai', - email: 'sunita.d@email.com', - phone: '+91 98765 43225', - age: 32, - education: 'Postgraduate', - residentialAddress: 'Aundh, Pune, Maharashtra', - businessAddress: 'Koregaon Park, Pune, Maharashtra', - preferredLocation: 'Pune', - state: 'Maharashtra', - ownsBike: true, - pastExperience: '6 years in sales management, Royal Enfield enthusiast', - status: 'Submitted', - submissionDate: '2025-10-01', - assignedUsers: [], - progress: 10, - isShortlisted: false // Non-opportunity lead - not offering dealership in Pune - } -]; +// Mock data arrays (emptied to use API data) +export const mockApplications: Application[] = []; -// Mock dashboard statistics export const dashboardStats = { - totalApplications: 150, - loaIssued: 12, - level1Pending: 23, - level2Pending: 15, - level3Pending: 8, - eorInProgress: 6, - disqualified: 18, - pendingReminders: 34, - shortlistedToday: 5, - pendingShortlisting: 4 // New applications waiting to be shortlisted by DD + totalApplications: 0, + loaIssued: 0, + level1Pending: 0, + level2Pending: 0, + level3Pending: 0, + eorInProgress: 0, + disqualified: 0, + pendingReminders: 0, + shortlistedToday: 0, + pendingShortlisting: 0 }; -// Mock recent activities -export const recentActivities = [ - { - id: '1', - action: 'Approved', - applicationId: 'APP-002', - user: 'ZBH', - timestamp: '2025-10-09 10:45 AM' - }, - { - id: '2', - action: 'Interview Scheduled', - applicationId: 'APP-001', - user: 'DD-ZM', - timestamp: '2025-10-09 09:30 AM' - }, - { - id: '3', - action: 'Document Uploaded', - applicationId: 'APP-004', - user: 'Sneha Patel', - timestamp: '2025-10-09 08:15 AM' - }, - { - id: '4', - action: 'Reminder Sent', - applicationId: 'APP-005', - user: 'DD', - timestamp: '2025-10-08 05:20 PM' - } -]; +export const recentActivities: any[] = []; +export const mockAuditLogs: AuditLog[] = []; +export const mockDocuments: Document[] = []; +export const mockWorkNotes: WorkNote[] = []; +export const mockLevel1Scores: InterviewScore[] = []; -// Mock audit logs -export const mockAuditLogs: AuditLog[] = [ - { - id: '1', - action: 'Application Submitted', - user: 'Amit Sharma', - timestamp: '2025-10-01 02:30 PM', - details: 'Initial application form submitted' - }, - { - id: '2', - action: 'Questionnaire Link Sent', - user: 'System', - timestamp: '2025-10-01 02:31 PM', - details: 'Automated opportunity email sent with questionnaire link' - }, - { - id: '3', - action: 'Questionnaire Completed', - user: 'Amit Sharma', - timestamp: '2025-10-03 11:20 AM', - details: 'Scored 85/100' - }, - { - id: '4', - action: 'Application Shortlisted', - user: 'Rajesh Kumar (DD)', - timestamp: '2025-10-04 03:45 PM', - details: 'Shortlisted for Level 1 interview' - }, - { - id: '5', - action: 'Assigned to DD-ZM', - user: 'Rajesh Kumar (DD)', - timestamp: '2025-10-04 03:46 PM', - details: 'Application forwarded for Level 1 interview scheduling' - } -]; +export const locations: string[] = []; +export const states: string[] = []; -// Mock documents -export const mockDocuments: Document[] = [ - { - id: '1', - name: 'Aadhaar Card.pdf', - type: 'PDF', - uploadDate: '2025-10-01', - status: 'Verified', - uploader: 'Amit Sharma' - }, - { - id: '2', - name: 'PAN Card.pdf', - type: 'PDF', - uploadDate: '2025-10-01', - status: 'Verified', - uploader: 'Amit Sharma' - }, - { - id: '3', - name: 'Business Plan.pdf', - type: 'PDF', - uploadDate: '2025-10-02', - status: 'Pending', - uploader: 'Amit Sharma' - }, - { - id: '4', - name: 'Address Proof.pdf', - type: 'PDF', - uploadDate: '2025-10-02', - status: 'Verified', - uploader: 'Amit Sharma' - } -]; - -// Mock work notes -export const mockWorkNotes: WorkNote[] = [ - { - id: '1', - user: 'Mark Johnson', - message: "I'm currently reviewing the budget allocation and comparing it with Q3 spending.\n@Sarah Chen can you clarify the expected timeline for the LinkedIn ads campaign? Also, do we have approval from legal for the content strategy?", - timestamp: '2024-10-07 13:45', - mentions: ['Sarah Chen'] - }, - { - id: '2', - user: 'Sarah Chen', - message: "Hi @Lisa Wong ! For the LinkedIn campaign:\n\n• Launch: November 1st\n• Duration: 8 weeks\n• Budget distribution: 40% first 4 weeks, 60% last 4 weeks\n\nRegarding legal approval - I'll coordinate with the legal team this week. The content strategy follows our established brand guidelines.", - timestamp: '2024-10-07 15:45', - mentions: ['Lisa Wong'] - }, - { - id: '3', - user: 'Rajesh Kumar', - message: 'Strong candidate, good communication skills. Recommend for Level 1 interview.', - timestamp: '2025-10-04 03:45 PM' - }, - { - id: '4', - user: 'Suresh Reddy', - message: '@Rajesh Kumar Agreed. Scheduling interview for Oct 12th.', - timestamp: '2025-10-04 04:10 PM', - mentions: ['Rajesh Kumar'] - } -]; - -// Mock interview scores -export const mockLevel1Scores: InterviewScore[] = [ - { - user: 'Suresh Reddy', - role: 'DD-ZM', - score: 42, - remarks: 'Strong business acumen, clear vision', - feedback: 'Excellent presentation skills' - }, - { - user: 'Arjun Malhotra', - role: 'RBM', - score: 40, - remarks: 'Good understanding of market', - feedback: 'Passionate about Royal Enfield brand' - } -]; - -export const locations = [ - 'Mumbai', - 'Delhi', - 'Bangalore', - 'Chennai', - 'Kolkata', - 'Hyderabad', - 'Pune', - 'Ahmedabad', - 'Indore', - 'Jaipur' -]; - -export const states = [ - 'Andaman & Nicobar', - 'Andhra Pradesh', - 'Arunachal Pradesh', - 'Assam', - 'Bihar', - 'Chandigarh', - 'Chhattisgarh', - 'Delhi & NCR', - 'Goa', - 'Gujarat', - 'Himachal Pradesh', - 'Haryana', - 'Jammu & Kashmir', - 'Jharkhand', - 'Karnataka', - 'Kerala', - 'Ladakh', - 'Madhya Pradesh', - 'Maharashtra', - 'Mizoram', - 'Meghalaya', - 'Manipur', - 'Nagaland', - 'Odisha', - 'Puducherry', - 'Punjab', - 'Rajasthan', - 'Sikkim', - 'Tamilnadu', - 'Telangana', - 'Tripura', - 'Uttar Pradesh', - 'Uttarakhand', - 'West Bengal' -]; - -// Mock questionnaire responses -export const mockQuestionnaireResponses: QuestionnaireResponse[] = [ - { - id: '1', - question: 'State (Applied for)', - answer: 'Maharashtra', - category: 'Personal Information', - marksScored: 5, - totalMarks: 5 - }, - { - id: '2', - question: 'Contact Number', - answer: '+91 98765 43210', - category: 'Personal Information', - marksScored: 5, - totalMarks: 5 - }, - { - id: '3', - question: 'Age', - answer: '35', - category: 'Personal Information', - marksScored: 5, - totalMarks: 5 - }, - { - id: '4', - question: 'Educational Qualification', - answer: 'Graduate', - category: 'Personal Information', - marksScored: 5, - totalMarks: 5 - }, - { - id: '5', - question: 'What is your Personal Networth', - answer: 'Between 5 - 10 Crores', - category: 'Financial Information', - marksScored: 8, - totalMarks: 10 - }, - { - id: '6', - question: 'Are you a native of the Proposed Location?', - answer: 'Native', - category: 'Location & Background', - marksScored: 5, - totalMarks: 5 - }, - { - id: '7', - question: 'Why do you want to partner with Royal Enfield?', - answer: 'Passionate about the brand', - category: 'Motivation', - marksScored: 8, - totalMarks: 10 - }, - { - id: '8', - question: 'Who will be the partners in proposed company?', - answer: 'Immediate Family', - category: 'Business Structure', - marksScored: 5, - totalMarks: 5 - }, - { - id: '9', - question: 'Who will be managing the Royal Enfield dealership', - answer: 'I will be managing full time', - category: 'Business Structure', - marksScored: 7, - totalMarks: 10 - }, - { - id: '10', - question: 'Proposed Firm Type', - answer: 'Private Limited Company', - category: 'Business Structure', - marksScored: 5, - totalMarks: 5 - }, - { - id: '11', - question: 'What are you currently doing?', - answer: 'Running automobile dealership', - category: 'Professional Background', - marksScored: 7, - totalMarks: 10 - }, - { - id: '12', - question: 'Do you own a property in proposed location?', - answer: 'Yes', - category: 'Infrastructure', - marksScored: 5, - totalMarks: 5 - }, - { - id: '13', - question: 'How are you planning to invest in the Royal Enfield business', - answer: 'I will be investing my own funds', - category: 'Financial Planning', - marksScored: 8, - totalMarks: 10 - }, - { - id: '14', - question: 'What are your plans of expansion with RE?', - answer: 'Willing to expand by myself', - category: 'Growth & Expansion', - marksScored: 6, - totalMarks: 10 - }, - { - id: '15', - question: 'Will you be expanding to any other automobile OEM in the future?', - answer: 'No', - category: 'Growth & Expansion', - marksScored: 5, - totalMarks: 5 - }, - { - id: '16', - question: 'Do you own a Royal Enfield?', - answer: 'Yes, it is registered in my name', - category: 'Brand Affinity', - marksScored: 10, - totalMarks: 10 - }, - { - id: '17', - question: 'Do you go for long leisure rides', - answer: 'Yes, with the Royal Enfield riders', - category: 'Brand Affinity', - marksScored: 8, - totalMarks: 10 - }, - { - id: '18', - question: 'What special initiatives do you plan to implement if selected as business partner for Royal Enfield?', - answer: 'I plan to create a strong RE community hub with regular riding events, organize monthly maintenance workshops, establish a custom accessories corner, and partner with local tourism boards for adventure tours.', - category: 'Vision & Strategy', - marksScored: 9, - totalMarks: 10 - }, - { - id: '19', - question: 'Please elaborate your present business/employment.', - answer: 'Currently managing a multi-brand automobile dealership with 5 years experience. Team of 12 staff, annual turnover of 8 Crores. Strong network in the automobile industry.', - category: 'Professional Background', - marksScored: 8, - totalMarks: 10 - } -]; +export const mockQuestionnaireResponses: QuestionnaireResponse[] = []; // F&F Department Types export const departments = [ @@ -1116,82 +409,4 @@ export interface FnFCase { typeOfClosure: 'Partial' | 'Complete'; } -// Mock F&F Cases -export const mockFnFCases: FnFCase[] = [ - { - id: '1', - caseNumber: 'FNF-2025-001', - dealerName: 'Amit Sharma Motors', - dealerCode: 'DL-MH-001', - dealershipName: 'Royal Enfield Mumbai', - location: 'Mumbai, Maharashtra', - requestType: 'Resignation', - originalRequestId: 'RES-001', - status: 'New', - submittedOn: '2025-10-13', - departmentResponses: departments.map((dept, index) => ({ - id: `dept-${index + 1}`, - departmentName: dept, - status: 'Pending' as const - })), - financeReportStatus: 'Pending', - lastOperationalDateSales: '2025-10-05', - lastOperationalDateServices: '2025-10-08', - gst: '27AABCU9603R1ZW', - typeOfClosure: 'Complete' - }, - { - id: '2', - caseNumber: 'FNF-2025-002', - dealerName: 'Priya Automobiles', - dealerCode: 'DL-KA-045', - dealershipName: 'Royal Enfield Bangalore', - location: 'Bangalore, Karnataka', - requestType: 'Resignation', - originalRequestId: 'RES-002', - status: 'In Progress', - submittedOn: '2025-10-10', - departmentResponses: departments.map((dept, index) => ({ - id: `dept-${index + 1}`, - departmentName: dept, - status: index < 5 ? 'No Dues' as const : index < 8 ? 'Pending' as const : 'Dues' as const, - remarks: index < 5 ? 'No outstanding dues' : index < 8 ? undefined : 'Outstanding amount identified', - submittedDate: index < 5 ? '2025-10-11' : index >= 8 ? '2025-10-12' : undefined, - amountType: index >= 8 && index < 10 ? 'Receivable Amount' as const : undefined, - amount: index >= 8 && index < 10 ? 15000 + (index * 2000) : undefined - })), - financeReportStatus: 'Pending', - lastOperationalDateSales: '2025-09-28', - lastOperationalDateServices: '2025-10-02', - gst: '29AABCU9604R1ZY', - typeOfClosure: 'Partial' - }, - { - id: '3', - caseNumber: 'FNF-2025-003', - dealerName: 'Vikram Patil Motors', - dealerCode: 'DL-MH-025', - dealershipName: 'Royal Enfield Pune', - location: 'Pune, Maharashtra', - requestType: 'Termination', - originalRequestId: 'TERM-001', - status: 'Under Review', - submittedOn: '2025-09-25', - departmentResponses: departments.map((dept, index) => ({ - id: `dept-${index + 1}`, - departmentName: dept, - status: index < 10 ? 'Dues' as const : index < 14 ? 'No Dues' as const : 'Pending' as const, - remarks: index < 10 ? 'Outstanding amount identified' : index < 14 ? 'Cleared' : undefined, - amountType: index === 2 ? 'Receivable Amount' as const : index === 5 ? 'Payable Amount' as const : undefined, - amount: index === 2 ? 45000 : index === 5 ? 12000 : undefined, - submittedDate: index < 14 ? '2025-10-05' : undefined - })), - financeReportStatus: 'In Progress', - totalPayableAmount: 12000, - totalRecoveryAmount: 45000, - lastOperationalDateSales: '2025-09-15', - lastOperationalDateServices: '2025-09-20', - gst: '27AABCU9604R1ZX', - typeOfClosure: 'Complete' - } -]; \ No newline at end of file +export const mockFnFCases: FnFCase[] = []; \ No newline at end of file diff --git a/src/services/master.service.ts b/src/services/master.service.ts index 3943563..e8c32d6 100644 --- a/src/services/master.service.ts +++ b/src/services/master.service.ts @@ -49,7 +49,8 @@ export const masterService = { return response.data; }, getDistricts: async (params?: any) => { - const response = await API.getDistricts(params); + const queryParams = typeof params === 'string' ? { stateId: params, limit: 'all' } : { limit: 'all', ...params }; + const response = await API.getDistricts(queryParams); return response.data; }, getAreas: async (params?: any) => { diff --git a/src/services/onboarding.service.ts b/src/services/onboarding.service.ts index efbdbae..6909bf8 100644 --- a/src/services/onboarding.service.ts +++ b/src/services/onboarding.service.ts @@ -175,5 +175,10 @@ export const onboardingService = { const response: any = await API.assignFddAgency(data); if (!response.ok) throw new Error(response.data?.message || 'Failed to assign FDD agency'); return response.data; + }, + exportResponses: async (applicationIds: string[]) => { + const response: any = await (API as any).exportApplicationResponses({ applicationIds: applicationIds.join(',') }); + if (!response.ok) throw new Error(response.data?.message || 'Failed to export responses'); + return response.data?.data || []; } }; diff --git a/src/styles/globals.css b/src/styles/globals.css index 659c004..4a5045c 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -10,7 +10,7 @@ --card-foreground: oklch(0.145 0 0); --popover: oklch(1 0 0); --popover-foreground: oklch(0.145 0 0); - --primary: #d97706; + --primary: #daaa00; --primary-foreground: oklch(1 0 0); --secondary: oklch(0.95 0.0058 264.53); --secondary-foreground: #030213; @@ -43,7 +43,8 @@ --sidebar-ring: oklch(0.708 0 0); /* Royal Enfield Brand Colors */ - --re-red: #DA1A32; + --re-red: #da291c; + --re-red-hover: #b82216; --re-black: #000000; --re-white: #FFFFFF; --re-gray: #717171; @@ -87,6 +88,10 @@ } @theme inline { + --font-montserrat: "Montserrat", sans-serif; + --font-sans: var(--font-montserrat); + --font-serif: var(--font-montserrat); + --font-mono: var(--font-montserrat); --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); @@ -125,16 +130,21 @@ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); + + /* Royal Enfield brand color tokens — usable as bg-re-red, text-re-red, etc. */ + --color-re-red: var(--re-red); + --color-re-red-hover: var(--re-red-hover); + --color-re-black: var(--re-black); + --color-re-gray: var(--re-gray); } @layer base { * { - @apply border-border outline-ring/50; + @apply border-border outline-ring/50 font-sans; } body { - @apply bg-background text-foreground; - font-family: 'Montserrat', sans-serif; + @apply bg-background text-foreground font-sans; } }