import { useState, useEffect } from 'react'; import { BrowserRouter, Routes, Route, useNavigate, Outlet } from 'react-router-dom'; import { PageLayout } from '@/components/layout/PageLayout'; import { Dashboard } from '@/pages/Dashboard'; import { OpenRequests } from '@/pages/OpenRequests'; import { ClosedRequests } from '@/pages/ClosedRequests'; import { RequestDetail } from '@/pages/RequestDetail'; import { SharedSummaries } from '@/pages/SharedSummaries/SharedSummaries'; import { SharedSummaryDetail } from '@/pages/SharedSummaries/SharedSummaryDetail'; import { WorkNotes } from '@/pages/WorkNotes'; import { CreateRequest } from '@/pages/CreateRequest'; import { ClaimManagementWizard } from '@/dealer-claim/components/request-creation/ClaimManagementWizard'; import { DealerDashboard } from '@/dealer-claim/pages/Dashboard'; import { MyRequests } from '@/pages/MyRequests'; import { Requests } from '@/pages/Requests/Requests'; import { UserAllRequests } from '@/pages/Requests/UserAllRequests'; import { useAuth, hasManagementAccess } from '@/contexts/AuthContext'; import { ApproverPerformance } from '@/pages/ApproverPerformance/ApproverPerformance'; import { Profile } from '@/pages/Profile'; import { Settings } from '@/pages/Settings'; import { SecuritySettings } from '@/pages/Settings/SecuritySettings'; import { Notifications } from '@/pages/Notifications'; import { DetailedReports } from '@/pages/DetailedReports'; import { AdminTemplatesList } from '@/pages/Admin/Templates/AdminTemplatesList'; import { CreateTemplate } from '@/pages/Admin/Templates/CreateTemplate'; import { CreateAdminRequest } from '@/pages/CreateAdminRequest/CreateAdminRequest'; import { Admin } from '@/pages/Admin/Admin'; import { Form16CreditNotes } from '@/pages/Form16/Form16CreditNotes'; import { Form16CreditNoteDetail } from '@/pages/Form16/Form16CreditNoteDetail'; import { Form16Submit } from '@/pages/Form16/Form16Submit'; import { Form16SubmissionResult } from '@/pages/Form16/Form16SubmissionResult'; import { Form16_26AS } from '@/pages/Form16/Form16_26AS'; import { Form16NonSubmittedDealers } from '@/pages/Form16/Form16NonSubmittedDealers'; import { Form16PendingSubmissions } from '@/pages/Form16/Form16PendingSubmissions'; import { ApprovalActionModal } from '@/components/modals/ApprovalActionModal'; import { Toaster } from '@/components/ui/sonner'; import { toast } from 'sonner'; import { AuthCallback } from '@/pages/Auth/AuthCallback'; import { createClaimRequest } from '@/services/dealerClaimApi'; import { ManagerSelectionModal } from '@/components/modals/ManagerSelectionModal'; import { navigateToRequest } from '@/utils/requestNavigation'; import { TokenManager } from '@/utils/tokenManager'; interface AppProps { onLogout?: () => void; } // Component to conditionally render Admin or User All Requests screen // This ensures that when navigating from the sidebar, the correct screen is shown based on user role function RequestsRoute({ onViewRequest }: { onViewRequest: (requestId: string) => void }) { const { user } = useAuth(); const isAdmin = hasManagementAccess(user); // Render separate screens based on user role // Admin/Management users see all organization requests // Regular users see only their participant requests (approver/spectator, NOT initiator) if (isAdmin) { return ; } else { return ; } } // Component to conditionally render Dashboard or DealerDashboard based on user job title function DashboardRoute({ onNavigate, onNewRequest }: { onNavigate?: (page: string) => void; onNewRequest?: () => void }) { const [isDealer, setIsDealer] = useState(false); const [isLoading, setIsLoading] = useState(true); useEffect(() => { try { const userData = TokenManager.getUserData(); setIsDealer(userData?.jobTitle === 'Dealer'); } catch (error) { console.error('[App] Error checking dealer status:', error); setIsDealer(false); } finally { setIsLoading(false); } }, []); if (isLoading) { return (

Loading...

); } // Render dealer-specific dashboard if user is a dealer if (isDealer) { return ; } // Render regular dashboard for all other users return ; } // Main Application Routes Component function AppRoutes({ onLogout }: AppProps) { const navigate = useNavigate(); const [approvalAction, setApprovalAction] = useState<'approve' | 'reject' | null>(null); const [dynamicRequests, setDynamicRequests] = useState([]); const [selectedRequestId, setSelectedRequestId] = useState(''); const [selectedRequestTitle, setSelectedRequestTitle] = useState(''); const [managerModalOpen, setManagerModalOpen] = useState(false); const [managerModalData, setManagerModalData] = useState<{ errorType: 'NO_MANAGER_FOUND' | 'MULTIPLE_MANAGERS_FOUND'; managers?: Array<{ userId: string; email: string; displayName: string; firstName?: string; lastName?: string; department?: string; }>; message?: string; pendingClaimData?: any; } | null>(null); // Retrieve dynamic requests from localStorage on mount useEffect(() => { const storedRequests = localStorage.getItem('dynamicRequests'); if (storedRequests) { try { const parsed = JSON.parse(storedRequests); setDynamicRequests(parsed); } catch (error) { console.error('Error parsing dynamic requests:', error); } } }, []); // Sync dynamic requests to localStorage whenever they change useEffect(() => { if (dynamicRequests.length > 0) { localStorage.setItem('dynamicRequests', JSON.stringify(dynamicRequests)); } }, [dynamicRequests]); const handleNavigate = (page: string) => { // Handle special routes if (page === 'profile') { navigate('/profile'); return; } if (page === 'settings') { navigate('/settings'); return; } // If page already starts with '/', use it directly (e.g., '/requests?status=approved') // Otherwise, add leading slash (e.g., 'open-requests' -> '/open-requests') if (page.startsWith('/')) { navigate(page); } else { navigate(`/${page}`); } }; const handleViewRequest = (requestId: string, requestTitle?: string, status?: string, request?: any) => { setSelectedRequestId(requestId); setSelectedRequestTitle(requestTitle || 'Unknown Request'); // Use global navigation utility for consistent routing navigateToRequest({ requestId, requestTitle, status, request, navigate, }); }; const handleBack = () => { navigate(-1); }; const handleNewRequest = () => { navigate('/new-request'); }; const handleNewRequestSubmit = (requestData: any) => { // Check if this is a template selection (from Existing Template button) if (requestData.templateType) { // Navigate to the specific template wizard if (requestData.templateType === 'claim-management') { navigate('/claim-management'); } return; } // If requestData has backendId, it means it came from the API flow (CreateRequest component) // The hook already shows the toast, so we just navigate if (requestData.backendId) { navigate('/my-requests'); return; } // Regular custom request submission (old flow without API) // Generate unique ID for the new custom request const requestId = `RE-REQ-2024-${String(dynamicRequests.length + 1).padStart(3, '0')}`; // Create full custom request object const newCustomRequest = { id: requestId, title: requestData.title, description: requestData.description || '', category: requestData.category || 'General', subcategory: requestData.subcategory || '', status: 'pending', priority: requestData.priority || 'standard', amount: requestData.budget || 'N/A', slaProgress: 0, slaRemaining: '5 days', slaEndDate: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true }), currentStep: 1, totalSteps: requestData.approvers?.length || 1, currentApprover: requestData.approvers?.[0]?.name || requestData.approvers?.[0]?.email?.split('@')[0] || 'Pending Assignment', approverLevel: `1 of ${requestData.approvers?.length || 1}`, template: 'custom', initiator: { name: 'Current User', role: requestData.initiatorRole || 'Employee', department: requestData.department || 'General', email: 'current.user@royalenfield.com', phone: '+91 98765 43290', avatar: 'CU' }, department: requestData.department || 'General', createdAt: new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true }), updatedAt: new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true }), dueDate: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000).toISOString(), submittedDate: new Date().toISOString(), estimatedCompletion: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }), conclusionRemark: '', approvalFlow: (requestData.approvers || []).filter((a: any) => a).map((approver: any, index: number) => { // Extract name from email if name is not available const approverName = approver?.name || approver?.email?.split('@')[0] || `Approver ${index + 1}`; const approverEmail = approver?.email || ''; return { step: index + 1, approver: `${approverName}${approverEmail ? ` (${approverEmail})` : ''}`, role: approver?.role || `Level ${approver?.level || index + 1} Approver`, status: index === 0 ? 'pending' : 'waiting', tatHours: approver?.tat ? (typeof approver.tat === 'string' ? parseInt(approver.tat) : approver.tat) : 48, elapsedHours: index === 0 ? 0 : 0, assignedAt: index === 0 ? new Date().toISOString() : null, comment: null, timestamp: null }; }), documents: [], spectators: (requestData.spectators || []) .filter((s: any) => s && (s.name || s.email)) .map((spectator: any) => { const name = spectator?.name || spectator?.email?.split('@')[0] || 'Observer'; return { name: name, role: spectator?.role || spectator?.department || 'Observer', avatar: name.split(' ').map((n: string) => n[0]).join('').toUpperCase().slice(0, 2) || 'OB' }; }), auditTrail: [ { type: 'created', action: 'Request Created', details: `Custom request "${requestData.title}" created`, user: 'Current User', timestamp: new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true }) }, { type: 'assignment', action: 'Assigned to Approver', details: `Request assigned to ${requestData.approvers?.[0]?.name || requestData.approvers?.[0]?.email || 'first approver'}`, user: 'System', timestamp: new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', hour12: true }) } ], tags: requestData.tags || ['custom-request'] }; // Add to dynamic requests setDynamicRequests([...dynamicRequests, newCustomRequest]); navigate('/my-requests'); }; const handleApprovalSubmit = (action: 'approve' | 'reject', _comment: string) => { return new Promise((resolve) => { setTimeout(() => { if (action === 'approve') { toast.success('Request Approved', { description: 'The request has been approved and forwarded to the next step.', duration: 5000, }); } else { toast.error('Request Rejected', { description: 'The request has been rejected and returned to the initiator.', duration: 5000, }); } setApprovalAction(null); resolve(true); }, 1000); }); }; const handleCloseApprovalModal = () => { setApprovalAction(null); }; const handleClaimManagementSubmit = async (claimData: any, _selectedManagerEmail?: string) => { try { // Prepare payload for API const payload = { activityName: claimData.activityName, activityType: claimData.activityType, dealerCode: claimData.dealerCode, dealerName: claimData.dealerName, dealerEmail: claimData.dealerEmail || undefined, dealerPhone: claimData.dealerPhone || undefined, dealerAddress: claimData.dealerAddress || undefined, activityDate: claimData.activityDate ? new Date(claimData.activityDate).toISOString() : undefined, location: claimData.location, requestDescription: claimData.requestDescription, periodStartDate: claimData.periodStartDate ? new Date(claimData.periodStartDate).toISOString() : undefined, periodEndDate: claimData.periodEndDate ? new Date(claimData.periodEndDate).toISOString() : undefined, estimatedBudget: claimData.estimatedBudget || undefined, approvers: claimData.approvers || [], // Pass approvers array }; // Call API to create claim request const response = await createClaimRequest(payload); // Validate response - ensure request was actually created successfully if (!response || !response.request) { throw new Error('Invalid response from server: Request object not found'); } const createdRequest = response.request; // Validate that we have at least one identifier (requestNumber or requestId) if (!createdRequest.requestNumber && !createdRequest.requestId) { throw new Error('Invalid response from server: Request identifier not found'); } // Close manager modal if open setManagerModalOpen(false); setManagerModalData(null); // Only show success toast if request was actually created successfully toast.success('Claim Request Submitted', { description: 'Your claim management request has been created successfully.', }); // Navigate to the created request detail page using requestNumber if (createdRequest.requestNumber) { navigate(`/request/${createdRequest.requestNumber}`); } else if (createdRequest.requestId) { // Fallback to requestId if requestNumber is not available navigate(`/request/${createdRequest.requestId}`); } else { // This should not happen due to validation above, but just in case navigate('/my-requests'); } } catch (error: any) { console.error('[App] Error creating claim request:', error); // Check for manager-related errors const errorData = error?.response?.data; const errorCode = errorData?.code || errorData?.error?.code; if (errorCode === 'NO_MANAGER_FOUND') { // Show modal for no manager found setManagerModalData({ errorType: 'NO_MANAGER_FOUND', message: errorData?.message || errorData?.error?.message || 'No reporting manager found. Please ensure your manager is correctly configured in the system.', pendingClaimData: claimData, }); setManagerModalOpen(true); return; } if (errorCode === 'MULTIPLE_MANAGERS_FOUND') { // Show modal with manager list for selection const managers = errorData?.managers || errorData?.error?.managers || []; setManagerModalData({ errorType: 'MULTIPLE_MANAGERS_FOUND', managers: managers, message: errorData?.message || errorData?.error?.message || 'Multiple managers found. Please select one.', pendingClaimData: claimData, }); setManagerModalOpen(true); return; } // Other errors - show toast const errorMessage = error?.response?.data?.message || error?.message || 'Failed to create claim request'; toast.error('Failed to Submit Claim Request', { description: errorMessage, }); } }; return (
{/* Auth Callback - Unified callback for both OKTA and Tanflow */} } /> {/* Dashboard - Conditionally renders DealerDashboard or regular Dashboard */} } /> } /> {/* Admin Routes Group with Shared Layout */} } > } /> } /> } /> } /> {/* Create Request from Admin Template (Dedicated Flow) */} } /> {/* Open Requests */} } /> {/* Closed Requests */} } /> {/* Shared Summaries */} } /> {/* Shared Summary Detail */} } /> {/* Form 16 – Credit Notes (dealer) */} } /> } /> {/* Form 16 – Submit (dealer) */} } /> {/* Form 16 – Submission result (after dealer submits) */} } /> {/* Form 16 – 26AS (RE) */} } /> {/* Form 16 – Pending Submissions (dealer) */} } /> {/* Form 16 – Non-submitted dealers (RE) */} } /> {/* My Requests */} } /> {/* Requests - Separate screens for Admin and Regular Users */} } /> {/* Approver Performance - Detailed Performance Analysis */} } /> {/* Request Detail - requestId will be read from URL params */} } /> {/* Work Notes - Dedicated Full-Screen Page */} } /> {/* New Request (Custom) */} } /> {/* Edit Draft Request */} } /> {/* Claim Management Wizard */} } /> {/* Profile */} } /> {/* Settings */} } /> {/* Security Settings */} } /> {/* Notifications */} } /> {/* Detailed Reports */} } /> {/* Manager Selection Modal */} { setManagerModalOpen(false); setManagerModalData(null); }} onSelect={async (managerEmail: string) => { if (managerModalData?.pendingClaimData) { // Retry creating claim request with selected manager // The pendingClaimData contains all the form data from the wizard // This preserves the entire submission state while waiting for manager selection await handleClaimManagementSubmit(managerModalData.pendingClaimData, managerEmail); } }} managers={managerModalData?.managers} errorType={managerModalData?.errorType || 'NO_MANAGER_FOUND'} message={managerModalData?.message} isLoading={false} // Will be set to true during retry if needed /> {/* Approval Action Modal */} {approvalAction && ( )}
); } // Main App Component with Router interface MainAppProps { onLogout?: () => void; } export default function App(props?: MainAppProps) { const { onLogout } = props || {}; return ( ); }