import { useState, useEffect } from 'react'; import { BrowserRouter, Routes, Route, useNavigate } 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 { RequestDetailTemplated } from '@/pages/RequestDetail/RequestDetailTemplated'; 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 '@/components/workflow/ClaimManagementWizard'; 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 { Notifications } from '@/pages/Notifications'; import { DetailedReports } from '@/pages/DetailedReports'; import { Admin } from '@/pages/Admin'; import { ApprovalActionModal } from '@/components/modals/ApprovalActionModal'; import { Toaster } from '@/components/ui/sonner'; import { toast } from 'sonner'; import { CUSTOM_REQUEST_DATABASE } from '@/utils/customRequestDatabase'; import { CLAIM_MANAGEMENT_DATABASE } from '@/utils/claimManagementDatabase'; import { AuthCallback } from '@/pages/Auth/AuthCallback'; import { mockApi } from '@/services/mockApi'; // Combined Request Database for backward compatibility // This combines both custom and claim management requests export const REQUEST_DATABASE: any = { ...CUSTOM_REQUEST_DATABASE, ...CLAIM_MANAGEMENT_DATABASE }; 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 ; } } // Main Application Routes Component function AppRoutes({ onLogout }: AppProps) { const navigate = useNavigate(); const { user } = useAuth(); // Add user from useAuth hook const [approvalAction, setApprovalAction] = useState<'approve' | 'reject' | null>(null); const [dynamicRequests, setDynamicRequests] = useState([]); const [selectedRequestId, setSelectedRequestId] = useState(''); const [selectedRequestTitle, setSelectedRequestTitle] = useState(''); // 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 = async (requestId: string, requestTitle?: string, status?: string) => { setSelectedRequestId(requestId); setSelectedRequestTitle(requestTitle || 'Unknown Request'); // Check if request is a draft - if so, route to edit form instead of detail view const isDraft = status?.toLowerCase() === 'draft' || status === 'DRAFT'; if (isDraft) { navigate(`/edit-request/${requestId}`); } else { navigate(`/request/${requestId}`); } }; 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; } // Regular custom request submission // Generate unique ID for the new custom request const requestId = `RE-REQ-2024-${String(Object.keys(CUSTOM_REQUEST_DATABASE).length + 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'); toast.success('Request Submitted Successfully!', { description: `Your request "${requestData.title}" (${requestId}) has been created and sent for approval.`, duration: 5000, }); }; 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) => { // Generate unique ID const year = new Date().getFullYear(); const requestsResponse = await mockApi.getAllRequests(); const requests = requestsResponse.success ? (requestsResponse.data || []) : []; const existingIds = requests .filter((r: any) => r.requestId?.includes(`${year}-CM`)) .map((r: any) => { const match = r.requestId?.match(/-(\d+)$/); return match ? parseInt(match[1]) : 0; }); const nextNumber = existingIds.length > 0 ? Math.max(...existingIds) + 1 : 1; const requestId = `RE-REQ-${year}-CM-${nextNumber.toString().padStart(3, '0')}`; const now = new Date().toISOString(); const dueDate = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(); // Create full request object matching DatabaseRequest interface const newRequest = { id: requestId, requestId: requestId, requestNumber: requestId, title: `${claimData.activityName} - Claim Request`, description: claimData.requestDescription, category: 'claim-management', subcategory: 'Claim Management', type: 'dealer-claim', status: 'pending', priority: 'standard', amount: claimData.estimatedBudget ? parseFloat(claimData.estimatedBudget.replace(/[₹,]/g, '')) || 0 : 0, claimAmount: claimData.estimatedBudget ? parseFloat(claimData.estimatedBudget.replace(/[₹,]/g, '')) || 0 : 0, slaProgress: 0, slaRemaining: '7 days', slaEndDate: dueDate, currentStep: 1, totalSteps: 8, template: 'claim-management', templateName: 'Claim Management', initiator: { userId: (user as any)?.userId || 'user-123', name: (user as any)?.name || 'Current User', email: (user as any)?.email || 'current.user@royalenfield.com', role: (user as any)?.role || 'Regional Marketing Coordinator', department: (user as any)?.department || 'Marketing', phone: (user as any)?.phone || '+91 98765 43290', avatar: (user as any)?.name?.split(' ').map((n: string) => n[0]).join('').toUpperCase() || 'CU' }, department: (user as any)?.department || 'Marketing', createdAt: now, updatedAt: now, dueDate: dueDate, conclusionRemark: '', claimDetails: { activityName: claimData.activityName, activityType: claimData.activityType, activityDate: claimData.activityDate ? new Date(claimData.activityDate).toISOString() : now, location: claimData.location, dealerCode: claimData.dealerCode, dealerName: claimData.dealerName, dealerEmail: claimData.dealerEmail || 'N/A', dealerPhone: claimData.dealerPhone || 'N/A', dealerAddress: claimData.dealerAddress || 'N/A', requestDescription: claimData.requestDescription, estimatedBudget: claimData.estimatedBudget || 'TBD', periodStart: claimData.periodStartDate ? new Date(claimData.periodStartDate).toISOString() : now, periodEnd: claimData.periodEndDate ? new Date(claimData.periodEndDate).toISOString() : dueDate }, // Also add dealerInfo for compatibility dealerInfo: { name: claimData.dealerName, code: claimData.dealerCode, email: claimData.dealerEmail || 'N/A', phone: claimData.dealerPhone || 'N/A', address: claimData.dealerAddress || 'N/A', }, // Add activityInfo for compatibility activityInfo: { activityName: claimData.activityName, activityType: claimData.activityType, activityDate: claimData.activityDate ? new Date(claimData.activityDate).toISOString() : now, location: claimData.location, }, tags: ['claim-management', 'new-request', claimData.activityType?.toLowerCase().replace(/\s+/g, '-')] }; // Save to mock API try { console.log('[Claim Management] Creating request:', requestId); const createResponse = await mockApi.createRequest(newRequest); if (!createResponse.success) { throw new Error(createResponse.error?.message || 'Failed to create request'); } const savedRequest = createResponse.data; console.log('[Claim Management] Request created successfully:', savedRequest.requestId); // Create approval flow steps for dealer claim (8-step workflow) const initiatorName = (user as any)?.name || 'Current User'; const approvalFlowSteps = [ { step: 1, approver: `${claimData.dealerName} (Dealer)`, role: 'Dealer - Proposal Submission', status: 'pending' as const, tatHours: 72, levelId: 'level-1', description: 'Dealer submits the proposal for the activity with comments including proposal document with requested details, cost break-up, timeline for closure, and other requests' }, { step: 2, approver: `${initiatorName} (Requestor)`, role: 'Requestor Evaluation & Confirmation', status: 'waiting' as const, tatHours: 48, levelId: 'level-2', description: 'Requestor evaluates the request and confirms with comments. Decision point: Confirms? (YES → Continue to Dept Lead / NO → Request is cancelled)' }, { step: 3, approver: 'Department Lead', role: 'Dept Lead Approval', status: 'waiting' as const, tatHours: 72, levelId: 'level-3', description: 'Department Lead approval. Decision point: Approved? (YES → Budget is blocked in the respective IO for the activity / NO → More clarification required → Request is cancelled)' }, { step: 4, approver: 'System Auto-Process', role: 'Activity Creation', status: 'waiting' as const, tatHours: 1, levelId: 'level-4', description: 'Activity is created. Activity confirmation email is auto-triggered to dealer / requestor / Lead. IO confirmation to be made.' }, { step: 5, approver: `${claimData.dealerName} (Dealer)`, role: 'Dealer - Completion Documents', status: 'waiting' as const, tatHours: 120, levelId: 'level-5', description: 'Dealer submits the necessary documents upon completion of the activity including document attachments (Zip Folder) and brief description' }, { step: 6, approver: `${initiatorName} (Requestor)`, role: 'Requestor - Claim Approval', status: 'waiting' as const, tatHours: 48, levelId: 'level-6', description: 'Requestor approves the claim in full or can modify the amount. If more information is required, can request additional details from dealer.' }, { step: 7, approver: 'System Auto-Process', role: 'E-Invoice Generation', status: 'waiting' as const, tatHours: 1, levelId: 'level-7', description: 'E-invoice will be generated through DMS.' }, { step: 8, approver: 'Finance Team', role: 'Credit Note from SAP', status: 'waiting' as const, tatHours: 48, levelId: 'level-8', description: 'Got credit note from SAP. Review and send to dealer to complete the claim management process.' }, ]; // Create approval flow steps console.log('[Claim Management] Creating approval flows...'); for (const step of approvalFlowSteps) { const flowResponse = await mockApi.createApprovalFlow(savedRequest.requestId, { step: step.step, levelId: step.levelId || `level-${step.step}`, approver: step.approver, role: step.role, status: step.status, tatHours: step.tatHours, assignedAt: step.status === 'pending' ? now : undefined, description: step.description, }); if (!flowResponse.success) { console.error(`[Claim Management] Failed to create approval flow step ${step.step}:`, flowResponse.error); } else { console.log(`[Claim Management] Created approval flow Step ${step.step}:`, flowResponse.data); } } console.log('[Claim Management] All approval flows created'); // Create initial activity const activityResponse = await mockApi.createActivity(savedRequest.requestId, { id: `act-${Date.now()}`, type: 'created', action: 'Request Created', details: `Claim request for ${claimData.activityName} created`, user: savedRequest.initiator.name, message: 'Request created', }); if (!activityResponse.success) { console.error('[Claim Management] Failed to create initial activity:', activityResponse.error); } // Add to dynamic requests for immediate UI update setDynamicRequests(prev => [...prev, savedRequest]); // Also add to REQUEST_DATABASE for backward compatibility (REQUEST_DATABASE as any)[requestId] = savedRequest; console.log('[Claim Management] Request fully created. Navigating to:', `/request/${requestId}`); // Show success message with more details toast.success('Claim Request Submitted Successfully!', { description: `Request ${requestId} has been created and is ready for dealer proposal submission.`, duration: 5000, }); // Small delay to ensure toast is visible before navigation await new Promise(resolve => setTimeout(resolve, 500)); // Navigate to the demo request detail page navigate(`/demo/request-detail/${requestId}`, { replace: false, state: { fromWizard: true, requestId: requestId } }); } catch (error: any) { console.error('[Claim Management] Failed to create request:', error); toast.error('Failed to Submit Request', { description: error.message || 'An error occurred while creating the request. Please try again.', duration: 6000, }); throw error; // Re-throw to allow component to handle it } }; return (
{/* Auth Callback - Must be before other routes */} } /> {/* Dashboard */} } /> } /> {/* Open Requests */} } /> {/* Closed Requests */} } /> {/* Shared Summaries */} } /> {/* Shared Summary Detail */} } /> {/* My Requests */} } /> {/* Requests - Separate screens for Admin and Regular Users */} } /> {/* Approver Performance - Detailed Performance Analysis */} } /> {/* Request Detail - requestId will be read from URL params */} } /> {/* Demo Request Detail - Template System Preview */} } /> {/* Work Notes - Dedicated Full-Screen Page */} } /> {/* New Request (Custom) */} } /> {/* Edit Draft Request */} } /> {/* Claim Management Wizard */} } /> {/* Profile */} } /> {/* Settings */} } /> {/* Notifications */} } /> {/* Detailed Reports */} } /> {/* Admin Control Panel */} } /> {/* Approval Action Modal */} {approvalAction && ( )}
); } // Main App Component with Router interface MainAppProps { onLogout?: () => void; } export default function App(props?: MainAppProps) { const { onLogout } = props || {}; return ( ); }