Re_Figma_Code/src/App.tsx

786 lines
28 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 <Requests onViewRequest={onViewRequest} />;
} else {
return <UserAllRequests onViewRequest={onViewRequest} />;
}
}
// 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<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(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 (
<div className="flex items-center justify-center h-screen">
<div className="flex flex-col items-center gap-4">
<div className="w-8 h-8 animate-spin rounded-full border-4 border-blue-600 border-t-transparent" />
<p className="text-muted-foreground">Loading...</p>
</div>
</div>
);
}
// Render dealer-specific dashboard if user is a dealer
if (isDealer) {
return <DealerDashboard onNavigate={onNavigate} onNewRequest={onNewRequest} />;
}
// Render regular dashboard for all other users
return <Dashboard onNavigate={onNavigate} onNewRequest={onNewRequest} />;
}
// 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>('');
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 (
<div className="min-h-screen h-screen flex flex-col overflow-hidden bg-background">
<Routes>
{/* Auth Callback - Unified callback for both OKTA and Tanflow */}
<Route
path="/login/callback"
element={<AuthCallback />}
/>
{/* Dashboard - Conditionally renders DealerDashboard or regular Dashboard */}
<Route
path="/"
element={
<PageLayout currentPage="dashboard" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<DashboardRoute onNavigate={handleNavigate} onNewRequest={handleNewRequest} />
</PageLayout>
}
/>
<Route
path="/dashboard"
element={
<PageLayout currentPage="dashboard" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<DashboardRoute onNavigate={handleNavigate} onNewRequest={handleNewRequest} />
</PageLayout>
}
/>
{/* Admin Routes Group with Shared Layout */}
<Route
element={
<PageLayout currentPage="admin" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Outlet />
</PageLayout>
}
>
<Route path="/admin" element={<Admin />} />
<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 />
}
/>
{/* 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>
}
/>
{/* Form 16 Credit Notes (dealer) */}
<Route
path="/form16/credit-notes"
element={
<PageLayout currentPage="form16-credit-notes" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Form16CreditNotes />
</PageLayout>
}
/>
<Route
path="/form16/credit-notes/:id"
element={
<PageLayout currentPage="form16-credit-notes" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Form16CreditNoteDetail />
</PageLayout>
}
/>
{/* Form 16 Submit (dealer) */}
<Route
path="/form16/submit"
element={
<PageLayout currentPage="form16-submit" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Form16Submit />
</PageLayout>
}
/>
{/* Form 16 Submission result (after dealer submits) */}
<Route
path="/form16/submit/result"
element={
<PageLayout currentPage="form16-submit" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Form16SubmissionResult />
</PageLayout>
}
/>
{/* Form 16 26AS (RE) */}
<Route
path="/form16/26as"
element={
<PageLayout currentPage="form16-26as" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Form16_26AS />
</PageLayout>
}
/>
{/* Form 16 Pending Submissions (dealer) */}
<Route
path="/form16/pending-submissions"
element={
<PageLayout currentPage="form16-pending-submissions" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Form16PendingSubmissions />
</PageLayout>
}
/>
{/* Form 16 Non-submitted dealers (RE) */}
<Route
path="/form16/non-submitted-dealers"
element={
<PageLayout currentPage="form16-non-submitted-dealers" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<Form16NonSubmittedDealers />
</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>
}
/>
{/* Security Settings */}
<Route
path="/settings/security"
element={
<PageLayout currentPage="settings" onNavigate={handleNavigate} onNewRequest={handleNewRequest} onLogout={onLogout}>
<SecuritySettings />
</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)',
},
}}
/>
{/* Manager Selection Modal */}
<ManagerSelectionModal
open={managerModalOpen}
onClose={() => {
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 && (
<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>
);
}