Re_Figma_Code/src/App.tsx

729 lines
25 KiB
TypeScript

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 '@/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 { AdminTemplatesList } from '@/pages/Admin/Templates/AdminTemplatesList';
import { CreateTemplate } from '@/pages/Admin/Templates/CreateTemplate';
import { CreateAdminRequest } from '@/pages/CreateAdminRequest/CreateAdminRequest';
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';
// 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 <Requests onViewRequest={onViewRequest} />;
} else {
return <UserAllRequests onViewRequest={onViewRequest} />;
}
}
// Main Application Routes Component
function AppRoutes({ onLogout }: AppProps) {
const navigate = useNavigate();
const [approvalAction, setApprovalAction] = useState<'approve' | 'reject' | null>(null);
const [dynamicRequests, setDynamicRequests] = useState<any[]>([]);
const [selectedRequestId, setSelectedRequestId] = useState<string>('');
const [selectedRequestTitle, setSelectedRequestTitle] = useState<string>('');
// 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');
};
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 = (claimData: any) => {
// Generate unique ID for the new claim request
const requestId = `RE-REQ-2024-CM-${String(dynamicRequests.length + 2).padStart(3, '0')}`;
// Create full request object
const newRequest = {
id: requestId,
title: `${claimData.activityName} - Claim Request`,
description: claimData.requestDescription,
category: 'Dealer Operations',
subcategory: 'Claim Management',
status: 'pending',
priority: 'standard',
amount: 'TBD',
slaProgress: 0,
slaRemaining: '7 days',
slaEndDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
currentStep: 1,
totalSteps: 8,
templateType: 'claim-management',
templateName: 'Claim Management',
initiator: {
name: 'Current User',
role: 'Regional Marketing Coordinator',
department: 'Marketing',
email: 'current.user@royalenfield.com',
phone: '+91 98765 43290',
avatar: 'CU'
},
department: 'Marketing',
createdAt: new Date().toLocaleString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true
}),
updatedAt: new Date().toLocaleString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true
}),
dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
conclusionRemark: '',
claimDetails: {
activityName: claimData.activityName,
activityType: claimData.activityType,
activityDate: claimData.activityDate ? new Date(claimData.activityDate).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : '',
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).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : '',
periodEnd: claimData.periodEndDate ? new Date(claimData.periodEndDate).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : ''
},
approvalFlow: claimData.workflowSteps || [
{
step: 1,
approver: `${claimData.dealerName} (Dealer)`,
role: 'Dealer - Document Upload',
status: 'pending',
tatHours: 72,
elapsedHours: 0,
assignedAt: new Date().toISOString(),
comment: null,
timestamp: null,
description: 'Dealer uploads proposal document, cost breakup, timeline for closure, and other supporting documents'
},
{
step: 2,
approver: 'Current User (Initiator)',
role: 'Initiator Evaluation',
status: 'waiting',
tatHours: 48,
elapsedHours: 0,
assignedAt: null,
comment: null,
timestamp: null,
description: 'Initiator reviews dealer documents and approves or requests modifications'
},
{
step: 3,
approver: 'System Auto-Process',
role: 'IO Confirmation',
status: 'waiting',
tatHours: 1,
elapsedHours: 0,
assignedAt: null,
comment: null,
timestamp: null,
description: 'Automatic IO (Internal Order) confirmation generated upon initiator approval'
},
{
step: 4,
approver: 'Rajesh Kumar',
role: 'Department Lead Approval',
status: 'waiting',
tatHours: 72,
elapsedHours: 0,
assignedAt: null,
comment: null,
timestamp: null,
description: 'Department head approves and blocks budget in IO for this activity'
},
{
step: 5,
approver: `${claimData.dealerName} (Dealer)`,
role: 'Dealer - Completion Documents',
status: 'waiting',
tatHours: 120,
elapsedHours: 0,
assignedAt: null,
comment: null,
timestamp: null,
description: 'Dealer submits activity completion documents and description'
},
{
step: 6,
approver: 'Current User (Initiator)',
role: 'Initiator Verification',
status: 'waiting',
tatHours: 48,
elapsedHours: 0,
assignedAt: null,
comment: null,
timestamp: null,
description: 'Initiator verifies completion documents and can modify approved amount'
},
{
step: 7,
approver: 'System Auto-Process',
role: 'E-Invoice Generation',
status: 'waiting',
tatHours: 1,
elapsedHours: 0,
assignedAt: null,
comment: null,
timestamp: null,
description: 'Auto-generate e-invoice based on final approved amount'
},
{
step: 8,
approver: 'Finance Team',
role: 'Credit Note Issuance',
status: 'waiting',
tatHours: 48,
elapsedHours: 0,
assignedAt: null,
comment: null,
timestamp: null,
description: 'Finance team issues credit note to dealer'
}
],
documents: [],
spectators: [],
auditTrail: [
{
type: 'created',
action: 'Request Created',
details: `Claim request for ${claimData.activityName} created`,
user: 'Current User',
timestamp: new Date().toLocaleString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
hour12: true
})
}
],
tags: ['claim-management', 'new-request', claimData.activityType?.toLowerCase().replace(/\s+/g, '-')]
};
// Add to dynamic requests
setDynamicRequests(prev => [...prev, newRequest]);
// Also add to REQUEST_DATABASE for immediate viewing
(REQUEST_DATABASE as any)[requestId] = newRequest;
toast.success('Claim Request Submitted', {
description: 'Your claim management request has been created successfully.',
});
navigate('/my-requests');
};
return (
<div className="min-h-screen h-screen flex flex-col overflow-hidden bg-background">
<Routes>
{/* Auth Callback - Must be before other routes */}
<Route
path="/login/callback"
element={<AuthCallback />}
/>
{/* Dashboard */}
<Route
path="/"
element={
<PageLayout currentPage="dashboard" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Dashboard onNavigate={handleNavigate} onNewRequest={handleNewRequest} />
</PageLayout>
}
/>
<Route
path="/dashboard"
element={
<PageLayout currentPage="dashboard" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Dashboard onNavigate={handleNavigate} onNewRequest={handleNewRequest} />
</PageLayout>
}
/>
{/* Admin Routes Group with Shared Layout */}
<Route
element={
<PageLayout currentPage="admin-templates" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Outlet />
</PageLayout>
}
>
<Route path="/admin/create-template" element={<CreateTemplate />} />
<Route path="/admin/edit-template/:templateId" element={<CreateTemplate />} />
<Route path="/admin/templates" element={<AdminTemplatesList />} />
</Route>
{/* Create Request from Admin Template (Dedicated Flow) */}
<Route
path="/create-admin-request/:templateId"
element={
<CreateAdminRequest />
}
/>
<Route
path="/admin"
element={
<PageLayout currentPage="admin" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Admin />
</PageLayout>
}
/>
{/* Open Requests */}
<Route
path="/open-requests"
element={
<PageLayout currentPage="open-requests" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<OpenRequests onViewRequest={handleViewRequest} />
</PageLayout>
}
/>
{/* Closed Requests */}
<Route
path="/closed-requests"
element={
<PageLayout currentPage="closed-requests" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<ClosedRequests onViewRequest={handleViewRequest} />
</PageLayout>
}
/>
{/* Shared Summaries */}
<Route
path="/shared-summaries"
element={
<PageLayout currentPage="shared-summaries" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<SharedSummaries />
</PageLayout>
}
/>
{/* Shared Summary Detail */}
<Route
path="/shared-summaries/:sharedSummaryId"
element={
<PageLayout currentPage="shared-summaries" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<SharedSummaryDetail />
</PageLayout>
}
/>
{/* My Requests */}
<Route
path="/my-requests"
element={
<PageLayout currentPage="my-requests" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<MyRequests onViewRequest={handleViewRequest} dynamicRequests={dynamicRequests} />
</PageLayout>
}
/>
{/* Requests - Separate screens for Admin and Regular Users */}
<Route
path="/requests"
element={
<PageLayout currentPage="requests" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<RequestsRoute onViewRequest={handleViewRequest} />
</PageLayout>
}
/>
{/* Approver Performance - Detailed Performance Analysis */}
<Route
path="/approver-performance"
element={
<PageLayout currentPage="approver-performance" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<ApproverPerformance />
</PageLayout>
}
/>
{/* Request Detail - requestId will be read from URL params */}
<Route
path="/request/:requestId"
element={
<PageLayout currentPage="request-detail" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<RequestDetail
requestId=""
onBack={handleBack}
dynamicRequests={dynamicRequests}
/>
</PageLayout>
}
/>
{/* Work Notes - Dedicated Full-Screen Page */}
<Route
path="/work-notes/:requestId"
element={<WorkNotes />}
/>
{/* New Request (Custom) */}
<Route
path="/new-request"
element={
<CreateRequest
onBack={handleBack}
onSubmit={handleNewRequestSubmit}
/>
}
/>
{/* Edit Draft Request */}
<Route
path="/edit-request/:requestId"
element={
<CreateRequest
onBack={handleBack}
onSubmit={handleNewRequestSubmit}
requestId={undefined} // Will be read from URL params
isEditMode={true}
/>
}
/>
{/* Claim Management Wizard */}
<Route
path="/claim-management"
element={
<ClaimManagementWizard
onBack={handleBack}
onSubmit={handleClaimManagementSubmit}
/>
}
/>
{/* Profile */}
<Route
path="/profile"
element={
<PageLayout currentPage="profile" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Profile />
</PageLayout>
}
/>
{/* Settings */}
<Route
path="/settings"
element={
<PageLayout currentPage="settings" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Settings />
</PageLayout>
}
/>
{/* Notifications */}
<Route
path="/notifications"
element={
<PageLayout currentPage="notifications" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Notifications onNavigate={handleNavigate} />
</PageLayout>
}
/>
{/* Detailed Reports */}
<Route
path="/detailed-reports"
element={
<PageLayout currentPage="detailed-reports" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<DetailedReports />
</PageLayout>
}
/>
</Routes>
<Toaster
position="top-right"
toastOptions={{
style: {
background: 'var(--card)',
color: 'var(--card-foreground)',
border: '1px solid var(--border)',
},
}}
/>
{/* Approval Action Modal */}
{approvalAction && (
<ApprovalActionModal
isOpen={!!approvalAction}
onClose={handleCloseApprovalModal}
action={approvalAction}
requestId={selectedRequestId}
requestTitle={selectedRequestTitle}
onSubmit={handleApprovalSubmit}
/>
)}
</div>
);
}
// Main App Component with Router
interface MainAppProps {
onLogout?: () => void;
}
export default function App(props?: MainAppProps) {
const { onLogout } = props || {};
return (
<BrowserRouter>
<AppRoutes onLogout={onLogout} />
</BrowserRouter>
);
}