diff --git a/src/App.tsx b/src/App.tsx index 7ba130e..ebc3865 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { RootState } from './store'; import { setCredentials, logout as logoutAction, initializeAuth } from './store/slices/authSlice'; +import { Routes, Route, Navigate, useLocation, useNavigate } from 'react-router-dom'; import { ApplicationFormPage } from './components/public/ApplicationFormPage'; import { LoginPage } from './components/auth/LoginPage'; import { Sidebar } from './components/layout/Sidebar'; @@ -34,32 +35,34 @@ import { WorknotePage } from './components/applications/WorknotePage'; import { DealerResignationPage } from './components/dealer/DealerResignationPage'; import { DealerConstitutionalChangePage } from './components/dealer/DealerConstitutionalChangePage'; import { DealerRelocationPage } from './components/dealer/DealerRelocationPage'; +import QuestionnaireBuilder from './components/admin/QuestionnaireBuilder'; import { Toaster } from './components/ui/sonner'; import { User } from './lib/mock-data'; import { toast } from 'sonner'; import { API } from './api/API'; -type View = 'dashboard' | 'applications' | 'all-applications' | 'opportunity-requests' | 'unopportunity-requests' | 'tasks' | 'reports' | 'settings' | 'users' | 'resignation' | 'termination' | 'fnf' | 'finance-onboarding' | 'finance-fnf' | 'master' | 'constitutional-change' | 'relocation-requests' | 'worknote' | 'dealer-resignation' | 'dealer-constitutional' | 'dealer-relocation'; +// Layout Component +const AppLayout = ({ children, onLogout, title }: { children: React.ReactNode, onLogout: () => void, title: string }) => { + return ( +
+ +
+
window.location.reload()} /> +
+ {children} +
+
+ +
+ ); +}; export default function App() { const dispatch = useDispatch(); const { user: currentUser, isAuthenticated, loading } = useSelector((state: RootState) => state.auth); const [showAdminLogin, setShowAdminLogin] = useState(false); - const [currentView, setCurrentView] = useState('dashboard'); - const [selectedApplicationId, setSelectedApplicationId] = useState(null); - const [selectedResignationId, setSelectedResignationId] = useState(null); - const [selectedTerminationId, setSelectedTerminationId] = useState(null); - const [selectedFnFId, setSelectedFnFId] = useState(null); - const [selectedPaymentId, setSelectedPaymentId] = useState(null); - const [selectedFinanceFnFId, setSelectedFinanceFnFId] = useState(null); - const [selectedConstitutionalChangeId, setSelectedConstitutionalChangeId] = useState(null); - const [selectedRelocationRequestId, setSelectedRelocationRequestId] = useState(null); - const [applicationFilter, setApplicationFilter] = useState('all'); - const [worknoteContext, setWorknoteContext] = useState<{ - requestId: string; - requestType: 'relocation' | 'constitutional-change' | 'fnf' | 'resignation' | 'termination'; - requestTitle: string; - } | null>(null); + const navigate = useNavigate(); + const location = useLocation(); useEffect(() => { dispatch(initializeAuth()); @@ -68,27 +71,19 @@ export default function App() { const handleLogin = async (email: string, password: string) => { try { const response = await API.login({ email, password }); - if (response.ok && response.data) { const { token, user } = response.data as any; - - // Store token for persistence localStorage.setItem('token', token); - - // Use backend user data const simplifiedUser: User = { id: user.id, name: user.fullName || email.split('@')[0], email: user.email, - password: password, + password: password, // Note: storing password in state is not ideal, but keeping existing structure role: typeof user.role === 'string' ? user.role : (user.roleCode || 'User') }; - - dispatch(setCredentials({ - user: simplifiedUser, - token - })); + dispatch(setCredentials({ user: simplifiedUser, token })); toast.success(`Welcome back, ${simplifiedUser.name}!`); + setShowAdminLogin(false); } else { const errorMsg = (response.data as any)?.message || 'Invalid credentials'; toast.error(errorMsg); @@ -101,181 +96,42 @@ export default function App() { const handleLogout = () => { dispatch(logoutAction()); - setCurrentView('dashboard'); - setSelectedApplicationId(null); setShowAdminLogin(false); toast.info('Logged out successfully'); + navigate('/'); }; - const handleShowAdminLogin = () => { - setShowAdminLogin(true); + // Helper to determine page title based on path + const getPageTitle = (pathname: string) => { + if (pathname.startsWith('/applications/') && pathname.length > 14) return 'Application Details'; + if (pathname.includes('/resignation/') && pathname.length > 13) return 'Resignation Details'; + // ... Add more dynamic title logic as needed + const titles: Record = { + '/dashboard': 'Dashboard', + '/applications': 'Dealership Requests', + '/all-applications': 'All Applications', + '/opportunity-requests': 'Opportunity Requests', + '/unopportunity-requests': 'Unopportunity Requests', + '/tasks': 'My Tasks', + '/reports': 'Reports & Analytics', + '/settings': 'Settings', + '/users': 'User Management', + '/resignation': 'Resignation Management', + '/termination': 'Termination Management', + '/fnf': 'Full & Final Settlement', + '/finance-onboarding': 'Payment Verification', + '/finance-fnf': 'F&F Financial Settlement', + '/master': 'Master Configuration', + '/constitutional-change': 'Constitutional Change', + '/relocation-requests': 'Relocation Requests', + '/dealer-resignation': 'Dealer Resignation Management', + '/dealer-constitutional': 'Dealer Constitutional Change', + '/dealer-relocation': 'Dealer Relocation Requests', + '/questionnaire-builder': 'Questionnaire Builder', + }; + return titles[pathname] || 'Dashboard'; }; - const handleNavigate = (view: string, filter?: string) => { - setCurrentView(view as View); - setSelectedApplicationId(null); - setSelectedResignationId(null); - setSelectedTerminationId(null); - setSelectedFnFId(null); - setSelectedPaymentId(null); - setSelectedFinanceFnFId(null); - if (filter) { - setApplicationFilter(filter); - } - }; - - const handleViewDetails = (id: string) => { - setSelectedApplicationId(id); - }; - - const handleViewResignationDetails = (id: string) => { - setSelectedResignationId(id); - }; - - const handleViewTerminationDetails = (id: string) => { - setSelectedTerminationId(id); - }; - - const handleViewFnFDetails = (id: string) => { - setSelectedFnFId(id); - }; - - const handleViewPaymentDetails = (id: string) => { - setSelectedPaymentId(id); - }; - - const handleViewFinanceFnFDetails = (id: string) => { - setSelectedFinanceFnFId(id); - }; - - const handleViewConstitutionalChangeDetails = (id: string) => { - setSelectedConstitutionalChangeId(id); - }; - - const handleViewRelocationRequestDetails = (id: string) => { - setSelectedRelocationRequestId(id); - }; - - const handleBackFromDetails = () => { - setSelectedApplicationId(null); - }; - - const handleBackFromPaymentDetails = () => { - setSelectedPaymentId(null); - }; - - const handleBackFromFinanceFnFDetails = () => { - setSelectedFinanceFnFId(null); - }; - - const handleBackFromResignation = () => { - setSelectedResignationId(null); - }; - - const handleBackFromTermination = () => { - setSelectedTerminationId(null); - }; - - const handleBackFromFnF = () => { - setSelectedFnFId(null); - }; - - const handleBackFromConstitutionalChange = () => { - setSelectedConstitutionalChangeId(null); - }; - - const handleBackFromRelocationRequest = () => { - setSelectedRelocationRequestId(null); - }; - - const handleOpenWorknote = (requestId: string, requestType: 'relocation' | 'constitutional-change' | 'fnf' | 'resignation' | 'termination', requestTitle: string) => { - setWorknoteContext({ requestId, requestType, requestTitle }); - setCurrentView('worknote'); - }; - - const handleBackFromWorknote = () => { - setWorknoteContext(null); - // Return to the previous view based on request type - if (worknoteContext) { - if (worknoteContext.requestType === 'relocation') { - setSelectedRelocationRequestId(worknoteContext.requestId); - setCurrentView('relocation-requests'); - } else if (worknoteContext.requestType === 'constitutional-change') { - setSelectedConstitutionalChangeId(worknoteContext.requestId); - setCurrentView('constitutional-change'); - } - // Add other request types as needed - } - }; - - const getPageTitle = () => { - if (selectedApplicationId) { - return 'Application Details'; - } - if (selectedResignationId) { - return 'Resignation Details'; - } - if (selectedTerminationId) { - return 'Termination Details'; - } - if (selectedFnFId) { - return 'F&F Case Details'; - } - if (selectedConstitutionalChangeId) { - return 'Constitutional Change Details'; - } - if (selectedRelocationRequestId) { - return 'Relocation Request Details'; - } - switch (currentView) { - case 'dashboard': - return 'Dashboard'; - case 'all-applications': - return 'All Applications'; - case 'opportunity-requests': - return 'Opportunity Requests'; - case 'unopportunity-requests': - return 'Unopportunity Requests'; - case 'applications': - return 'Dealership Requests'; - case 'tasks': - return 'My Tasks'; - case 'reports': - return 'Reports & Analytics'; - case 'settings': - return 'Settings'; - case 'users': - return 'User Management'; - case 'resignation': - return 'Resignation Management'; - case 'termination': - return 'Termination Management'; - case 'fnf': - return 'Full & Final Settlement'; - case 'finance-onboarding': - return 'Payment Verification'; - case 'finance-fnf': - return 'F&F Financial Settlement'; - case 'master': - return 'Master Configuration'; - case 'constitutional-change': - return 'Constitutional Change'; - case 'relocation-requests': - return 'Relocation Requests'; - case 'worknote': - return 'Worknote Management'; - case 'dealer-resignation': - return 'Dealer Resignation Management'; - case 'dealer-constitutional': - return 'Dealer Constitutional Change'; - case 'dealer-relocation': - return 'Dealer Relocation Requests'; - default: - return 'Dashboard'; - } - }; - - // Show loading state while initializing auth if (loading) { return (
@@ -284,333 +140,117 @@ export default function App() { ); } - // Show public application form if not authenticated and not trying to log in as admin - if (!isAuthenticated && !showAdminLogin) { + // Public Routes + if (!isAuthenticated) { return ( - <> - - - - ); - } - - // Show admin login page if user clicked admin login but hasn't authenticated yet - if (!isAuthenticated && showAdminLogin) { - return ( - <> - - - - ); - } - - return ( -
- - -
-
window.location.reload()} + + } /> + : + <> setShowAdminLogin(true)} />} /> + + ) + } -
- {currentView === 'worknote' && worknoteContext ? ( - - ) : selectedPaymentId ? ( - - ) : selectedFinanceFnFId ? ( - - ) : selectedApplicationId ? ( - - ) : selectedResignationId ? ( - - ) : selectedTerminationId ? ( - - ) : selectedFnFId ? ( - - ) : selectedConstitutionalChangeId ? ( - - ) : selectedRelocationRequestId ? ( - - ) : ( - <> - {currentView === 'dashboard' && ( - currentUser?.role === 'Finance Admin' || currentUser?.role === 'Finance' ? ( - - ) : currentUser?.role === 'Dealer' ? ( - - ) : ( - - ) - )} + // Protected Routes + return ( + + + } /> - {currentView === 'all-applications' && ( - currentUser?.role === 'DD' ? ( - - ) : ( -
-

Access Denied

-

This page is only accessible to DD users.

-
- ) - )} + {/* Dashboards */} + navigate(`/${path}`)} onViewPaymentDetails={(id) => navigate(`/finance-onboarding/${id}`)} onViewFnFDetails={(id) => navigate(`/finance-fnf/${id}`)} /> : + currentUser?.role === 'Dealer' ? + navigate(`/${path}`)} /> : + navigate(`/${path}`)} /> + } /> - {currentView === 'opportunity-requests' && ( - currentUser?.role === 'DD Lead' ? ( - - ) : ( -
-

Access Denied

-

This page is only accessible to DD Lead users.

-
- ) - )} + {/* Applications */} + navigate(`/applications/${id}`)} initialFilter="all" />} /> + navigate('/applications')} />} /> - {currentView === 'unopportunity-requests' && ( - currentUser?.role === 'DD Lead' ? ( - - ) : ( -
-

Access Denied

-

This page is only accessible to DD Lead users.

-
- ) - )} + navigate(`/applications/${id}`)} initialFilter="all" /> : + } /> - {currentView === 'applications' && ( - - )} + {/* Admin/Lead Routes */} + navigate(`/applications/${id}`)} />} /> + navigate(`/applications/${id}`)} />} /> - {currentView === 'tasks' && ( -
-

My Tasks

-

Task management interface would be displayed here

-

Shows applications assigned to the current user

-
- )} + {/* Other Modules */} + } /> + } /> + } /> + } /> - {currentView === 'reports' && ( -
-

Reports & Analytics

-

Advanced reporting and analytics dashboard

-

Charts, export capabilities, and custom filters

-
- )} + {/* HR/Finance Modules (Simplified for brevity, following pattern) */} + navigate(`/resignation/${id}`)} />} /> + navigate('/resignation')} currentUser={currentUser} />} /> - {currentView === 'settings' && ( -
-

Settings

-
-
-

Profile Settings

-

Update your profile information and preferences

-
-
-

Notification Preferences

-

Configure email and system notifications

-
-
-

Security

-

Change password and manage security settings

-
-
-
- )} + navigate(`/termination/${id}`)} />} /> + navigate('/termination')} currentUser={currentUser} />} /> - {currentView === 'users' && ( - - )} + navigate(`/fnf/${id}`)} />} /> + navigate('/fnf')} currentUser={currentUser} />} /> - {currentView === 'resignation' && ( - - )} + navigate(`/finance-onboarding/${id}`)} />} /> + navigate('/finance-onboarding')} />} /> - {currentView === 'termination' && ( - - )} + navigate(`/finance-fnf/${id}`)} />} /> + navigate('/finance-fnf')} />} /> - {currentView === 'fnf' && ( - - )} + navigate(`/constitutional-change/${id}`)} />} /> + navigate('/constitutional-change')} currentUser={currentUser} onOpenWorknote={() => { }} />} /> - {currentView === 'finance-onboarding' && ( - currentUser?.role === 'Finance' ? ( - - ) : ( -
-

Access Denied

-

This page is only accessible to Finance users.

-
- ) - )} + navigate(`/relocation-requests/${id}`)} />} /> + navigate('/relocation-requests')} currentUser={currentUser} onOpenWorknote={() => { }} />} /> - {currentView === 'finance-fnf' && ( - currentUser?.role === 'Finance' ? ( - - ) : ( -
-

Access Denied

-

This page is only accessible to Finance users.

-
- ) - )} + {/* Dealer Routes */} + navigate(`/resignation/${id}`)} />} /> + navigate(`/constitutional-change/${id}`)} />} /> + navigate(`/relocation-requests/${id}`)} />} /> - {currentView === 'master' && ( - currentUser?.role === 'Super Admin' || currentUser?.role === 'DD Admin' || currentUser?.role === 'DD Lead' ? ( - - ) : ( -
-

Access Denied

-

This page is only accessible to Super Admin, DD Admin, and DD Lead users.

-
- ) - )} + {/* Placeholder Routes */} + +

My Tasks

+

Task management interface would be displayed here

+

Shows applications assigned to the current user

+
+ } /> + +

Reports & Analytics

+

Advanced reporting and analytics dashboard

+

Charts, export capabilities, and custom filters

+
+ } /> + +

Settings

+
+
+

Profile Settings

+

Update your profile information and preferences

+
+
+

Notification Preferences

+

Configure email and system notifications

+
+
+

Security

+

Change password and manage security settings

+
+
+
+ } /> - {currentView === 'constitutional-change' && ( - currentUser?.role === 'Super Admin' || currentUser?.role === 'DD Admin' || currentUser?.role === 'DD Lead' ? ( - - ) : ( -
-

Access Denied

-

This page is only accessible to Super Admin, DD Admin, and DD Lead users.

-
- ) - )} + {/* Fallback */} + } /> - {currentView === 'relocation-requests' && ( - currentUser?.role === 'Super Admin' || currentUser?.role === 'DD Admin' || currentUser?.role === 'DD Lead' ? ( - - ) : ( -
-

Access Denied

-

This page is only accessible to Super Admin, DD Admin, and DD Lead users.

-
- ) - )} - - {/* Dealer-specific views */} - {currentView === 'dealer-resignation' && ( - currentUser?.role === 'Dealer' ? ( - - ) : ( -
-

Access Denied

-

This page is only accessible to Dealer users.

-
- ) - )} - - {currentView === 'dealer-constitutional' && ( - currentUser?.role === 'Dealer' ? ( - - ) : ( -
-

Access Denied

-

This page is only accessible to Dealer users.

-
- ) - )} - - {currentView === 'dealer-relocation' && ( - currentUser?.role === 'Dealer' ? ( - - ) : ( -
-

Access Denied

-

This page is only accessible to Dealer users.

-
- ) - )} - - )} - - - - - + + ); } diff --git a/src/api/API.ts b/src/api/API.ts index 1e5f09e..9c4e9e9 100644 --- a/src/api/API.ts +++ b/src/api/API.ts @@ -12,12 +12,28 @@ export const API = { updateRole: (id: string, data: any) => client.put(`/admin/roles/${id}`, data), getZones: () => client.get('/master/zones'), + updateZone: (id: string, data: any) => client.put(`/master/zones/${id}`, data), + createRegion: (data: any) => client.post('/master/regions', data), + updateRegion: (id: string, data: any) => client.put(`/master/regions/${id}`, data), getRegions: () => client.get('/master/regions'), getStates: (zoneId?: string) => client.get('/master/states', { zoneId }), getDistricts: (stateId?: string) => client.get('/master/districts', { stateId }), + getAreas: (districtId?: string) => client.get('/master/areas', { districtId }), + updateArea: (id: string, data: any) => client.put(`/master/areas/${id}`, data), + createArea: (data: any) => client.post('/master/areas', data), + getAreaManagers: () => client.get('/master/area-managers'), + + // Onboarding + submitApplication: (data: any) => client.post('/onboarding/apply', data), + getApplications: () => client.get('/onboarding/applications'), + getApplicationById: (id: string) => client.get(`/onboarding/applications/${id}`), + getLatestQuestionnaire: () => client.get('/questionnaire/latest'), + createQuestionnaireVersion: (data: any) => client.post('/questionnaire/version', data), + submitQuestionnaireResponse: (data: any) => client.post('/questionnaire/response', data), // User management routes getUsers: () => client.get('/admin/users'), + createUser: (data: any) => client.post('/admin/users', data), updateUser: (id: string, data: any) => client.put(`/admin/users/${id}`, data), updateUserStatus: (id: string, data: any) => client.patch(`/admin/users/${id}/status`, data), deleteUser: (id: string) => client.delete(`/admin/users/${id}`), diff --git a/src/components/admin/QuestionnaireBuilder.tsx b/src/components/admin/QuestionnaireBuilder.tsx new file mode 100644 index 0000000..a1dbca9 --- /dev/null +++ b/src/components/admin/QuestionnaireBuilder.tsx @@ -0,0 +1,175 @@ +import React, { useState, useEffect } from 'react'; +import { API } from '../../api/API'; +import { toast } from 'sonner'; // Assuming hot-toast is used +import { Trash2, Plus, Save } from 'lucide-react'; // Assuming lucide-react icons + +interface Question { + courseId?: string; // Legacy? + sectionName: string; + questionText: string; + inputType: 'text' | 'yesno' | 'file' | 'number'; + options?: any; + weight: number; + order: number; + isMandatory: boolean; +} + +const SECTIONS = ['General', 'Financial', 'Infrastructure', 'Experience', 'Market Knowledge']; + +const QuestionnaireBuilder: React.FC = () => { + const [version, setVersion] = useState(`v${new Date().toISOString().split('T')[0]}`); + const [questions, setQuestions] = useState([ + { sectionName: 'General', questionText: '', inputType: 'text', weight: 0, order: 1, isMandatory: true } + ]); + const [loading, setLoading] = useState(false); + + const addQuestion = () => { + setQuestions([...questions, { + sectionName: 'General', + questionText: '', + inputType: 'text', + weight: 0, + order: questions.length + 1, + isMandatory: true + }]); + }; + + const removeQuestion = (index: number) => { + const newQuestions = questions.filter((_, i) => i !== index); + // Re-order + const reOrdered = newQuestions.map((q, i) => ({ ...q, order: i + 1 })); + setQuestions(reOrdered); + }; + + const updateQuestion = (index: number, field: keyof Question, value: any) => { + const newQuestions = [...questions]; + newQuestions[index] = { ...newQuestions[index], [field]: value }; + setQuestions(newQuestions); + }; + + const handleSave = async () => { + if (questions.some(q => !q.questionText)) { + toast.error('All questions must have text'); + return; + } + + try { + setLoading(true); + await API.createQuestionnaireVersion({ + version, + questions + }); + toast.success('Questionnaire version created successfully'); + } catch (error) { + console.error(error); + toast.error('Failed to create questionnaire'); + } finally { + setLoading(false); + } + }; + + return ( +
+
+

Questionnaire Builder

+
+ setVersion(e.target.value)} + className="border p-2 rounded" + placeholder="Version Name" + /> + +
+
+ +
+ {questions.map((q, index) => ( +
+
+
+ + updateQuestion(index, 'questionText', e.target.value)} + className="w-full border p-2 rounded" + placeholder="Enter question..." + /> +
+ +
+ + +
+ +
+ + +
+ +
+ + updateQuestion(index, 'weight', parseFloat(e.target.value))} + className="w-full border p-2 rounded" + /> +
+ +
+ updateQuestion(index, 'isMandatory', e.target.checked)} + className="mr-2" + /> + +
+
+ + +
+ ))} +
+ + +
+ ); +}; + +export default QuestionnaireBuilder; diff --git a/src/components/admin/UserManagementPage.tsx b/src/components/admin/UserManagementPage.tsx index 94b96d9..f70baec 100644 --- a/src/components/admin/UserManagementPage.tsx +++ b/src/components/admin/UserManagementPage.tsx @@ -98,8 +98,23 @@ export function UserManagementPage() { fetchData(); } } else { - // Implementation for Create User can be added here - toast.info('Create user functionality coming soon'); + const res = await adminService.createUser(formData); + if (res.success) { + toast.success('User created successfully'); + setFormData({ + fullName: '', + email: '', + roleCode: '', + status: 'active', + isActive: true, + mobileNumber: '', + department: '', + designation: '', + employeeId: '' + }); + setShowUserModal(false); + fetchData(); + } } } catch (error) { toast.error('Operation failed'); diff --git a/src/components/applications/ApplicationDetails.tsx b/src/components/applications/ApplicationDetails.tsx index b41ac6e..af2db87 100644 --- a/src/components/applications/ApplicationDetails.tsx +++ b/src/components/applications/ApplicationDetails.tsx @@ -1,14 +1,17 @@ -import { useState } from 'react'; -import { mockApplications, mockAuditLogs, mockDocuments, mockWorkNotes, mockLevel1Scores, mockQuestionnaireResponses } from '../../lib/mock-data'; +import { useState, useEffect } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { mockApplications, mockAuditLogs, mockDocuments, mockWorkNotes, mockLevel1Scores, mockQuestionnaireResponses, Application, ApplicationStatus } from '../../lib/mock-data'; +import { onboardingService } from '../../services/onboarding.service'; import { WorkNotesPage } from './WorkNotesPage'; +import QuestionnaireForm from '../dealer/QuestionnaireForm'; import { Button } from '../ui/button'; import { Badge } from '../ui/badge'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs'; import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'; -import { - ArrowLeft, - CheckCircle, - XCircle, +import { + ArrowLeft, + CheckCircle, + XCircle, MessageSquare, Calendar, Clock, @@ -59,10 +62,7 @@ import { DropdownMenuTrigger, } from '../ui/dropdown-menu'; -interface ApplicationDetailsProps { - applicationId: string; - onBack: () => void; -} + interface ProcessStage { id: number | string; @@ -80,8 +80,89 @@ interface ProcessStage { }[]; } -export function ApplicationDetails({ applicationId, onBack }: ApplicationDetailsProps) { - const application = mockApplications.find(app => app.id === applicationId); +export function ApplicationDetails() { + const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + const applicationId = id || ''; + const onBack = () => navigate(-1); + // const application = mockApplications.find(app => app.id === applicationId); + const [application, setApplication] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchApplication = async () => { + try { + setLoading(true); + const data = await onboardingService.getApplicationById(applicationId); + + // Helper to find stage date + const getStageDate = (stageName: string) => { + const stage = data.progressTracking?.find((p: any) => p.stageName === stageName); + return stage?.stageCompletedAt ? new Date(stage.stageCompletedAt).toISOString().split('T')[0] : + stage?.stageStartedAt ? new Date(stage.stageStartedAt).toISOString().split('T')[0] : undefined; + }; + + // Map backend data to frontend Application interface + const mappedApp: Application = { + id: data.id, + registrationNumber: data.applicationId || 'N/A', + name: data.applicantName, + email: data.email, + phone: data.phone, + age: data.age, + education: data.education, + residentialAddress: data.address || data.city || '', + businessAddress: data.address || '', + preferredLocation: data.preferredLocation, + state: data.state, + ownsBike: data.ownRoyalEnfield === 'yes', + pastExperience: data.experienceYears ? `${data.experienceYears} years` : (data.description || ''), + status: data.overallStatus as ApplicationStatus, + questionnaireMarks: 0, + rank: 0, + totalApplicantsAtLocation: 0, + submissionDate: data.createdAt, + assignedUsers: [], + progress: data.progressPercentage || 0, + isShortlisted: data.isShortlisted || true, // Default to true for now + // Add other fields to match interface + companyName: data.companyName, + source: data.source, + existingDealer: data.existingDealer, + royalEnfieldModel: data.royalEnfieldModel, + description: data.description, + pincode: data.pincode, + locationType: data.locationType, + ownRoyalEnfield: data.ownRoyalEnfield, + address: data.address, + // Map timeline dates from progressTracking + level1InterviewDate: getStageDate('1st Level Interview'), + level2InterviewDate: getStageDate('2nd Level Interview'), + level3InterviewDate: getStageDate('3rd Level Interview'), + fddDate: getStageDate('FDD'), + loiApprovalDate: getStageDate('LOI Approval'), + securityDetailsDate: getStageDate('Security Details'), + loiIssueDate: getStageDate('LOI Issue'), + dealerCodeDate: getStageDate('Dealer Code Generation'), + architectureAssignedDate: getStageDate('Architecture Team Assigned'), + architectureDocumentDate: getStageDate('Architecture Document Upload'), + architectureCompletionDate: getStageDate('Architecture Team Completion'), + loaDate: getStageDate('LOA'), + eorCompleteDate: getStageDate('EOR Complete'), + inaugurationDate: getStageDate('Inauguration'), + }; + setApplication(mappedApp); + } catch (error) { + console.error('Failed to fetch application details', error); + } finally { + setLoading(false); + } + }; + + if (applicationId) { + fetchApplication(); + } + }, [applicationId]); const [activeTab, setActiveTab] = useState('questionnaire'); const [showApproveModal, setShowApproveModal] = useState(false); const [showRejectModal, setShowRejectModal] = useState(false); @@ -102,97 +183,101 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails 'statutory-documents': true }); + if (loading) { + return
Loading application details...
; + } + if (!application) { return
Application not found
; } const processStages: ProcessStage[] = [ - { - id: 1, - name: 'Submitted', - status: 'completed', + { + id: 1, + name: 'Submitted', + status: 'completed', date: application.submissionDate, description: 'Application submitted', documentsUploaded: 3 }, - { - id: 2, - name: 'Questionnaire', - status: application.questionnaireMarks ? 'completed' : 'pending', + { + id: 2, + name: 'Questionnaire', + status: application.questionnaireMarks ? 'completed' : 'pending', date: '2025-10-03', description: 'Questionnaire completed', documentsUploaded: 0 }, - { - id: 3, - name: 'Shortlist', - status: ['Shortlisted', 'Level 1 Pending', 'Level 1 Approved', 'Level 2 Pending', 'Level 2 Approved', 'Level 3 Pending', 'FDD Verification', 'Payment Pending', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : 'pending', + { + id: 3, + name: 'Shortlist', + status: ['Shortlisted', 'Level 1 Pending', 'Level 1 Approved', 'Level 2 Pending', 'Level 2 Approved', 'Level 3 Pending', 'FDD Verification', 'Payment Pending', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : 'pending', date: '2025-10-04', description: 'Application shortlisted by DD', documentsUploaded: 2 }, - { - id: 4, - name: '1st Level Interview', + { + id: 4, + name: '1st Level Interview', status: ['Level 1 Approved', 'Level 2 Pending', 'Level 2 Approved', 'Level 3 Pending', 'FDD Verification', 'Payment Pending', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Level 1 Pending' ? 'active' : 'pending', date: application.level1InterviewDate, description: 'DD-ZM + RBM evaluation', evaluators: ['DD-ZM', 'RBM'], documentsUploaded: 1 }, - { - id: 5, - name: '2nd Level Interview', + { + id: 5, + name: '2nd Level Interview', status: ['Level 2 Approved', 'Level 2 Recommended', 'Level 3 Pending', 'FDD Verification', 'Payment Pending', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Level 2 Pending' ? 'active' : 'pending', date: application.level2InterviewDate, description: 'DD Lead + ZBH evaluation', evaluators: ['DD Lead', 'ZBH'], documentsUploaded: 1 }, - { - id: 6, - name: '3rd Level Interview', + { + id: 6, + name: '3rd Level Interview', status: ['FDD Verification', 'Payment Pending', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Level 3 Pending' ? 'active' : 'pending', date: application.level3InterviewDate, description: 'NBH + DD-Head evaluation', evaluators: ['NBH', 'DD-Head'], documentsUploaded: 2 }, - { - id: 7, - name: 'FDD', + { + id: 7, + name: 'FDD', status: ['Payment Pending', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'FDD Verification' ? 'active' : 'pending', date: application.fddDate, description: 'Financial Due Diligence', documentsUploaded: 5 }, - { - id: 8, - name: 'LOI Approval', + { + id: 8, + name: 'LOI Approval', status: ['Payment Pending', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : 'pending', date: application.loiApprovalDate, description: 'Letter of Intent approval', documentsUploaded: 1 }, - { - id: 9, - name: 'Security Details', + { + id: 9, + name: 'Security Details', status: ['Payment Pending', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : 'pending', date: application.securityDetailsDate, description: 'Security verification', documentsUploaded: 3 }, - { - id: 10, - name: 'LOI Issue', + { + id: 10, + name: 'LOI Issue', status: ['Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Payment Pending' ? 'active' : 'pending', date: application.loiIssueDate, description: 'Letter of Intent issued', documentsUploaded: 1 }, - { - id: 11, - name: 'Dealer Code Generation', + { + id: 11, + name: 'Dealer Code Generation', status: ['Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'Statutory GST', 'Statutory PAN', 'Statutory Nodal', 'Statutory Check', 'Statutory Partnership', 'Statutory Firm Reg', 'Statutory Rental', 'Statutory Virtual Code', 'Statutory Domain', 'Statutory MSD', 'Statutory LOI Ack', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Dealer Code Generation' ? 'active' : 'pending', date: application.dealerCodeDate, description: 'Dealer code generated and assigned', @@ -314,25 +399,25 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails } ] }, - { - id: 12, - name: 'LOA', + { + id: 12, + name: 'LOA', status: ['EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'LOA Pending' ? 'active' : 'pending', date: application.loaDate, description: 'Letter of Authorization', documentsUploaded: 1 }, - { - id: 13, - name: 'EOR Complete', + { + id: 13, + name: 'EOR Complete', status: ['Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'EOR Complete' ? 'active' : 'pending', date: application.eorCompleteDate, description: 'Essential Operating Requirements completed', documentsUploaded: 6 }, - { - id: 14, - name: 'Inauguration', + { + id: 14, + name: 'Inauguration', status: application.status === 'Approved' ? 'completed' : application.status === 'Inauguration' ? 'active' : 'pending', date: application.inaugurationDate, description: 'Dealership inauguration ceremony', @@ -554,7 +639,7 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails

Age

-

{application.age} years

+

{application.age ? `${application.age} years` : 'N/A'}

@@ -562,7 +647,7 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails

Education

-

{application.education}

+

{application.education || 'N/A'}

@@ -570,7 +655,15 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails

Preferred Location

-

{application.preferredLocation}

+

{application.preferredLocation || 'N/A'}

+
+ + +
+ +
+

Location Type

+

{application.locationType || 'N/A'}

@@ -578,7 +671,43 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails

Owns Bike

-

{application.ownsBike ? 'Yes' : 'No'}

+

{application.ownRoyalEnfield === 'yes' ? 'Yes' : 'No'}

+
+ + + {application.ownRoyalEnfield === 'yes' && ( +
+ +
+

Bike Model

+

{application.royalEnfieldModel || 'N/A'}

+
+
+ )} + +
+ +
+

Existing Dealer

+

{application.existingDealer === 'yes' ? 'Yes' : 'No'}

+
+
+ + {application.existingDealer === 'yes' && ( +
+ +
+

Company Name

+

{application.companyName || 'N/A'}

+
+
+ )} + +
+ +
+

Source

+

{application.source || 'N/A'}

@@ -596,18 +725,23 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
-

Residential Address

-

{application.residentialAddress}

+

Address

+

{application.address || 'N/A'}

-

Business Address

-

{application.businessAddress}

+

Pincode

+

{application.pincode || 'N/A'}

+
+ +
+

Description

+

{application.description || 'N/A'}

Past Experience

-

{application.pastExperience}

+

{application.pastExperience || 'N/A'}

@@ -645,42 +779,7 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails )} - {application.questionnaireMarks ? ( -
- {mockQuestionnaireResponses.map((response, index) => ( -
-
-
- {index + 1} -
-
-
- {response.category} - - {response.marksScored}/{response.totalMarks} - -
-

{response.question}

-
-
-
-

{response.answer}

-
-
- ))} -
- ) : ( -
- -

Questionnaire Not Completed

-

The applicant has not yet completed the questionnaire.

- {application.deadline && ( -

- Deadline: {new Date(application.deadline).toLocaleDateString()} -

- )} -
- )} + {/* Progress Tab */} @@ -693,18 +792,17 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails -
+
{processStages.map((stage, index) => (
-
+ }`}> {stage.isParallel ? ( ) : ( @@ -722,9 +820,8 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails )}
{index < processStages.length - 1 && !stage.isParallel && ( -
+
)}
@@ -738,7 +835,7 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails

)} {stage.documentsUploaded !== undefined && stage.documentsUploaded > 0 && ( -

{ setSelectedStage(stage.name); @@ -763,7 +860,7 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails const branchKey = branch.name.toLowerCase().replace(/\s+/g, '-'); const isExpanded = expandedBranches[branchKey]; const branchColor = branch.color === 'blue' ? 'blue' : 'green'; - + return (

{/* Branch Header - Clickable */} @@ -772,20 +869,18 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails ...prev, [branchKey]: !prev[branchKey] }))} - className={`w-full flex items-center gap-3 p-4 rounded-lg border-2 transition-all hover:shadow-md ${ - branchColor === 'blue' - ? 'border-blue-300 bg-blue-50 hover:bg-blue-100' - : 'border-green-300 bg-green-50 hover:bg-green-100' - }`} + className={`w-full flex items-center gap-3 p-4 rounded-lg border-2 transition-all hover:shadow-md ${branchColor === 'blue' + ? 'border-blue-300 bg-blue-50 hover:bg-blue-100' + : 'border-green-300 bg-green-50 hover:bg-green-100' + }`} > {isExpanded ? ( ) : ( )} -
+
@@ -805,13 +900,12 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
-
+ }`}> {branchStage.status === 'completed' && ( )} @@ -829,7 +923,7 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails

{branchStage.description}

)} {branchStage.documentsUploaded !== undefined && branchStage.documentsUploaded > 0 && ( -

{ setSelectedStage(branchStage.name); @@ -971,7 +1065,7 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails {/* Payments Tab */}

Payment Information

- +
@@ -1044,7 +1138,7 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails

Rank

- {application.rank} of {application.totalApplicantsAtLocation} + {application.rank} of {application.totalApplicantsAtLocation} in {application.preferredLocation}

@@ -1074,7 +1168,7 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails Actions - - -
- -
- -
- -
)}
- -
- -
- -
- - -
@@ -1039,8 +1410,9 @@ export function MasterPage() { Region Code Region Name Zone + Regional Manager States - Key Cities + Cities Regional Officers ASMs Status @@ -1058,9 +1430,17 @@ export function MasterPage() { {region.code}
- {region.name} + {region.name} + {region.zoneName} - {region.zoneName} + {region.regionalManager ? ( +
+ {region.regionalManager.name} + {region.regionalManager.email} +
+ ) : ( + Not Assigned + )}
@@ -1103,7 +1483,7 @@ export function MasterPage() {
- - -
-
- - ); - })} + {allAreas.map((area) => ( + + +
+ + {area.district?.state?.stateName || area.state?.stateName || 'N/A'} +
+
+ {area.areaName} ({area.city}) + {area.district?.districtName || 'N/A'} + {area.pincode} + + {area.manager ? area.manager.fullName : Unassigned} + + + {area.isActive ? ( + Active + ) : ( + Inactive + )} + + +
+ + +
+
+
+ ))} @@ -1705,6 +2081,131 @@ export function MasterPage() { )} + {/* Add/Edit Region Dialog */} + { + setShowRegionDialog(open); + if (!open) { + setEditingRegionId(null); + setRegionCode(''); + setRegionName(''); + setRegionDescription(''); + setSelectedRegionZone(''); + setSelectedRegionStates([]); + setRegionalManagerId(''); + } + }}> + + + {editingRegionId ? 'Edit' : 'Add'} Regional Office + Create a new region and assign to a zone + +
+
+
+ + +
+
+ +
{userAssignedData.length === 0 && "Debug: No users loaded!"}
+ +
+
+ + setRegionCode(e.target.value)} + /> +
+
+
+ + setRegionName(e.target.value)} + /> +
+
+ +