add location add asm and deealer form submition along with api integrated upto application detail screen
This commit is contained in:
parent
cb793354bf
commit
5bb4b481c2
650
src/App.tsx
650
src/App.tsx
@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { RootState } from './store';
|
import { RootState } from './store';
|
||||||
import { setCredentials, logout as logoutAction, initializeAuth } from './store/slices/authSlice';
|
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 { ApplicationFormPage } from './components/public/ApplicationFormPage';
|
||||||
import { LoginPage } from './components/auth/LoginPage';
|
import { LoginPage } from './components/auth/LoginPage';
|
||||||
import { Sidebar } from './components/layout/Sidebar';
|
import { Sidebar } from './components/layout/Sidebar';
|
||||||
@ -34,32 +35,34 @@ import { WorknotePage } from './components/applications/WorknotePage';
|
|||||||
import { DealerResignationPage } from './components/dealer/DealerResignationPage';
|
import { DealerResignationPage } from './components/dealer/DealerResignationPage';
|
||||||
import { DealerConstitutionalChangePage } from './components/dealer/DealerConstitutionalChangePage';
|
import { DealerConstitutionalChangePage } from './components/dealer/DealerConstitutionalChangePage';
|
||||||
import { DealerRelocationPage } from './components/dealer/DealerRelocationPage';
|
import { DealerRelocationPage } from './components/dealer/DealerRelocationPage';
|
||||||
|
import QuestionnaireBuilder from './components/admin/QuestionnaireBuilder';
|
||||||
import { Toaster } from './components/ui/sonner';
|
import { Toaster } from './components/ui/sonner';
|
||||||
import { User } from './lib/mock-data';
|
import { User } from './lib/mock-data';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { API } from './api/API';
|
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 (
|
||||||
|
<div className="flex h-screen bg-slate-50">
|
||||||
|
<Sidebar onLogout={onLogout} />
|
||||||
|
<div className="flex-1 flex flex-col overflow-hidden">
|
||||||
|
<Header title={title} onRefresh={() => window.location.reload()} />
|
||||||
|
<main className="flex-1 overflow-y-auto p-6">
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
<Toaster />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const dispatch = useDispatch<any>();
|
const dispatch = useDispatch<any>();
|
||||||
const { user: currentUser, isAuthenticated, loading } = useSelector((state: RootState) => state.auth);
|
const { user: currentUser, isAuthenticated, loading } = useSelector((state: RootState) => state.auth);
|
||||||
const [showAdminLogin, setShowAdminLogin] = useState(false);
|
const [showAdminLogin, setShowAdminLogin] = useState(false);
|
||||||
const [currentView, setCurrentView] = useState<View>('dashboard');
|
const navigate = useNavigate();
|
||||||
const [selectedApplicationId, setSelectedApplicationId] = useState<string | null>(null);
|
const location = useLocation();
|
||||||
const [selectedResignationId, setSelectedResignationId] = useState<string | null>(null);
|
|
||||||
const [selectedTerminationId, setSelectedTerminationId] = useState<string | null>(null);
|
|
||||||
const [selectedFnFId, setSelectedFnFId] = useState<string | null>(null);
|
|
||||||
const [selectedPaymentId, setSelectedPaymentId] = useState<string | null>(null);
|
|
||||||
const [selectedFinanceFnFId, setSelectedFinanceFnFId] = useState<string | null>(null);
|
|
||||||
const [selectedConstitutionalChangeId, setSelectedConstitutionalChangeId] = useState<string | null>(null);
|
|
||||||
const [selectedRelocationRequestId, setSelectedRelocationRequestId] = useState<string | null>(null);
|
|
||||||
const [applicationFilter, setApplicationFilter] = useState<string>('all');
|
|
||||||
const [worknoteContext, setWorknoteContext] = useState<{
|
|
||||||
requestId: string;
|
|
||||||
requestType: 'relocation' | 'constitutional-change' | 'fnf' | 'resignation' | 'termination';
|
|
||||||
requestTitle: string;
|
|
||||||
} | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(initializeAuth());
|
dispatch(initializeAuth());
|
||||||
@ -68,27 +71,19 @@ export default function App() {
|
|||||||
const handleLogin = async (email: string, password: string) => {
|
const handleLogin = async (email: string, password: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await API.login({ email, password });
|
const response = await API.login({ email, password });
|
||||||
|
|
||||||
if (response.ok && response.data) {
|
if (response.ok && response.data) {
|
||||||
const { token, user } = response.data as any;
|
const { token, user } = response.data as any;
|
||||||
|
|
||||||
// Store token for persistence
|
|
||||||
localStorage.setItem('token', token);
|
localStorage.setItem('token', token);
|
||||||
|
|
||||||
// Use backend user data
|
|
||||||
const simplifiedUser: User = {
|
const simplifiedUser: User = {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
name: user.fullName || email.split('@')[0],
|
name: user.fullName || email.split('@')[0],
|
||||||
email: user.email,
|
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')
|
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}!`);
|
toast.success(`Welcome back, ${simplifiedUser.name}!`);
|
||||||
|
setShowAdminLogin(false);
|
||||||
} else {
|
} else {
|
||||||
const errorMsg = (response.data as any)?.message || 'Invalid credentials';
|
const errorMsg = (response.data as any)?.message || 'Invalid credentials';
|
||||||
toast.error(errorMsg);
|
toast.error(errorMsg);
|
||||||
@ -101,181 +96,42 @@ export default function App() {
|
|||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
dispatch(logoutAction());
|
dispatch(logoutAction());
|
||||||
setCurrentView('dashboard');
|
|
||||||
setSelectedApplicationId(null);
|
|
||||||
setShowAdminLogin(false);
|
setShowAdminLogin(false);
|
||||||
toast.info('Logged out successfully');
|
toast.info('Logged out successfully');
|
||||||
|
navigate('/');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleShowAdminLogin = () => {
|
// Helper to determine page title based on path
|
||||||
setShowAdminLogin(true);
|
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<string, string> = {
|
||||||
|
'/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) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-screen bg-slate-50">
|
<div className="flex items-center justify-center h-screen bg-slate-50">
|
||||||
@ -284,333 +140,117 @@ export default function App() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show public application form if not authenticated and not trying to log in as admin
|
// Public Routes
|
||||||
if (!isAuthenticated && !showAdminLogin) {
|
if (!isAuthenticated) {
|
||||||
return (
|
return (
|
||||||
<>
|
<Routes>
|
||||||
<ApplicationFormPage onAdminLogin={handleShowAdminLogin} />
|
<Route path="/admin-login" element={<LoginPage onLogin={handleLogin} />} />
|
||||||
<Toaster />
|
<Route path="*" element={showAdminLogin ? <LoginPage onLogin={handleLogin} /> :
|
||||||
</>
|
<><ApplicationFormPage onAdminLogin={() => setShowAdminLogin(true)} /><Toaster /></>}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show admin login page if user clicked admin login but hasn't authenticated yet
|
|
||||||
if (!isAuthenticated && showAdminLogin) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<LoginPage onLogin={handleLogin} />
|
|
||||||
<Toaster />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex h-screen bg-slate-50">
|
|
||||||
<Sidebar
|
|
||||||
activeView={currentView}
|
|
||||||
onNavigate={handleNavigate}
|
|
||||||
onLogout={handleLogout}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex-1 flex flex-col overflow-hidden">
|
|
||||||
<Header
|
|
||||||
title={getPageTitle()}
|
|
||||||
onRefresh={() => window.location.reload()}
|
|
||||||
/>
|
/>
|
||||||
|
</Routes>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
<main className={`flex-1 overflow-y-auto ${currentView === 'worknote' ? '' : 'p-6'}`}>
|
// Protected Routes
|
||||||
{currentView === 'worknote' && worknoteContext ? (
|
return (
|
||||||
<WorknotePage
|
<AppLayout onLogout={handleLogout} title={getPageTitle(location.pathname)}>
|
||||||
requestId={worknoteContext.requestId}
|
<Routes>
|
||||||
requestType={worknoteContext.requestType}
|
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
||||||
requestTitle={worknoteContext.requestTitle}
|
|
||||||
onBack={handleBackFromWorknote}
|
|
||||||
currentUser={currentUser}
|
|
||||||
/>
|
|
||||||
) : selectedPaymentId ? (
|
|
||||||
<FinancePaymentDetailsPage
|
|
||||||
applicationId={selectedPaymentId}
|
|
||||||
onBack={handleBackFromPaymentDetails}
|
|
||||||
/>
|
|
||||||
) : selectedFinanceFnFId ? (
|
|
||||||
<FinanceFnFDetailsPage
|
|
||||||
fnfId={selectedFinanceFnFId}
|
|
||||||
onBack={handleBackFromFinanceFnFDetails}
|
|
||||||
/>
|
|
||||||
) : selectedApplicationId ? (
|
|
||||||
<ApplicationDetails
|
|
||||||
applicationId={selectedApplicationId}
|
|
||||||
onBack={handleBackFromDetails}
|
|
||||||
/>
|
|
||||||
) : selectedResignationId ? (
|
|
||||||
<ResignationDetails
|
|
||||||
resignationId={selectedResignationId}
|
|
||||||
onBack={handleBackFromResignation}
|
|
||||||
currentUser={currentUser}
|
|
||||||
/>
|
|
||||||
) : selectedTerminationId ? (
|
|
||||||
<TerminationDetails
|
|
||||||
terminationId={selectedTerminationId}
|
|
||||||
onBack={handleBackFromTermination}
|
|
||||||
currentUser={currentUser}
|
|
||||||
/>
|
|
||||||
) : selectedFnFId ? (
|
|
||||||
<FnFDetails
|
|
||||||
fnfId={selectedFnFId}
|
|
||||||
onBack={handleBackFromFnF}
|
|
||||||
currentUser={currentUser}
|
|
||||||
/>
|
|
||||||
) : selectedConstitutionalChangeId ? (
|
|
||||||
<ConstitutionalChangeDetails
|
|
||||||
requestId={selectedConstitutionalChangeId}
|
|
||||||
onBack={handleBackFromConstitutionalChange}
|
|
||||||
currentUser={currentUser}
|
|
||||||
onOpenWorknote={handleOpenWorknote}
|
|
||||||
/>
|
|
||||||
) : selectedRelocationRequestId ? (
|
|
||||||
<RelocationRequestDetails
|
|
||||||
requestId={selectedRelocationRequestId}
|
|
||||||
onBack={handleBackFromRelocationRequest}
|
|
||||||
currentUser={currentUser}
|
|
||||||
onOpenWorknote={handleOpenWorknote}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{currentView === 'dashboard' && (
|
|
||||||
currentUser?.role === 'Finance Admin' || currentUser?.role === 'Finance' ? (
|
|
||||||
<FinanceDashboard
|
|
||||||
currentUser={currentUser}
|
|
||||||
onNavigate={handleNavigate}
|
|
||||||
onViewPaymentDetails={handleViewPaymentDetails}
|
|
||||||
onViewFnFDetails={handleViewFinanceFnFDetails}
|
|
||||||
/>
|
|
||||||
) : currentUser?.role === 'Dealer' ? (
|
|
||||||
<DealerDashboard
|
|
||||||
currentUser={currentUser}
|
|
||||||
onNavigate={handleNavigate}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Dashboard onNavigate={handleNavigate} />
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentView === 'all-applications' && (
|
{/* Dashboards */}
|
||||||
currentUser?.role === 'DD' ? (
|
<Route path="/dashboard" element={
|
||||||
<AllApplicationsPage
|
currentUser?.role === 'Finance Admin' || currentUser?.role === 'Finance' ?
|
||||||
onViewDetails={handleViewDetails}
|
<FinanceDashboard currentUser={currentUser} onNavigate={(path) => navigate(`/${path}`)} onViewPaymentDetails={(id) => navigate(`/finance-onboarding/${id}`)} onViewFnFDetails={(id) => navigate(`/finance-fnf/${id}`)} /> :
|
||||||
initialFilter={applicationFilter}
|
currentUser?.role === 'Dealer' ?
|
||||||
/>
|
<DealerDashboard currentUser={currentUser} onNavigate={(path) => navigate(`/${path}`)} /> :
|
||||||
) : (
|
<Dashboard onNavigate={(path) => navigate(`/${path}`)} />
|
||||||
<div className="bg-white rounded-lg border border-slate-200 p-8 text-center">
|
} />
|
||||||
<h2 className="text-slate-900 mb-2">Access Denied</h2>
|
|
||||||
<p className="text-slate-600">This page is only accessible to DD users.</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentView === 'opportunity-requests' && (
|
{/* Applications */}
|
||||||
currentUser?.role === 'DD Lead' ? (
|
<Route path="/applications" element={<ApplicationsPage onViewDetails={(id) => navigate(`/applications/${id}`)} initialFilter="all" />} />
|
||||||
<OpportunityRequestsPage
|
<Route path="/applications/:id" element={<ApplicationDetails applicationId={window.location.pathname.split('/').pop() || ''} onBack={() => navigate('/applications')} />} />
|
||||||
onViewDetails={handleViewDetails}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="bg-white rounded-lg border border-slate-200 p-8 text-center">
|
|
||||||
<h2 className="text-slate-900 mb-2">Access Denied</h2>
|
|
||||||
<p className="text-slate-600">This page is only accessible to DD Lead users.</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentView === 'unopportunity-requests' && (
|
<Route path="/all-applications" element={
|
||||||
currentUser?.role === 'DD Lead' ? (
|
currentUser?.role === 'DD' ? <AllApplicationsPage onViewDetails={(id) => navigate(`/applications/${id}`)} initialFilter="all" /> : <Navigate to="/dashboard" />
|
||||||
<UnopportunityRequestsPage
|
} />
|
||||||
onViewDetails={handleViewDetails}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="bg-white rounded-lg border border-slate-200 p-8 text-center">
|
|
||||||
<h2 className="text-slate-900 mb-2">Access Denied</h2>
|
|
||||||
<p className="text-slate-600">This page is only accessible to DD Lead users.</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentView === 'applications' && (
|
{/* Admin/Lead Routes */}
|
||||||
<ApplicationsPage
|
<Route path="/opportunity-requests" element={<OpportunityRequestsPage onViewDetails={(id) => navigate(`/applications/${id}`)} />} />
|
||||||
onViewDetails={handleViewDetails}
|
<Route path="/unopportunity-requests" element={<UnopportunityRequestsPage onViewDetails={(id) => navigate(`/applications/${id}`)} />} />
|
||||||
initialFilter={applicationFilter}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentView === 'tasks' && (
|
{/* Other Modules */}
|
||||||
<div className="bg-white rounded-lg border border-slate-200 p-8 text-center">
|
<Route path="/users" element={<UserManagementPage />} />
|
||||||
<h2 className="text-slate-900 mb-2">My Tasks</h2>
|
<Route path="/master" element={<MasterPage />} />
|
||||||
<p className="text-slate-600">Task management interface would be displayed here</p>
|
<Route path="/questions" element={<QuestionnaireBuilder />} />
|
||||||
<p className="text-slate-500 mt-4">Shows applications assigned to the current user</p>
|
<Route path="/questionnaire-builder" element={<QuestionnaireBuilder />} />
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentView === 'reports' && (
|
{/* HR/Finance Modules (Simplified for brevity, following pattern) */}
|
||||||
<div className="bg-white rounded-lg border border-slate-200 p-8 text-center">
|
<Route path="/resignation" element={<ResignationPage currentUser={currentUser} onViewDetails={(id) => navigate(`/resignation/${id}`)} />} />
|
||||||
<h2 className="text-slate-900 mb-2">Reports & Analytics</h2>
|
<Route path="/resignation/:id" element={<ResignationDetails resignationId={window.location.pathname.split('/').pop() || ''} onBack={() => navigate('/resignation')} currentUser={currentUser} />} />
|
||||||
<p className="text-slate-600">Advanced reporting and analytics dashboard</p>
|
|
||||||
<p className="text-slate-500 mt-4">Charts, export capabilities, and custom filters</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentView === 'settings' && (
|
<Route path="/termination" element={<TerminationPage currentUser={currentUser} onViewDetails={(id) => navigate(`/termination/${id}`)} />} />
|
||||||
<div className="bg-white rounded-lg border border-slate-200 p-8">
|
<Route path="/termination/:id" element={<TerminationDetails terminationId={window.location.pathname.split('/').pop() || ''} onBack={() => navigate('/termination')} currentUser={currentUser} />} />
|
||||||
<h2 className="text-slate-900 mb-4">Settings</h2>
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<h3 className="text-slate-900 mb-2">Profile Settings</h3>
|
|
||||||
<p className="text-slate-600">Update your profile information and preferences</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="text-slate-900 mb-2">Notification Preferences</h3>
|
|
||||||
<p className="text-slate-600">Configure email and system notifications</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="text-slate-900 mb-2">Security</h3>
|
|
||||||
<p className="text-slate-600">Change password and manage security settings</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentView === 'users' && (
|
<Route path="/fnf" element={<FnFPage currentUser={currentUser} onViewDetails={(id) => navigate(`/fnf/${id}`)} />} />
|
||||||
<UserManagementPage />
|
<Route path="/fnf/:id" element={<FnFDetails fnfId={window.location.pathname.split('/').pop() || ''} onBack={() => navigate('/fnf')} currentUser={currentUser} />} />
|
||||||
)}
|
|
||||||
|
|
||||||
{currentView === 'resignation' && (
|
<Route path="/finance-onboarding" element={<FinanceOnboardingPage onViewPaymentDetails={(id) => navigate(`/finance-onboarding/${id}`)} />} />
|
||||||
<ResignationPage
|
<Route path="/finance-onboarding/:id" element={<FinancePaymentDetailsPage applicationId={window.location.pathname.split('/').pop() || ''} onBack={() => navigate('/finance-onboarding')} />} />
|
||||||
currentUser={currentUser}
|
|
||||||
onViewDetails={handleViewResignationDetails}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentView === 'termination' && (
|
<Route path="/finance-fnf" element={<FinanceFnFPage onViewFnFDetails={(id) => navigate(`/finance-fnf/${id}`)} />} />
|
||||||
<TerminationPage
|
<Route path="/finance-fnf/:id" element={<FinanceFnFDetailsPage fnfId={window.location.pathname.split('/').pop() || ''} onBack={() => navigate('/finance-fnf')} />} />
|
||||||
currentUser={currentUser}
|
|
||||||
onViewDetails={handleViewTerminationDetails}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentView === 'fnf' && (
|
<Route path="/constitutional-change" element={<ConstitutionalChangePage currentUser={currentUser} onViewDetails={(id) => navigate(`/constitutional-change/${id}`)} />} />
|
||||||
<FnFPage
|
<Route path="/constitutional-change/:id" element={<ConstitutionalChangeDetails requestId={window.location.pathname.split('/').pop() || ''} onBack={() => navigate('/constitutional-change')} currentUser={currentUser} onOpenWorknote={() => { }} />} />
|
||||||
currentUser={currentUser}
|
|
||||||
onViewDetails={handleViewFnFDetails}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentView === 'finance-onboarding' && (
|
<Route path="/relocation-requests" element={<RelocationRequestPage currentUser={currentUser} onViewDetails={(id) => navigate(`/relocation-requests/${id}`)} />} />
|
||||||
currentUser?.role === 'Finance' ? (
|
<Route path="/relocation-requests/:id" element={<RelocationRequestDetails requestId={window.location.pathname.split('/').pop() || ''} onBack={() => navigate('/relocation-requests')} currentUser={currentUser} onOpenWorknote={() => { }} />} />
|
||||||
<FinanceOnboardingPage onViewPaymentDetails={handleViewPaymentDetails} />
|
|
||||||
) : (
|
|
||||||
<div className="bg-white rounded-lg border border-slate-200 p-8 text-center">
|
|
||||||
<h2 className="text-slate-900 mb-2">Access Denied</h2>
|
|
||||||
<p className="text-slate-600">This page is only accessible to Finance users.</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentView === 'finance-fnf' && (
|
{/* Dealer Routes */}
|
||||||
currentUser?.role === 'Finance' ? (
|
<Route path="/dealer-resignation" element={<DealerResignationPage currentUser={currentUser} onViewDetails={(id) => navigate(`/resignation/${id}`)} />} />
|
||||||
<FinanceFnFPage onViewFnFDetails={handleViewFinanceFnFDetails} />
|
<Route path="/dealer-constitutional" element={<DealerConstitutionalChangePage currentUser={currentUser} onViewDetails={(id) => navigate(`/constitutional-change/${id}`)} />} />
|
||||||
) : (
|
<Route path="/dealer-relocation" element={<DealerRelocationPage currentUser={currentUser} onViewDetails={(id) => navigate(`/relocation-requests/${id}`)} />} />
|
||||||
<div className="bg-white rounded-lg border border-slate-200 p-8 text-center">
|
|
||||||
<h2 className="text-slate-900 mb-2">Access Denied</h2>
|
|
||||||
<p className="text-slate-600">This page is only accessible to Finance users.</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentView === 'master' && (
|
{/* Placeholder Routes */}
|
||||||
currentUser?.role === 'Super Admin' || currentUser?.role === 'DD Admin' || currentUser?.role === 'DD Lead' ? (
|
<Route path="/tasks" element={
|
||||||
<MasterPage />
|
<div className="bg-white rounded-lg border border-slate-200 p-8 text-center">
|
||||||
) : (
|
<h2 className="text-slate-900 mb-2">My Tasks</h2>
|
||||||
<div className="bg-white rounded-lg border border-slate-200 p-8 text-center">
|
<p className="text-slate-600">Task management interface would be displayed here</p>
|
||||||
<h2 className="text-slate-900 mb-2">Access Denied</h2>
|
<p className="text-slate-500 mt-4">Shows applications assigned to the current user</p>
|
||||||
<p className="text-slate-600">This page is only accessible to Super Admin, DD Admin, and DD Lead users.</p>
|
</div>
|
||||||
</div>
|
} />
|
||||||
)
|
<Route path="/reports" element={
|
||||||
)}
|
<div className="bg-white rounded-lg border border-slate-200 p-8 text-center">
|
||||||
|
<h2 className="text-slate-900 mb-2">Reports & Analytics</h2>
|
||||||
|
<p className="text-slate-600">Advanced reporting and analytics dashboard</p>
|
||||||
|
<p className="text-slate-500 mt-4">Charts, export capabilities, and custom filters</p>
|
||||||
|
</div>
|
||||||
|
} />
|
||||||
|
<Route path="/settings" element={
|
||||||
|
<div className="bg-white rounded-lg border border-slate-200 p-8">
|
||||||
|
<h2 className="text-slate-900 mb-4">Settings</h2>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-slate-900 mb-2">Profile Settings</h3>
|
||||||
|
<p className="text-slate-600">Update your profile information and preferences</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-slate-900 mb-2">Notification Preferences</h3>
|
||||||
|
<p className="text-slate-600">Configure email and system notifications</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-slate-900 mb-2">Security</h3>
|
||||||
|
<p className="text-slate-600">Change password and manage security settings</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} />
|
||||||
|
|
||||||
{currentView === 'constitutional-change' && (
|
{/* Fallback */}
|
||||||
currentUser?.role === 'Super Admin' || currentUser?.role === 'DD Admin' || currentUser?.role === 'DD Lead' ? (
|
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
||||||
<ConstitutionalChangePage
|
|
||||||
currentUser={currentUser}
|
|
||||||
onViewDetails={handleViewConstitutionalChangeDetails}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="bg-white rounded-lg border border-slate-200 p-8 text-center">
|
|
||||||
<h2 className="text-slate-900 mb-2">Access Denied</h2>
|
|
||||||
<p className="text-slate-600">This page is only accessible to Super Admin, DD Admin, and DD Lead users.</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentView === 'relocation-requests' && (
|
</Routes>
|
||||||
currentUser?.role === 'Super Admin' || currentUser?.role === 'DD Admin' || currentUser?.role === 'DD Lead' ? (
|
</AppLayout>
|
||||||
<RelocationRequestPage
|
|
||||||
currentUser={currentUser}
|
|
||||||
onViewDetails={handleViewRelocationRequestDetails}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="bg-white rounded-lg border border-slate-200 p-8 text-center">
|
|
||||||
<h2 className="text-slate-900 mb-2">Access Denied</h2>
|
|
||||||
<p className="text-slate-600">This page is only accessible to Super Admin, DD Admin, and DD Lead users.</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Dealer-specific views */}
|
|
||||||
{currentView === 'dealer-resignation' && (
|
|
||||||
currentUser?.role === 'Dealer' ? (
|
|
||||||
<DealerResignationPage
|
|
||||||
currentUser={currentUser}
|
|
||||||
onViewDetails={handleViewResignationDetails}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="bg-white rounded-lg border border-slate-200 p-8 text-center">
|
|
||||||
<h2 className="text-slate-900 mb-2">Access Denied</h2>
|
|
||||||
<p className="text-slate-600">This page is only accessible to Dealer users.</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentView === 'dealer-constitutional' && (
|
|
||||||
currentUser?.role === 'Dealer' ? (
|
|
||||||
<DealerConstitutionalChangePage
|
|
||||||
currentUser={currentUser}
|
|
||||||
onViewDetails={handleViewConstitutionalChangeDetails}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="bg-white rounded-lg border border-slate-200 p-8 text-center">
|
|
||||||
<h2 className="text-slate-900 mb-2">Access Denied</h2>
|
|
||||||
<p className="text-slate-600">This page is only accessible to Dealer users.</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentView === 'dealer-relocation' && (
|
|
||||||
currentUser?.role === 'Dealer' ? (
|
|
||||||
<DealerRelocationPage
|
|
||||||
currentUser={currentUser}
|
|
||||||
onViewDetails={handleViewRelocationRequestDetails}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="bg-white rounded-lg border border-slate-200 p-8 text-center">
|
|
||||||
<h2 className="text-slate-900 mb-2">Access Denied</h2>
|
|
||||||
<p className="text-slate-600">This page is only accessible to Dealer users.</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Toaster />
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,12 +12,28 @@ export const API = {
|
|||||||
updateRole: (id: string, data: any) => client.put(`/admin/roles/${id}`, data),
|
updateRole: (id: string, data: any) => client.put(`/admin/roles/${id}`, data),
|
||||||
|
|
||||||
getZones: () => client.get('/master/zones'),
|
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'),
|
getRegions: () => client.get('/master/regions'),
|
||||||
getStates: (zoneId?: string) => client.get('/master/states', { zoneId }),
|
getStates: (zoneId?: string) => client.get('/master/states', { zoneId }),
|
||||||
getDistricts: (stateId?: string) => client.get('/master/districts', { stateId }),
|
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
|
// User management routes
|
||||||
getUsers: () => client.get('/admin/users'),
|
getUsers: () => client.get('/admin/users'),
|
||||||
|
createUser: (data: any) => client.post('/admin/users', data),
|
||||||
updateUser: (id: string, data: any) => client.put(`/admin/users/${id}`, data),
|
updateUser: (id: string, data: any) => client.put(`/admin/users/${id}`, data),
|
||||||
updateUserStatus: (id: string, data: any) => client.patch(`/admin/users/${id}/status`, data),
|
updateUserStatus: (id: string, data: any) => client.patch(`/admin/users/${id}/status`, data),
|
||||||
deleteUser: (id: string) => client.delete(`/admin/users/${id}`),
|
deleteUser: (id: string) => client.delete(`/admin/users/${id}`),
|
||||||
|
|||||||
175
src/components/admin/QuestionnaireBuilder.tsx
Normal file
175
src/components/admin/QuestionnaireBuilder.tsx
Normal file
@ -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<string>(`v${new Date().toISOString().split('T')[0]}`);
|
||||||
|
const [questions, setQuestions] = useState<Question[]>([
|
||||||
|
{ 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 (
|
||||||
|
<div className="p-6 bg-white rounded-lg shadow-md">
|
||||||
|
<div className="flex justify-between items-center mb-6">
|
||||||
|
<h2 className="text-2xl font-bold">Questionnaire Builder</h2>
|
||||||
|
<div className="flex gap-4 items-center">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={version}
|
||||||
|
onChange={(e) => setVersion(e.target.value)}
|
||||||
|
className="border p-2 rounded"
|
||||||
|
placeholder="Version Name"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleSave}
|
||||||
|
disabled={loading}
|
||||||
|
className="bg-blue-600 text-white px-4 py-2 rounded flex items-center gap-2 hover:bg-blue-700"
|
||||||
|
>
|
||||||
|
<Save size={18} /> {loading ? 'Saving...' : 'Publish Version'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{questions.map((q, index) => (
|
||||||
|
<div key={index} className="border p-4 rounded-md bg-gray-50 flex gap-4 items-start">
|
||||||
|
<div className="flex-1 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
|
<div className="col-span-2">
|
||||||
|
<label className="block text-sm font-medium mb-1">Question Text</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={q.questionText}
|
||||||
|
onChange={(e) => updateQuestion(index, 'questionText', e.target.value)}
|
||||||
|
className="w-full border p-2 rounded"
|
||||||
|
placeholder="Enter question..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1">Section</label>
|
||||||
|
<select
|
||||||
|
value={q.sectionName}
|
||||||
|
onChange={(e) => updateQuestion(index, 'sectionName', e.target.value)}
|
||||||
|
className="w-full border p-2 rounded"
|
||||||
|
>
|
||||||
|
{SECTIONS.map(s => <option key={s} value={s}>{s}</option>)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1">Type</label>
|
||||||
|
<select
|
||||||
|
value={q.inputType}
|
||||||
|
onChange={(e) => updateQuestion(index, 'inputType', e.target.value as any)}
|
||||||
|
className="w-full border p-2 rounded"
|
||||||
|
>
|
||||||
|
<option value="text">Text</option>
|
||||||
|
<option value="yesno">Yes/No</option>
|
||||||
|
<option value="number">Number</option>
|
||||||
|
<option value="file">File Upload</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1">Weightage</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={q.weight}
|
||||||
|
onChange={(e) => updateQuestion(index, 'weight', parseFloat(e.target.value))}
|
||||||
|
className="w-full border p-2 rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center pt-6">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={q.isMandatory}
|
||||||
|
onChange={(e) => updateQuestion(index, 'isMandatory', e.target.checked)}
|
||||||
|
className="mr-2"
|
||||||
|
/>
|
||||||
|
<label className="text-sm">Mandatory</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => removeQuestion(index)}
|
||||||
|
className="text-red-500 hover:text-red-700 mt-8"
|
||||||
|
title="Remove Question"
|
||||||
|
>
|
||||||
|
<Trash2 size={20} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={addQuestion}
|
||||||
|
className="mt-6 w-full border-2 border-dashed border-gray-300 p-4 rounded text-gray-500 hover:border-blue-500 hover:text-blue-500 flex justify-center items-center gap-2"
|
||||||
|
>
|
||||||
|
<Plus size={20} /> Add Question
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QuestionnaireBuilder;
|
||||||
@ -98,8 +98,23 @@ export function UserManagementPage() {
|
|||||||
fetchData();
|
fetchData();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Implementation for Create User can be added here
|
const res = await adminService.createUser(formData);
|
||||||
toast.info('Create user functionality coming soon');
|
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) {
|
} catch (error) {
|
||||||
toast.error('Operation failed');
|
toast.error('Operation failed');
|
||||||
|
|||||||
@ -1,14 +1,17 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { mockApplications, mockAuditLogs, mockDocuments, mockWorkNotes, mockLevel1Scores, mockQuestionnaireResponses } from '../../lib/mock-data';
|
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 { WorkNotesPage } from './WorkNotesPage';
|
||||||
|
import QuestionnaireForm from '../dealer/QuestionnaireForm';
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
import { Badge } from '../ui/badge';
|
import { Badge } from '../ui/badge';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
|
||||||
import {
|
import {
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
XCircle,
|
XCircle,
|
||||||
MessageSquare,
|
MessageSquare,
|
||||||
Calendar,
|
Calendar,
|
||||||
Clock,
|
Clock,
|
||||||
@ -59,10 +62,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '../ui/dropdown-menu';
|
} from '../ui/dropdown-menu';
|
||||||
|
|
||||||
interface ApplicationDetailsProps {
|
|
||||||
applicationId: string;
|
|
||||||
onBack: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ProcessStage {
|
interface ProcessStage {
|
||||||
id: number | string;
|
id: number | string;
|
||||||
@ -80,8 +80,89 @@ interface ProcessStage {
|
|||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ApplicationDetails({ applicationId, onBack }: ApplicationDetailsProps) {
|
export function ApplicationDetails() {
|
||||||
const application = mockApplications.find(app => app.id === applicationId);
|
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<Application | null>(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 [activeTab, setActiveTab] = useState('questionnaire');
|
||||||
const [showApproveModal, setShowApproveModal] = useState(false);
|
const [showApproveModal, setShowApproveModal] = useState(false);
|
||||||
const [showRejectModal, setShowRejectModal] = useState(false);
|
const [showRejectModal, setShowRejectModal] = useState(false);
|
||||||
@ -102,97 +183,101 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
'statutory-documents': true
|
'statutory-documents': true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <div>Loading application details...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
if (!application) {
|
if (!application) {
|
||||||
return <div>Application not found</div>;
|
return <div>Application not found</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const processStages: ProcessStage[] = [
|
const processStages: ProcessStage[] = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'Submitted',
|
name: 'Submitted',
|
||||||
status: 'completed',
|
status: 'completed',
|
||||||
date: application.submissionDate,
|
date: application.submissionDate,
|
||||||
description: 'Application submitted',
|
description: 'Application submitted',
|
||||||
documentsUploaded: 3
|
documentsUploaded: 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
name: 'Questionnaire',
|
name: 'Questionnaire',
|
||||||
status: application.questionnaireMarks ? 'completed' : 'pending',
|
status: application.questionnaireMarks ? 'completed' : 'pending',
|
||||||
date: '2025-10-03',
|
date: '2025-10-03',
|
||||||
description: 'Questionnaire completed',
|
description: 'Questionnaire completed',
|
||||||
documentsUploaded: 0
|
documentsUploaded: 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
name: 'Shortlist',
|
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',
|
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',
|
date: '2025-10-04',
|
||||||
description: 'Application shortlisted by DD',
|
description: 'Application shortlisted by DD',
|
||||||
documentsUploaded: 2
|
documentsUploaded: 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
name: '1st Level Interview',
|
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',
|
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,
|
date: application.level1InterviewDate,
|
||||||
description: 'DD-ZM + RBM evaluation',
|
description: 'DD-ZM + RBM evaluation',
|
||||||
evaluators: ['DD-ZM', 'RBM'],
|
evaluators: ['DD-ZM', 'RBM'],
|
||||||
documentsUploaded: 1
|
documentsUploaded: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 5,
|
id: 5,
|
||||||
name: '2nd Level Interview',
|
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',
|
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,
|
date: application.level2InterviewDate,
|
||||||
description: 'DD Lead + ZBH evaluation',
|
description: 'DD Lead + ZBH evaluation',
|
||||||
evaluators: ['DD Lead', 'ZBH'],
|
evaluators: ['DD Lead', 'ZBH'],
|
||||||
documentsUploaded: 1
|
documentsUploaded: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 6,
|
id: 6,
|
||||||
name: '3rd Level Interview',
|
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',
|
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,
|
date: application.level3InterviewDate,
|
||||||
description: 'NBH + DD-Head evaluation',
|
description: 'NBH + DD-Head evaluation',
|
||||||
evaluators: ['NBH', 'DD-Head'],
|
evaluators: ['NBH', 'DD-Head'],
|
||||||
documentsUploaded: 2
|
documentsUploaded: 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 7,
|
id: 7,
|
||||||
name: 'FDD',
|
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',
|
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,
|
date: application.fddDate,
|
||||||
description: 'Financial Due Diligence',
|
description: 'Financial Due Diligence',
|
||||||
documentsUploaded: 5
|
documentsUploaded: 5
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 8,
|
id: 8,
|
||||||
name: 'LOI Approval',
|
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',
|
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,
|
date: application.loiApprovalDate,
|
||||||
description: 'Letter of Intent approval',
|
description: 'Letter of Intent approval',
|
||||||
documentsUploaded: 1
|
documentsUploaded: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 9,
|
id: 9,
|
||||||
name: 'Security Details',
|
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',
|
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,
|
date: application.securityDetailsDate,
|
||||||
description: 'Security verification',
|
description: 'Security verification',
|
||||||
documentsUploaded: 3
|
documentsUploaded: 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 10,
|
id: 10,
|
||||||
name: 'LOI Issue',
|
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',
|
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,
|
date: application.loiIssueDate,
|
||||||
description: 'Letter of Intent issued',
|
description: 'Letter of Intent issued',
|
||||||
documentsUploaded: 1
|
documentsUploaded: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 11,
|
id: 11,
|
||||||
name: 'Dealer Code Generation',
|
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',
|
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,
|
date: application.dealerCodeDate,
|
||||||
description: 'Dealer code generated and assigned',
|
description: 'Dealer code generated and assigned',
|
||||||
@ -314,25 +399,25 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 12,
|
id: 12,
|
||||||
name: 'LOA',
|
name: 'LOA',
|
||||||
status: ['EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'LOA Pending' ? 'active' : 'pending',
|
status: ['EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'LOA Pending' ? 'active' : 'pending',
|
||||||
date: application.loaDate,
|
date: application.loaDate,
|
||||||
description: 'Letter of Authorization',
|
description: 'Letter of Authorization',
|
||||||
documentsUploaded: 1
|
documentsUploaded: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 13,
|
id: 13,
|
||||||
name: 'EOR Complete',
|
name: 'EOR Complete',
|
||||||
status: ['Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'EOR Complete' ? 'active' : 'pending',
|
status: ['Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'EOR Complete' ? 'active' : 'pending',
|
||||||
date: application.eorCompleteDate,
|
date: application.eorCompleteDate,
|
||||||
description: 'Essential Operating Requirements completed',
|
description: 'Essential Operating Requirements completed',
|
||||||
documentsUploaded: 6
|
documentsUploaded: 6
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 14,
|
id: 14,
|
||||||
name: 'Inauguration',
|
name: 'Inauguration',
|
||||||
status: application.status === 'Approved' ? 'completed' : application.status === 'Inauguration' ? 'active' : 'pending',
|
status: application.status === 'Approved' ? 'completed' : application.status === 'Inauguration' ? 'active' : 'pending',
|
||||||
date: application.inaugurationDate,
|
date: application.inaugurationDate,
|
||||||
description: 'Dealership inauguration ceremony',
|
description: 'Dealership inauguration ceremony',
|
||||||
@ -554,7 +639,7 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
<User className="w-5 h-5 text-slate-400 mt-1" />
|
<User className="w-5 h-5 text-slate-400 mt-1" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-slate-600">Age</p>
|
<p className="text-slate-600">Age</p>
|
||||||
<p className="text-slate-900">{application.age} years</p>
|
<p className="text-slate-900">{application.age ? `${application.age} years` : 'N/A'}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -562,7 +647,7 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
<GraduationCap className="w-5 h-5 text-slate-400 mt-1" />
|
<GraduationCap className="w-5 h-5 text-slate-400 mt-1" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-slate-600">Education</p>
|
<p className="text-slate-600">Education</p>
|
||||||
<p className="text-slate-900">{application.education}</p>
|
<p className="text-slate-900">{application.education || 'N/A'}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -570,7 +655,15 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
<MapPin className="w-5 h-5 text-slate-400 mt-1" />
|
<MapPin className="w-5 h-5 text-slate-400 mt-1" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-slate-600">Preferred Location</p>
|
<p className="text-slate-600">Preferred Location</p>
|
||||||
<p className="text-slate-900">{application.preferredLocation}</p>
|
<p className="text-slate-900">{application.preferredLocation || 'N/A'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<MapPin className="w-5 h-5 text-slate-400 mt-1" />
|
||||||
|
<div>
|
||||||
|
<p className="text-slate-600">Location Type</p>
|
||||||
|
<p className="text-slate-900">{application.locationType || 'N/A'}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -578,7 +671,43 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
<Bike className="w-5 h-5 text-slate-400 mt-1" />
|
<Bike className="w-5 h-5 text-slate-400 mt-1" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-slate-600">Owns Bike</p>
|
<p className="text-slate-600">Owns Bike</p>
|
||||||
<p className="text-slate-900">{application.ownsBike ? 'Yes' : 'No'}</p>
|
<p className="text-slate-900">{application.ownRoyalEnfield === 'yes' ? 'Yes' : 'No'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{application.ownRoyalEnfield === 'yes' && (
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<Bike className="w-5 h-5 text-slate-400 mt-1" />
|
||||||
|
<div>
|
||||||
|
<p className="text-slate-600">Bike Model</p>
|
||||||
|
<p className="text-slate-900">{application.royalEnfieldModel || 'N/A'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<User className="w-5 h-5 text-slate-400 mt-1" />
|
||||||
|
<div>
|
||||||
|
<p className="text-slate-600">Existing Dealer</p>
|
||||||
|
<p className="text-slate-900">{application.existingDealer === 'yes' ? 'Yes' : 'No'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{application.existingDealer === 'yes' && (
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<User className="w-5 h-5 text-slate-400 mt-1" />
|
||||||
|
<div>
|
||||||
|
<p className="text-slate-600">Company Name</p>
|
||||||
|
<p className="text-slate-900">{application.companyName || 'N/A'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<ClipboardList className="w-5 h-5 text-slate-400 mt-1" />
|
||||||
|
<div>
|
||||||
|
<p className="text-slate-600">Source</p>
|
||||||
|
<p className="text-slate-900">{application.source || 'N/A'}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -596,18 +725,23 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-slate-600 mb-2">Residential Address</p>
|
<p className="text-slate-600 mb-2">Address</p>
|
||||||
<p className="text-slate-900">{application.residentialAddress}</p>
|
<p className="text-slate-900">{application.address || 'N/A'}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-slate-600 mb-2">Business Address</p>
|
<p className="text-slate-600 mb-2">Pincode</p>
|
||||||
<p className="text-slate-900">{application.businessAddress}</p>
|
<p className="text-slate-900">{application.pincode || 'N/A'}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-slate-600 mb-2">Description</p>
|
||||||
|
<p className="text-slate-900">{application.description || 'N/A'}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-slate-600 mb-2">Past Experience</p>
|
<p className="text-slate-600 mb-2">Past Experience</p>
|
||||||
<p className="text-slate-900">{application.pastExperience}</p>
|
<p className="text-slate-900">{application.pastExperience || 'N/A'}</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@ -645,42 +779,7 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{application.questionnaireMarks ? (
|
<QuestionnaireForm applicationId={application.id} readOnly={true} />
|
||||||
<div className="space-y-6">
|
|
||||||
{mockQuestionnaireResponses.map((response, index) => (
|
|
||||||
<div key={response.id} className="border border-slate-200 rounded-lg p-5 hover:border-amber-300 transition-colors">
|
|
||||||
<div className="flex items-start gap-3 mb-3">
|
|
||||||
<div className="w-8 h-8 rounded-full bg-amber-100 flex items-center justify-center flex-shrink-0">
|
|
||||||
<span className="text-amber-600">{index + 1}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="flex items-center gap-2 mb-2">
|
|
||||||
<Badge variant="outline">{response.category}</Badge>
|
|
||||||
<Badge className="bg-green-600">
|
|
||||||
{response.marksScored}/{response.totalMarks}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<h4 className="text-slate-900">{response.question}</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="ml-11">
|
|
||||||
<p className="text-slate-600 leading-relaxed">{response.answer}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
||||||
<ClipboardList className="w-16 h-16 text-slate-300 mb-4" />
|
|
||||||
<h3 className="text-slate-900 mb-2">Questionnaire Not Completed</h3>
|
|
||||||
<p className="text-slate-600 mb-4">The applicant has not yet completed the questionnaire.</p>
|
|
||||||
{application.deadline && (
|
|
||||||
<p className="text-slate-500">
|
|
||||||
Deadline: {new Date(application.deadline).toLocaleDateString()}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
{/* Progress Tab */}
|
{/* Progress Tab */}
|
||||||
@ -693,18 +792,17 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
<Progress value={application.progress} className="h-3 mb-6" />
|
<Progress value={application.progress} className="h-3 mb-6" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{processStages.map((stage, index) => (
|
{processStages.map((stage, index) => (
|
||||||
<div key={stage.id}>
|
<div key={stage.id}>
|
||||||
<div className="flex gap-4 pb-8">
|
<div className="flex gap-4 pb-8">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className={`w-10 h-10 rounded-full flex items-center justify-center border-2 ${
|
<div className={`w-10 h-10 rounded-full flex items-center justify-center border-2 ${stage.status === 'completed'
|
||||||
stage.status === 'completed'
|
? 'bg-green-500 border-green-500'
|
||||||
? 'bg-green-500 border-green-500'
|
: stage.status === 'active'
|
||||||
: stage.status === 'active'
|
|
||||||
? 'bg-amber-500 border-amber-500'
|
? 'bg-amber-500 border-amber-500'
|
||||||
: 'bg-slate-200 border-slate-300'
|
: 'bg-slate-200 border-slate-300'
|
||||||
}`}>
|
}`}>
|
||||||
{stage.isParallel ? (
|
{stage.isParallel ? (
|
||||||
<GitBranch className="w-5 h-5 text-white" />
|
<GitBranch className="w-5 h-5 text-white" />
|
||||||
) : (
|
) : (
|
||||||
@ -722,9 +820,8 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{index < processStages.length - 1 && !stage.isParallel && (
|
{index < processStages.length - 1 && !stage.isParallel && (
|
||||||
<div className={`absolute top-10 left-1/2 -translate-x-1/2 w-0.5 h-full ${
|
<div className={`absolute top-10 left-1/2 -translate-x-1/2 w-0.5 h-full ${stage.status === 'completed' ? 'bg-green-500' : 'bg-slate-300'
|
||||||
stage.status === 'completed' ? 'bg-green-500' : 'bg-slate-300'
|
}`}></div>
|
||||||
}`}></div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 pt-1">
|
<div className="flex-1 pt-1">
|
||||||
@ -738,7 +835,7 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{stage.documentsUploaded !== undefined && stage.documentsUploaded > 0 && (
|
{stage.documentsUploaded !== undefined && stage.documentsUploaded > 0 && (
|
||||||
<p
|
<p
|
||||||
className="text-blue-600 text-sm mt-0.5 cursor-pointer hover:underline hover:text-blue-700"
|
className="text-blue-600 text-sm mt-0.5 cursor-pointer hover:underline hover:text-blue-700"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedStage(stage.name);
|
setSelectedStage(stage.name);
|
||||||
@ -763,7 +860,7 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
const branchKey = branch.name.toLowerCase().replace(/\s+/g, '-');
|
const branchKey = branch.name.toLowerCase().replace(/\s+/g, '-');
|
||||||
const isExpanded = expandedBranches[branchKey];
|
const isExpanded = expandedBranches[branchKey];
|
||||||
const branchColor = branch.color === 'blue' ? 'blue' : 'green';
|
const branchColor = branch.color === 'blue' ? 'blue' : 'green';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={branchIndex} className="mb-6 last:mb-0">
|
<div key={branchIndex} className="mb-6 last:mb-0">
|
||||||
{/* Branch Header - Clickable */}
|
{/* Branch Header - Clickable */}
|
||||||
@ -772,20 +869,18 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
...prev,
|
...prev,
|
||||||
[branchKey]: !prev[branchKey]
|
[branchKey]: !prev[branchKey]
|
||||||
}))}
|
}))}
|
||||||
className={`w-full flex items-center gap-3 p-4 rounded-lg border-2 transition-all hover:shadow-md ${
|
className={`w-full flex items-center gap-3 p-4 rounded-lg border-2 transition-all hover:shadow-md ${branchColor === 'blue'
|
||||||
branchColor === 'blue'
|
? 'border-blue-300 bg-blue-50 hover:bg-blue-100'
|
||||||
? 'border-blue-300 bg-blue-50 hover:bg-blue-100'
|
: 'border-green-300 bg-green-50 hover:bg-green-100'
|
||||||
: 'border-green-300 bg-green-50 hover:bg-green-100'
|
}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{isExpanded ? (
|
{isExpanded ? (
|
||||||
<ChevronDown className={`w-5 h-5 ${branchColor === 'blue' ? 'text-blue-600' : 'text-green-600'}`} />
|
<ChevronDown className={`w-5 h-5 ${branchColor === 'blue' ? 'text-blue-600' : 'text-green-600'}`} />
|
||||||
) : (
|
) : (
|
||||||
<ChevronRight className={`w-5 h-5 ${branchColor === 'blue' ? 'text-blue-600' : 'text-green-600'}`} />
|
<ChevronRight className={`w-5 h-5 ${branchColor === 'blue' ? 'text-blue-600' : 'text-green-600'}`} />
|
||||||
)}
|
)}
|
||||||
<div className={`w-8 h-8 rounded-full flex items-center justify-center ${
|
<div className={`w-8 h-8 rounded-full flex items-center justify-center ${branchColor === 'blue' ? 'bg-blue-200' : 'bg-green-200'
|
||||||
branchColor === 'blue' ? 'bg-blue-200' : 'bg-green-200'
|
}`}>
|
||||||
}`}>
|
|
||||||
<GitBranch className={`w-4 h-4 ${branchColor === 'blue' ? 'text-blue-700' : 'text-green-700'}`} />
|
<GitBranch className={`w-4 h-4 ${branchColor === 'blue' ? 'text-blue-700' : 'text-green-700'}`} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 text-left">
|
<div className="flex-1 text-left">
|
||||||
@ -805,13 +900,12 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
<div key={branchStage.id} className="relative">
|
<div key={branchStage.id} className="relative">
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className={`w-8 h-8 rounded-full flex items-center justify-center border-2 ${
|
<div className={`w-8 h-8 rounded-full flex items-center justify-center border-2 ${branchStage.status === 'completed'
|
||||||
branchStage.status === 'completed'
|
? `${branchColor === 'blue' ? 'bg-blue-500 border-blue-500' : 'bg-green-500 border-green-500'}`
|
||||||
? `${branchColor === 'blue' ? 'bg-blue-500 border-blue-500' : 'bg-green-500 border-green-500'}`
|
: branchStage.status === 'active'
|
||||||
: branchStage.status === 'active'
|
|
||||||
? 'bg-amber-500 border-amber-500'
|
? 'bg-amber-500 border-amber-500'
|
||||||
: 'bg-slate-200 border-slate-300'
|
: 'bg-slate-200 border-slate-300'
|
||||||
}`}>
|
}`}>
|
||||||
{branchStage.status === 'completed' && (
|
{branchStage.status === 'completed' && (
|
||||||
<CheckCircle className="w-4 h-4 text-white" />
|
<CheckCircle className="w-4 h-4 text-white" />
|
||||||
)}
|
)}
|
||||||
@ -829,7 +923,7 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
<p className="text-slate-600 text-sm mt-0.5">{branchStage.description}</p>
|
<p className="text-slate-600 text-sm mt-0.5">{branchStage.description}</p>
|
||||||
)}
|
)}
|
||||||
{branchStage.documentsUploaded !== undefined && branchStage.documentsUploaded > 0 && (
|
{branchStage.documentsUploaded !== undefined && branchStage.documentsUploaded > 0 && (
|
||||||
<p
|
<p
|
||||||
className="text-blue-600 text-sm mt-0.5 cursor-pointer hover:underline hover:text-blue-700"
|
className="text-blue-600 text-sm mt-0.5 cursor-pointer hover:underline hover:text-blue-700"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedStage(branchStage.name);
|
setSelectedStage(branchStage.name);
|
||||||
@ -971,7 +1065,7 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
{/* Payments Tab */}
|
{/* Payments Tab */}
|
||||||
<TabsContent value="payments" className="space-y-4">
|
<TabsContent value="payments" className="space-y-4">
|
||||||
<h3 className="text-slate-900 mb-4">Payment Information</h3>
|
<h3 className="text-slate-900 mb-4">Payment Information</h3>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="p-4 border border-slate-200 rounded-lg">
|
<div className="p-4 border border-slate-200 rounded-lg">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
@ -1044,7 +1138,7 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
<div>
|
<div>
|
||||||
<p className="text-slate-600">Rank</p>
|
<p className="text-slate-600">Rank</p>
|
||||||
<p className="text-slate-900">
|
<p className="text-slate-900">
|
||||||
{application.rank} of {application.totalApplicantsAtLocation}
|
{application.rank} of {application.totalApplicantsAtLocation}
|
||||||
<span className="text-slate-500"> in {application.preferredLocation}</span>
|
<span className="text-slate-500"> in {application.preferredLocation}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -1074,7 +1168,7 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
<CardTitle>Actions</CardTitle>
|
<CardTitle>Actions</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
<Button
|
<Button
|
||||||
className="w-full bg-green-600 hover:bg-green-700"
|
className="w-full bg-green-600 hover:bg-green-700"
|
||||||
onClick={() => setShowApproveModal(true)}
|
onClick={() => setShowApproveModal(true)}
|
||||||
>
|
>
|
||||||
@ -1082,8 +1176,8 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
Approve
|
Approve
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
onClick={() => setShowRejectModal(true)}
|
onClick={() => setShowRejectModal(true)}
|
||||||
>
|
>
|
||||||
@ -1093,8 +1187,8 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
onClick={() => setShowWorkNotesPage(true)}
|
onClick={() => setShowWorkNotesPage(true)}
|
||||||
>
|
>
|
||||||
@ -1102,8 +1196,8 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
Work Note
|
Work Note
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
onClick={() => setShowScheduleModal(true)}
|
onClick={() => setShowScheduleModal(true)}
|
||||||
>
|
>
|
||||||
@ -1212,14 +1306,14 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
<Input type="file" className="mt-2" />
|
<Input type="file" className="mt-2" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
onClick={() => setShowApproveModal(false)}
|
onClick={() => setShowApproveModal(false)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex-1 bg-green-600 hover:bg-green-700"
|
className="flex-1 bg-green-600 hover:bg-green-700"
|
||||||
onClick={handleApprove}
|
onClick={handleApprove}
|
||||||
>
|
>
|
||||||
@ -1251,14 +1345,14 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
onClick={() => setShowRejectModal(false)}
|
onClick={() => setShowRejectModal(false)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
onClick={handleReject}
|
onClick={handleReject}
|
||||||
@ -1295,14 +1389,14 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
<Input type="file" className="mt-2" multiple />
|
<Input type="file" className="mt-2" multiple />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
onClick={() => setShowWorkNoteModal(false)}
|
onClick={() => setShowWorkNoteModal(false)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex-1 bg-amber-600 hover:bg-amber-700"
|
className="flex-1 bg-amber-600 hover:bg-amber-700"
|
||||||
onClick={handleWorkNote}
|
onClick={handleWorkNote}
|
||||||
>
|
>
|
||||||
@ -1369,14 +1463,14 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
onClick={() => setShowScheduleModal(false)}
|
onClick={() => setShowScheduleModal(false)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex-1 bg-amber-600 hover:bg-amber-700"
|
className="flex-1 bg-amber-600 hover:bg-amber-700"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
alert('Interview scheduled! Calendar invites will be sent.');
|
alert('Interview scheduled! Calendar invites will be sent.');
|
||||||
@ -1629,14 +1723,14 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
onClick={() => setShowKTMatrixModal(false)}
|
onClick={() => setShowKTMatrixModal(false)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex-1 bg-amber-600 hover:bg-amber-700"
|
className="flex-1 bg-amber-600 hover:bg-amber-700"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
alert('KT Matrix submitted successfully!');
|
alert('KT Matrix submitted successfully!');
|
||||||
@ -1757,14 +1851,14 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
onClick={() => setShowLevel2FeedbackModal(false)}
|
onClick={() => setShowLevel2FeedbackModal(false)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex-1 bg-blue-600 hover:bg-blue-700"
|
className="flex-1 bg-blue-600 hover:bg-blue-700"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
alert('Level 2 feedback submitted successfully!');
|
alert('Level 2 feedback submitted successfully!');
|
||||||
@ -1894,14 +1988,14 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
onClick={() => setShowLevel3FeedbackModal(false)}
|
onClick={() => setShowLevel3FeedbackModal(false)}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex-1 bg-purple-600 hover:bg-purple-700"
|
className="flex-1 bg-purple-600 hover:bg-purple-700"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
alert('Level 3 feedback submitted successfully!');
|
alert('Level 3 feedback submitted successfully!');
|
||||||
@ -1951,8 +2045,8 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
<TableCell>{new Date(doc.uploadDate).toLocaleDateString()}</TableCell>
|
<TableCell>{new Date(doc.uploadDate).toLocaleDateString()}</TableCell>
|
||||||
<TableCell>{doc.uploader}</TableCell>
|
<TableCell>{doc.uploader}</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => alert(`Downloading ${doc.name}`)}
|
onClick={() => alert(`Downloading ${doc.name}`)}
|
||||||
>
|
>
|
||||||
@ -1975,14 +2069,14 @@ export function ApplicationDetails({ applicationId, onBack }: ApplicationDetails
|
|||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
>
|
>
|
||||||
<Upload className="w-4 h-4 mr-2" />
|
<Upload className="w-4 h-4 mr-2" />
|
||||||
Upload Document
|
Upload Document
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => setShowDocumentsModal(false)}
|
onClick={() => setShowDocumentsModal(false)}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { mockApplications, locations, ApplicationStatus } from '../../lib/mock-data';
|
import { mockApplications, locations, ApplicationStatus, Application } from '../../lib/mock-data';
|
||||||
|
import { onboardingService } from '../../services/onboarding.service';
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
import { Input } from '../ui/input';
|
import { Input } from '../ui/input';
|
||||||
import {
|
import {
|
||||||
@ -9,10 +10,10 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '../ui/select';
|
} from '../ui/select';
|
||||||
import {
|
import {
|
||||||
Search,
|
Search,
|
||||||
Filter,
|
Filter,
|
||||||
Download,
|
Download,
|
||||||
Mail,
|
Mail,
|
||||||
Plus
|
Plus
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
@ -44,22 +45,78 @@ export function ApplicationsPage({ onViewDetails, initialFilter }: ApplicationsP
|
|||||||
const [sortBy, setSortBy] = useState<'date'>('date');
|
const [sortBy, setSortBy] = useState<'date'>('date');
|
||||||
const [showNewApplicationModal, setShowNewApplicationModal] = useState(false);
|
const [showNewApplicationModal, setShowNewApplicationModal] = useState(false);
|
||||||
|
|
||||||
|
// Real Data Integration
|
||||||
|
const [applications, setApplications] = useState<Application[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchApplications = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const response = await onboardingService.getApplications();
|
||||||
|
// Check if response is array or wrapped in data property
|
||||||
|
const applicationsData = response.data || (Array.isArray(response) ? response : []);
|
||||||
|
|
||||||
|
// Map backend data to frontend Application interface
|
||||||
|
const mappedApps = applicationsData.map((app: any) => ({
|
||||||
|
id: app.id,
|
||||||
|
registrationNumber: app.applicationId || 'N/A',
|
||||||
|
name: app.applicantName,
|
||||||
|
email: app.email,
|
||||||
|
phone: app.phone,
|
||||||
|
age: app.age,
|
||||||
|
education: app.education,
|
||||||
|
residentialAddress: app.address || app.city || '',
|
||||||
|
businessAddress: app.address || '',
|
||||||
|
preferredLocation: app.preferredLocation,
|
||||||
|
state: app.state,
|
||||||
|
ownsBike: app.ownRoyalEnfield === 'yes',
|
||||||
|
pastExperience: app.experienceYears ? `${app.experienceYears} years` : (app.description || ''),
|
||||||
|
status: app.overallStatus as ApplicationStatus,
|
||||||
|
questionnaireMarks: 0,
|
||||||
|
rank: 0,
|
||||||
|
totalApplicantsAtLocation: 0,
|
||||||
|
submissionDate: app.createdAt,
|
||||||
|
assignedUsers: [],
|
||||||
|
progress: app.progressPercentage || 0,
|
||||||
|
isShortlisted: true, // Show all for admin view
|
||||||
|
// Add other fields to match interface
|
||||||
|
companyName: app.companyName,
|
||||||
|
source: app.source,
|
||||||
|
existingDealer: app.existingDealer,
|
||||||
|
royalEnfieldModel: app.royalEnfieldModel,
|
||||||
|
description: app.description,
|
||||||
|
pincode: app.pincode,
|
||||||
|
locationType: app.locationType,
|
||||||
|
ownRoyalEnfield: app.ownRoyalEnfield,
|
||||||
|
address: app.address
|
||||||
|
}));
|
||||||
|
setApplications(mappedApps);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch applications', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchApplications();
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Filter and sort applications - ONLY show shortlisted applications
|
// Filter and sort applications - ONLY show shortlisted applications
|
||||||
// Exclude specific applications (APP-005, APP-006, APP-007, APP-008) from Dealership Requests page
|
// Exclude specific applications (APP-005, APP-006, APP-007, APP-008) from Dealership Requests page
|
||||||
const excludedApplicationIds = ['5', '6', '7', '8'];
|
const excludedApplicationIds = ['5', '6', '7', '8'];
|
||||||
|
|
||||||
const filteredApplications = mockApplications
|
const filteredApplications = applications
|
||||||
.filter((app) => {
|
.filter((app) => {
|
||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
app.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
app.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
app.registrationNumber.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
app.registrationNumber.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
app.email.toLowerCase().includes(searchQuery.toLowerCase());
|
app.email.toLowerCase().includes(searchQuery.toLowerCase());
|
||||||
|
|
||||||
const matchesLocation = locationFilter === 'all' || app.preferredLocation === locationFilter;
|
const matchesLocation = locationFilter === 'all' || app.preferredLocation === locationFilter;
|
||||||
const matchesStatus = statusFilter === 'all' || app.status === statusFilter;
|
const matchesStatus = statusFilter === 'all' || app.status === statusFilter;
|
||||||
const isShortlisted = app.isShortlisted === true; // Only show shortlisted applications
|
const isShortlisted = app.isShortlisted === true; // Only show shortlisted applications
|
||||||
const notExcluded = !excludedApplicationIds.includes(app.id); // Exclude APP-005, 006, 007, 008
|
const notExcluded = !excludedApplicationIds.includes(app.id); // Exclude APP-005, 006, 007, 008
|
||||||
|
|
||||||
return matchesSearch && matchesLocation && matchesStatus && isShortlisted && notExcluded;
|
return matchesSearch && matchesLocation && matchesStatus && isShortlisted && notExcluded;
|
||||||
})
|
})
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
@ -70,7 +127,7 @@ export function ApplicationsPage({ onViewDetails, initialFilter }: ApplicationsP
|
|||||||
});
|
});
|
||||||
|
|
||||||
const toggleSelection = (id: string) => {
|
const toggleSelection = (id: string) => {
|
||||||
setSelectedIds(prev =>
|
setSelectedIds(prev =>
|
||||||
prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id]
|
prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -134,7 +191,7 @@ export function ApplicationsPage({ onViewDetails, initialFilter }: ApplicationsP
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Info Banner - Only visible for DD users */}
|
{/* Info Banner - Only visible for DD users */}
|
||||||
{/* Note: This page shows only applications that have been shortlisted */}
|
{/* Note: This page shows only applications that have been shortlisted */}
|
||||||
|
|
||||||
{/* Filters and Actions Bar */}
|
{/* Filters and Actions Bar */}
|
||||||
<div className="bg-white rounded-lg border border-slate-200 p-4">
|
<div className="bg-white rounded-lg border border-slate-200 p-4">
|
||||||
<div className="flex flex-col lg:flex-row gap-4">
|
<div className="flex flex-col lg:flex-row gap-4">
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
173
src/components/dealer/QuestionnaireForm.tsx
Normal file
173
src/components/dealer/QuestionnaireForm.tsx
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { API } from '../../api/API';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
interface Question {
|
||||||
|
id: string;
|
||||||
|
sectionName: string;
|
||||||
|
questionText: string;
|
||||||
|
inputType: 'text' | 'yesno' | 'file' | 'number';
|
||||||
|
options?: any;
|
||||||
|
weight: number;
|
||||||
|
order: number;
|
||||||
|
isMandatory: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QuestionnaireFormProps {
|
||||||
|
applicationId: string;
|
||||||
|
onComplete?: () => void;
|
||||||
|
readOnly?: boolean;
|
||||||
|
existingResponses?: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const QuestionnaireForm: React.FC<QuestionnaireFormProps> = ({ applicationId, onComplete, readOnly = false, existingResponses }) => {
|
||||||
|
const [questions, setQuestions] = useState<Question[]>([]);
|
||||||
|
const [responses, setResponses] = useState<Record<string, any>>({});
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchQuestionnaire();
|
||||||
|
if (existingResponses) {
|
||||||
|
const initialResponses: any = {};
|
||||||
|
existingResponses.forEach(r => {
|
||||||
|
initialResponses[r.questionId] = r.responseValue;
|
||||||
|
});
|
||||||
|
setResponses(initialResponses);
|
||||||
|
}
|
||||||
|
}, [existingResponses]);
|
||||||
|
|
||||||
|
const fetchQuestionnaire = async () => {
|
||||||
|
try {
|
||||||
|
const res = await API.getLatestQuestionnaire();
|
||||||
|
if (res.data && res.data.data && res.data.data.questions) {
|
||||||
|
setQuestions(res.data.data.questions);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error('Failed to load questionnaire');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (questionId: string, value: any) => {
|
||||||
|
if (readOnly) return;
|
||||||
|
setResponses(prev => ({ ...prev, [questionId]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
const missing = questions.filter(q => q.isMandatory && !responses[q.id]);
|
||||||
|
if (missing.length > 0) {
|
||||||
|
toast.error(`Please answer all mandatory questions. Missing: ${missing.length}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setSubmitting(true);
|
||||||
|
const payload = Object.entries(responses).map(([qId, val]) => ({
|
||||||
|
questionId: qId,
|
||||||
|
value: val
|
||||||
|
}));
|
||||||
|
|
||||||
|
await API.submitQuestionnaireResponse({
|
||||||
|
applicationId,
|
||||||
|
responses: payload
|
||||||
|
});
|
||||||
|
toast.success('Responses submitted successfully');
|
||||||
|
if (onComplete) onComplete();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error('Failed to submit responses');
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) return <div>Loading questionnaire...</div>;
|
||||||
|
if (questions.length === 0) return <div>No active questionnaire found.</div>;
|
||||||
|
|
||||||
|
const sections = questions.reduce((acc, q) => {
|
||||||
|
if (!acc[q.sectionName]) acc[q.sectionName] = [];
|
||||||
|
acc[q.sectionName].push(q);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, Question[]>);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<h3 className="text-xl font-semibold">Dealership Assessment Questionnaire</h3>
|
||||||
|
|
||||||
|
{Object.entries(sections).map(([sectionName, sectionQuestions]) => (
|
||||||
|
<div key={sectionName} className="border p-4 rounded bg-white shadow-sm">
|
||||||
|
<h4 className="font-medium text-lg mb-4 border-b pb-2">{sectionName}</h4>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{sectionQuestions.map(q => (
|
||||||
|
<div key={q.id}>
|
||||||
|
<label className="block text-sm font-medium mb-1">
|
||||||
|
{q.questionText} {q.isMandatory && !readOnly && <span className="text-red-500">*</span>}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{q.inputType === 'text' && (
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-full border p-2 rounded disabled:bg-gray-100"
|
||||||
|
onChange={(e) => handleInputChange(q.id, e.target.value)}
|
||||||
|
value={responses[q.id] || ''}
|
||||||
|
disabled={readOnly}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{q.inputType === 'number' && (
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="w-full border p-2 rounded disabled:bg-gray-100"
|
||||||
|
onChange={(e) => handleInputChange(q.id, e.target.value)}
|
||||||
|
value={responses[q.id] || ''}
|
||||||
|
disabled={readOnly}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{q.inputType === 'yesno' && (
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<label className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name={`q-${q.id}`}
|
||||||
|
value="yes"
|
||||||
|
checked={responses[q.id] === 'yes'}
|
||||||
|
onChange={() => handleInputChange(q.id, 'yes')}
|
||||||
|
disabled={readOnly}
|
||||||
|
/> Yes
|
||||||
|
</label>
|
||||||
|
<label className="flex items-center gap-2">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name={`q-${q.id}`}
|
||||||
|
value="no"
|
||||||
|
checked={responses[q.id] === 'no'}
|
||||||
|
onChange={() => handleInputChange(q.id, 'no')}
|
||||||
|
disabled={readOnly}
|
||||||
|
/> No
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{!readOnly && (
|
||||||
|
<button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={submitting}
|
||||||
|
className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700 w-full md:w-auto"
|
||||||
|
>
|
||||||
|
{submitting ? 'Submitting...' : 'Submit Assessment'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QuestionnaireForm;
|
||||||
@ -16,18 +16,20 @@ import {
|
|||||||
MapPin
|
MapPin
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { RootState } from '../../store';
|
import { RootState } from '../../store';
|
||||||
import { Input } from '../ui/input';
|
import { Input } from '../ui/input';
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
activeView: string;
|
|
||||||
onNavigate: (view: string) => void;
|
|
||||||
onLogout: () => void;
|
onLogout: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Sidebar({ activeView, onNavigate, onLogout }: SidebarProps) {
|
export function Sidebar({ onLogout }: SidebarProps) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
const activeView = location.pathname.substring(1) || 'dashboard'; // Simple mapping for now
|
||||||
const { user: currentUser } = useSelector((state: RootState) => state.auth);
|
const { user: currentUser } = useSelector((state: RootState) => state.auth);
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
@ -96,7 +98,7 @@ export function Sidebar({ activeView, onNavigate, onLogout }: SidebarProps) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (searchQuery.trim()) {
|
if (searchQuery.trim()) {
|
||||||
// Navigate to applications with search query
|
// Navigate to applications with search query
|
||||||
onNavigate('applications');
|
navigate('/applications');
|
||||||
// In real app, would pass search query
|
// In real app, would pass search query
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -173,7 +175,7 @@ export function Sidebar({ activeView, onNavigate, onLogout }: SidebarProps) {
|
|||||||
setAllRequestsExpanded(!allRequestsExpanded);
|
setAllRequestsExpanded(!allRequestsExpanded);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onNavigate(item.id);
|
navigate(`/${item.id}`);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className={`w-full flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive || isSubmenuActive
|
className={`w-full flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive || isSubmenuActive
|
||||||
@ -205,7 +207,7 @@ export function Sidebar({ activeView, onNavigate, onLogout }: SidebarProps) {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={subItem.id}
|
key={subItem.id}
|
||||||
onClick={() => onNavigate(subItem.id)}
|
onClick={() => navigate(`/${subItem.id}`)}
|
||||||
className={`w-full flex items-center gap-3 px-4 py-2 rounded-lg transition-colors text-sm ${isSubActive
|
className={`w-full flex items-center gap-3 px-4 py-2 rounded-lg transition-colors text-sm ${isSubActive
|
||||||
? 'bg-amber-600 text-white'
|
? 'bg-amber-600 text-white'
|
||||||
: 'text-slate-400 hover:bg-slate-800 hover:text-white'
|
: 'text-slate-400 hover:bg-slate-800 hover:text-white'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
import { Input } from '../ui/input';
|
import { Input } from '../ui/input';
|
||||||
import { Label } from '../ui/label';
|
import { Label } from '../ui/label';
|
||||||
@ -9,6 +9,8 @@ import { Checkbox } from '../ui/checkbox';
|
|||||||
import { CheckCircle, Users, Star, Shield, LogIn, Award, TrendingUp, Handshake } from 'lucide-react';
|
import { CheckCircle, Users, Star, Shield, LogIn, Award, TrendingUp, Handshake } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
// import backgroundImage from 'figma:asset/ee01d864b6e23a8197b42f3168c98eedec9d2440.png';
|
// import backgroundImage from 'figma:asset/ee01d864b6e23a8197b42f3168c98eedec9d2440.png';
|
||||||
|
import { onboardingService } from '../../services/onboarding.service';
|
||||||
|
import { masterService } from '../../services/master.service';
|
||||||
|
|
||||||
interface ApplicationFormPageProps {
|
interface ApplicationFormPageProps {
|
||||||
onAdminLogin: () => void;
|
onAdminLogin: () => void;
|
||||||
@ -16,7 +18,7 @@ interface ApplicationFormPageProps {
|
|||||||
|
|
||||||
export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps) {
|
export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps) {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
country: '',
|
country: 'India', // Default to India
|
||||||
state: '',
|
state: '',
|
||||||
district: '',
|
district: '',
|
||||||
name: '',
|
name: '',
|
||||||
@ -36,7 +38,54 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps)
|
|||||||
acceptTerms: false
|
acceptTerms: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const [states, setStates] = useState<any[]>([]);
|
||||||
|
const [districts, setDistricts] = useState<any[]>([]);
|
||||||
|
const [fetchingStates, setFetchingStates] = useState(false);
|
||||||
|
const [fetchingDistricts, setFetchingDistricts] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchStates();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchStates = async () => {
|
||||||
|
setFetchingStates(true);
|
||||||
|
try {
|
||||||
|
// ZoneID is optional, public API should return all states if no zone override
|
||||||
|
const response: any = await masterService.getStates();
|
||||||
|
if (response && response.states) {
|
||||||
|
setStates(response.states);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching states:', error);
|
||||||
|
toast.error('Failed to load states. Please refresh the page.');
|
||||||
|
} finally {
|
||||||
|
setFetchingStates(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStateChange = async (selectedState: any) => {
|
||||||
|
if (!selectedState) return;
|
||||||
|
|
||||||
|
setFormData(prev => ({ ...prev, state: selectedState.stateName, district: '' }));
|
||||||
|
setDistricts([]);
|
||||||
|
setFetchingDistricts(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response: any = await masterService.getDistricts(selectedState.id);
|
||||||
|
if (response && response.districts) {
|
||||||
|
setDistricts(response.districts);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching districts:', error);
|
||||||
|
toast.error('Failed to load districts.');
|
||||||
|
} finally {
|
||||||
|
setFetchingDistricts(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
@ -49,42 +98,58 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate Royal Enfield model if they own one
|
// Validate Royal Enfield model
|
||||||
if (formData.ownRoyalEnfield === 'yes' && !formData.royalEnfieldModel) {
|
if (formData.ownRoyalEnfield === 'yes' && !formData.royalEnfieldModel) {
|
||||||
toast.error('Please select your Royal Enfield model');
|
toast.error('Please select your Royal Enfield model');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate terms acceptance
|
|
||||||
if (!formData.acceptTerms) {
|
if (!formData.acceptTerms) {
|
||||||
toast.error('Please accept the terms and conditions to continue');
|
toast.error('Please accept the terms and conditions');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Success message
|
try {
|
||||||
toast.success('Application submitted successfully! We will contact you soon.');
|
// Map form data to backend expected format
|
||||||
|
const payload = {
|
||||||
|
applicantName: formData.name,
|
||||||
|
email: formData.email,
|
||||||
|
phone: formData.mobile,
|
||||||
|
state: formData.state,
|
||||||
|
city: formData.interestedCity, // Or district?
|
||||||
|
district: formData.district, // Crucial for auto-assignment
|
||||||
|
preferredLocation: `${formData.interestedCity}, ${formData.state}`,
|
||||||
|
businessType: 'Dealership', // Default or derived
|
||||||
|
locationType: 'Urban', // Default or need field
|
||||||
|
address: formData.address, // Need backend support?
|
||||||
|
pincode: formData.pincode, // Need backend support?
|
||||||
|
age: formData.age,
|
||||||
|
education: formData.education,
|
||||||
|
companyName: formData.companyName,
|
||||||
|
source: formData.source,
|
||||||
|
existingDealer: formData.existingDealer,
|
||||||
|
ownRoyalEnfield: formData.ownRoyalEnfield,
|
||||||
|
royalEnfieldModel: formData.royalEnfieldModel,
|
||||||
|
description: formData.description,
|
||||||
|
// flexible fields for now
|
||||||
|
experienceYears: 0, // Not in form
|
||||||
|
investmentCapacity: 'Unknown' // Not in form
|
||||||
|
};
|
||||||
|
|
||||||
// Reset form
|
await onboardingService.submitApplication(payload);
|
||||||
setFormData({
|
toast.success('Application submitted successfully! We will contact you soon.');
|
||||||
country: '',
|
|
||||||
state: '',
|
// Reset form
|
||||||
district: '',
|
setFormData({
|
||||||
name: '',
|
country: '', state: '', district: '', name: '', interestedCity: '',
|
||||||
interestedCity: '',
|
email: '', pincode: '', mobile: '', ownRoyalEnfield: '', royalEnfieldModel: '',
|
||||||
email: '',
|
age: '', education: '', companyName: '', source: '', existingDealer: '',
|
||||||
pincode: '',
|
description: '', address: '', acceptTerms: false
|
||||||
mobile: '',
|
});
|
||||||
ownRoyalEnfield: '',
|
|
||||||
royalEnfieldModel: '',
|
} catch (error: any) {
|
||||||
age: '',
|
toast.error(error.response?.data?.message || 'Failed to submit application.');
|
||||||
education: '',
|
}
|
||||||
companyName: '',
|
|
||||||
source: '',
|
|
||||||
existingDealer: '',
|
|
||||||
description: '',
|
|
||||||
address: '',
|
|
||||||
acceptTerms: false
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -215,11 +280,6 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps)
|
|||||||
<section className="relative py-24 overflow-hidden">
|
<section className="relative py-24 overflow-hidden">
|
||||||
{/* Background Image with Overlay */}
|
{/* Background Image with Overlay */}
|
||||||
<div className="absolute inset-0">
|
<div className="absolute inset-0">
|
||||||
{/* <img
|
|
||||||
src={backgroundImage}
|
|
||||||
alt="Royal Enfield Showroom"
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/> */}
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-slate-950/95 via-slate-900/90 to-slate-950/95 backdrop-blur-sm"></div>
|
<div className="absolute inset-0 bg-gradient-to-br from-slate-950/95 via-slate-900/90 to-slate-950/95 backdrop-blur-sm"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -312,15 +372,15 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps)
|
|||||||
<span className="text-amber-400">🌍</span>
|
<span className="text-amber-400">🌍</span>
|
||||||
Country <span className="text-amber-500">*</span>
|
Country <span className="text-amber-500">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Select value={formData.country} onValueChange={(value) => setFormData({ ...formData, country: value })}>
|
<Select value={formData.country} onValueChange={(value) => setFormData({ ...formData, country: value })} disabled>
|
||||||
<SelectTrigger className="bg-slate-800/50 border-slate-600/50 text-white focus:border-amber-500/50 focus:ring-amber-500/20">
|
<SelectTrigger className="bg-slate-800/50 border-slate-600/50 text-white focus:border-amber-500/50 focus:ring-amber-500/20 disabled:opacity-70 disabled:cursor-not-allowed">
|
||||||
<SelectValue placeholder="Select country" />
|
<SelectValue placeholder="Select country" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="bg-slate-800 border-slate-700 text-white">
|
<SelectContent className="bg-slate-800 border-slate-700 text-white">
|
||||||
<SelectItem value="india">India</SelectItem>
|
<SelectItem value="India">India</SelectItem>
|
||||||
<SelectItem value="nepal">Nepal</SelectItem>
|
<SelectItem value="Nepal">Nepal</SelectItem>
|
||||||
<SelectItem value="bangladesh">Bangladesh</SelectItem>
|
<SelectItem value="Bangladesh">Bangladesh</SelectItem>
|
||||||
<SelectItem value="sri-lanka">Sri Lanka</SelectItem>
|
<SelectItem value="Sri Lanka">Sri Lanka</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
@ -329,26 +389,23 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps)
|
|||||||
<span className="text-amber-400">🏛️</span>
|
<span className="text-amber-400">🏛️</span>
|
||||||
State <span className="text-amber-500">*</span>
|
State <span className="text-amber-500">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Select value={formData.state} onValueChange={(value) => setFormData({ ...formData, state: value })}>
|
<Select
|
||||||
|
value={formData.state}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
const selectedState = states.find((s: any) => s.stateName === value);
|
||||||
|
handleStateChange(selectedState);
|
||||||
|
}}
|
||||||
|
disabled={fetchingStates}
|
||||||
|
>
|
||||||
<SelectTrigger className="bg-slate-800/50 border-slate-600/50 text-white focus:border-amber-500/50 focus:ring-amber-500/20">
|
<SelectTrigger className="bg-slate-800/50 border-slate-600/50 text-white focus:border-amber-500/50 focus:ring-amber-500/20">
|
||||||
<SelectValue placeholder="Select state" />
|
<SelectValue placeholder={fetchingStates ? "Loading states..." : "Select state"} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="bg-slate-800 border-slate-700 text-white">
|
<SelectContent className="bg-slate-800 border-slate-700 text-white h-64">
|
||||||
<SelectItem value="maharashtra">Maharashtra</SelectItem>
|
{states.map((state: any) => (
|
||||||
<SelectItem value="karnataka">Karnataka</SelectItem>
|
<SelectItem key={state.id} value={state.stateName}>
|
||||||
<SelectItem value="tamil-nadu">Tamil Nadu</SelectItem>
|
{state.stateName}
|
||||||
<SelectItem value="delhi">Delhi</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value="rajasthan">Rajasthan</SelectItem>
|
))}
|
||||||
<SelectItem value="uttar-pradesh">Uttar Pradesh</SelectItem>
|
|
||||||
<SelectItem value="gujarat">Gujarat</SelectItem>
|
|
||||||
<SelectItem value="west-bengal">West Bengal</SelectItem>
|
|
||||||
<SelectItem value="andhra-pradesh">Andhra Pradesh</SelectItem>
|
|
||||||
<SelectItem value="telangana">Telangana</SelectItem>
|
|
||||||
<SelectItem value="kerala">Kerala</SelectItem>
|
|
||||||
<SelectItem value="punjab">Punjab</SelectItem>
|
|
||||||
<SelectItem value="haryana">Haryana</SelectItem>
|
|
||||||
<SelectItem value="madhya-pradesh">Madhya Pradesh</SelectItem>
|
|
||||||
<SelectItem value="odisha">Odisha</SelectItem>
|
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
@ -357,14 +414,22 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps)
|
|||||||
<span className="text-amber-400">📍</span>
|
<span className="text-amber-400">📍</span>
|
||||||
District <span className="text-amber-500">*</span>
|
District <span className="text-amber-500">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Select
|
||||||
id="district"
|
|
||||||
placeholder="Enter district"
|
|
||||||
value={formData.district}
|
value={formData.district}
|
||||||
onChange={(e) => setFormData({ ...formData, district: e.target.value })}
|
onValueChange={(value) => setFormData({ ...formData, district: value })}
|
||||||
className="bg-slate-800/50 border-slate-600/50 text-white placeholder:text-slate-500 focus:border-amber-500/50 focus:ring-amber-500/20"
|
disabled={!formData.state || fetchingDistricts}
|
||||||
required
|
>
|
||||||
/>
|
<SelectTrigger className="bg-slate-800/50 border-slate-600/50 text-white focus:border-amber-500/50 focus:ring-amber-500/20">
|
||||||
|
<SelectValue placeholder={fetchingDistricts ? "Loading districts..." : "Select district"} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent className="bg-slate-800 border-slate-700 text-white h-64">
|
||||||
|
{districts.map((district: any) => (
|
||||||
|
<SelectItem key={district.id} value={district.districtName}>
|
||||||
|
{district.districtName}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -61,12 +61,21 @@ export interface Application {
|
|||||||
email: string;
|
email: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
age: number;
|
age: number;
|
||||||
education: 'Graduate' | 'Undergraduate' | 'Postgraduate';
|
education: string;
|
||||||
residentialAddress: string;
|
residentialAddress: string;
|
||||||
businessAddress: string;
|
businessAddress: string;
|
||||||
preferredLocation: string;
|
preferredLocation: string;
|
||||||
state: string;
|
state: string;
|
||||||
ownsBike: boolean;
|
ownsBike?: boolean;
|
||||||
|
ownRoyalEnfield?: string;
|
||||||
|
royalEnfieldModel?: string;
|
||||||
|
existingDealer?: string;
|
||||||
|
companyName?: string;
|
||||||
|
source?: string;
|
||||||
|
description?: string;
|
||||||
|
address?: string;
|
||||||
|
pincode?: string;
|
||||||
|
locationType?: string;
|
||||||
pastExperience: string;
|
pastExperience: string;
|
||||||
status: ApplicationStatus;
|
status: ApplicationStatus;
|
||||||
questionnaireMarks?: number;
|
questionnaireMarks?: number;
|
||||||
|
|||||||
@ -5,10 +5,14 @@ import { store } from './store'
|
|||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
import './styles/globals.css'
|
import './styles/globals.css'
|
||||||
|
|
||||||
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<App />
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
</Provider>
|
</Provider>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -13,13 +13,27 @@ export const adminService = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async createUser(userData: any) {
|
||||||
|
try {
|
||||||
|
const response = await API.createUser(userData) as any;
|
||||||
|
if (response.ok && response.data?.success) {
|
||||||
|
// Toast handled in component
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error creating user:', error);
|
||||||
|
toast.error(error.response?.data?.message || 'Failed to create user');
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async updateUser(id: string, userData: any) {
|
async updateUser(id: string, userData: any) {
|
||||||
try {
|
try {
|
||||||
const response = await API.updateUser(id, userData) as any;
|
const response = await API.updateUser(id, userData) as any;
|
||||||
if (response.success) {
|
if (response.ok && response.data?.success) {
|
||||||
toast.success(response.message || 'User updated successfully');
|
toast.success(response.data.message || 'User updated successfully');
|
||||||
}
|
}
|
||||||
return response;
|
return response.data;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error updating user:', error);
|
console.error('Error updating user:', error);
|
||||||
toast.error(error.response?.data?.message || 'Failed to update user');
|
toast.error(error.response?.data?.message || 'Failed to update user');
|
||||||
@ -30,10 +44,10 @@ export const adminService = {
|
|||||||
async updateUserStatus(id: string, status: string, isActive: boolean) {
|
async updateUserStatus(id: string, status: string, isActive: boolean) {
|
||||||
try {
|
try {
|
||||||
const response = await API.updateUserStatus(id, { status, isActive }) as any;
|
const response = await API.updateUserStatus(id, { status, isActive }) as any;
|
||||||
if (response.success) {
|
if (response.ok && response.data?.success) {
|
||||||
toast.success(response.message || 'User status updated');
|
toast.success(response.data.message || 'User status updated');
|
||||||
}
|
}
|
||||||
return response;
|
return response.data;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error updating status:', error);
|
console.error('Error updating status:', error);
|
||||||
toast.error(error.response?.data?.message || 'Failed to update status');
|
toast.error(error.response?.data?.message || 'Failed to update status');
|
||||||
|
|||||||
@ -20,6 +20,18 @@ export const masterService = {
|
|||||||
const response = await API.getZones();
|
const response = await API.getZones();
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
updateZone: async (id: string, data: any) => {
|
||||||
|
const response = await API.updateZone(id, data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
createRegion: async (data: any) => {
|
||||||
|
const response = await API.createRegion(data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
updateRegion: async (id: string, data: any) => {
|
||||||
|
const response = await API.updateRegion(id, data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
getRegions: async () => {
|
getRegions: async () => {
|
||||||
const response = await API.getRegions();
|
const response = await API.getRegions();
|
||||||
return response.data;
|
return response.data;
|
||||||
@ -32,6 +44,22 @@ export const masterService = {
|
|||||||
const response = await API.getDistricts(stateId);
|
const response = await API.getDistricts(stateId);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
getAreas: async (districtId?: string) => {
|
||||||
|
const response = await API.getAreas(districtId);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
updateArea: async (id: string, data: any) => {
|
||||||
|
const response = await API.updateArea(id, data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
createArea: async (data: any) => {
|
||||||
|
const response = await API.createArea(data);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
getAreaManagers: async () => {
|
||||||
|
const response = await API.getAreaManagers();
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
// User Management
|
// User Management
|
||||||
getUsers: async () => {
|
getUsers: async () => {
|
||||||
|
|||||||
31
src/services/onboarding.service.ts
Normal file
31
src/services/onboarding.service.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { API } from '../api/API';
|
||||||
|
|
||||||
|
export const onboardingService = {
|
||||||
|
submitApplication: async (data: any) => {
|
||||||
|
try {
|
||||||
|
const response = await API.submitApplication(data);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Submit application error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getApplications: async () => {
|
||||||
|
try {
|
||||||
|
const response = await API.getApplications();
|
||||||
|
return response.data?.data || response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Get applications error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getApplicationById: async (id: string) => {
|
||||||
|
try {
|
||||||
|
const response = await API.getApplicationById(id);
|
||||||
|
return response.data?.data || response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Get application by id error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user