151 lines
9.3 KiB
TypeScript
151 lines
9.3 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
|
|
import { bootstrapFrappeUserFromSession } from './utils/bootstrapFrappeUserFromSession';
|
|
import Login from './pages/Login';
|
|
import Sidebar from './components/Sidebar';
|
|
import Header from './components/Header';
|
|
import UserProfilePage from './pages/UserProfilePage';
|
|
import ProjectModulePage from './pages/ProjectModulePage';
|
|
import ProjectReportsDashboard from './pages/ProjectReportsDashboard';
|
|
import ProjectList from './pages/ProjectList';
|
|
import ProjectDetail from './pages/ProjectDetail';
|
|
import TaskList from './pages/TaskList';
|
|
import TaskDetail from './pages/TaskDetail';
|
|
import TimesheetList from './pages/TimesheetList';
|
|
import TimesheetDetail from './pages/TimesheetDetail';
|
|
import ActivityTypeList from './pages/ActivityTypeList';
|
|
import ActivityTypeDetail from './pages/ActivityTypeDetail';
|
|
import ProjectTemplateList from './pages/ProjectTemplateList';
|
|
import ProjectTemplateDetail from './pages/ProjectTemplateDetail';
|
|
import CustomerList from './pages/CustomerList';
|
|
import CustomerDetail from './pages/CustomerDetail';
|
|
import EmployeeList from './pages/EmployeeList';
|
|
import EmployeeDetail from './pages/EmployeeDetail';
|
|
import SalesInvoiceList from './pages/SalesInvoiceList';
|
|
import SalesInvoiceDetail from './pages/SalesInvoiceDetail';
|
|
import SalesOrderList from './pages/SalesOrderList';
|
|
import SalesOrderDetail from './pages/SalesOrderDetail';
|
|
import PurchaseOrderList from './pages/PurchaseOrderList';
|
|
import PurchaseOrderDetail from './pages/PurchaseOrderDetail';
|
|
import DeliveryNoteList from './pages/DeliveryNoteList';
|
|
import DeliveryNoteDetail from './pages/DeliveryNoteDetail';
|
|
import MaterialRequestList from './pages/MaterialRequestList';
|
|
import MaterialRequestDetail from './pages/MaterialRequestDetail';
|
|
import PurchaseReceiptList from './pages/PurchaseReceiptList';
|
|
import PurchaseReceiptDetail from './pages/PurchaseReceiptDetail';
|
|
import PaymentEntryList from './pages/PaymentEntryList';
|
|
import PaymentEntryDetail from './pages/PaymentEntryDetail';
|
|
import { SidebarLayoutProvider } from './contexts/SidebarLayoutContext';
|
|
|
|
const LayoutWithSidebar: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
const user = localStorage.getItem('user');
|
|
const userEmail = user ? JSON.parse(user).email : '';
|
|
return (
|
|
<SidebarLayoutProvider>
|
|
<div className="flex h-screen overflow-hidden bg-gray-50 dark:bg-gray-900">
|
|
<Sidebar userEmail={userEmail} />
|
|
<div className="pm-app-main flex min-w-0 flex-1 flex-col overflow-hidden">
|
|
<Header userEmail={userEmail} />
|
|
<div className="flex-1 overflow-y-auto bg-gray-50 dark:bg-gray-900">{children}</div>
|
|
</div>
|
|
</div>
|
|
</SidebarLayoutProvider>
|
|
);
|
|
};
|
|
|
|
const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
const [status, setStatus] = useState<'loading' | 'authed' | 'guest'>('loading');
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
|
|
(async () => {
|
|
if (localStorage.getItem('user')) {
|
|
if (!cancelled) setStatus('authed');
|
|
return;
|
|
}
|
|
const result = await bootstrapFrappeUserFromSession();
|
|
if (!cancelled) setStatus(result.ok ? 'authed' : 'guest');
|
|
})();
|
|
|
|
return () => {
|
|
cancelled = true;
|
|
};
|
|
}, []);
|
|
|
|
if (status === 'loading') {
|
|
return (
|
|
<div className="flex h-screen items-center justify-center bg-gray-50 dark:bg-gray-900">
|
|
<div className="flex flex-col items-center gap-3 text-gray-600 dark:text-gray-400">
|
|
<svg
|
|
className="h-10 w-10 animate-spin text-indigo-600"
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
aria-hidden
|
|
>
|
|
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
<path
|
|
className="opacity-75"
|
|
fill="currentColor"
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
/>
|
|
</svg>
|
|
<span className="text-sm">Loading…</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (status === 'guest') {
|
|
return <Navigate to="/login" replace />;
|
|
}
|
|
|
|
return <>{children}</>;
|
|
};
|
|
|
|
const App: React.FC = () => (
|
|
<Router basename="/project_management">
|
|
<Routes>
|
|
<Route path="/login" element={<Login />} />
|
|
<Route path="/user-profile" element={<ProtectedRoute><LayoutWithSidebar><UserProfilePage /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/projects" element={<ProtectedRoute><LayoutWithSidebar><ProjectModulePage /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/projects/reports" element={<ProtectedRoute><LayoutWithSidebar><ProjectReportsDashboard /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/projects/project-updates" element={<Navigate to="/projects" replace />} />
|
|
<Route path="/projects/project-updates/:updateName" element={<Navigate to="/projects" replace />} />
|
|
<Route path="/projects/list" element={<ProtectedRoute><LayoutWithSidebar><ProjectList /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/projects/list/:projectName" element={<ProtectedRoute><LayoutWithSidebar><ProjectDetail /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/projects/tasks" element={<ProtectedRoute><LayoutWithSidebar><TaskList /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/projects/tasks/:taskName" element={<ProtectedRoute><LayoutWithSidebar><TaskDetail /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/projects/timesheets" element={<ProtectedRoute><LayoutWithSidebar><TimesheetList /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/projects/timesheets/:timesheetName" element={<ProtectedRoute><LayoutWithSidebar><TimesheetDetail /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/projects/activity-types" element={<ProtectedRoute><LayoutWithSidebar><ActivityTypeList /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/projects/activity-types/:activityTypeName" element={<ProtectedRoute><LayoutWithSidebar><ActivityTypeDetail /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/projects/templates" element={<ProtectedRoute><LayoutWithSidebar><ProjectTemplateList /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/projects/templates/:templateName" element={<ProtectedRoute><LayoutWithSidebar><ProjectTemplateDetail /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/customers" element={<ProtectedRoute><LayoutWithSidebar><CustomerList /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/customers/:customerName" element={<ProtectedRoute><LayoutWithSidebar><CustomerDetail /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/employees" element={<ProtectedRoute><LayoutWithSidebar><EmployeeList /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/employees/:employeeName" element={<ProtectedRoute><LayoutWithSidebar><EmployeeDetail /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/invoices" element={<ProtectedRoute><LayoutWithSidebar><SalesInvoiceList /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/invoices/:invoiceName" element={<ProtectedRoute><LayoutWithSidebar><SalesInvoiceDetail /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/sales-orders" element={<ProtectedRoute><LayoutWithSidebar><SalesOrderList /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/sales-orders/:soName" element={<ProtectedRoute><LayoutWithSidebar><SalesOrderDetail /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/purchase-orders" element={<ProtectedRoute><LayoutWithSidebar><PurchaseOrderList /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/purchase-orders/:poName" element={<ProtectedRoute><LayoutWithSidebar><PurchaseOrderDetail /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/delivery-notes" element={<ProtectedRoute><LayoutWithSidebar><DeliveryNoteList /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/delivery-notes/:dnName" element={<ProtectedRoute><LayoutWithSidebar><DeliveryNoteDetail /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/material-requests" element={<ProtectedRoute><LayoutWithSidebar><MaterialRequestList /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/material-requests/:mrName" element={<ProtectedRoute><LayoutWithSidebar><MaterialRequestDetail /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/purchase-receipts" element={<ProtectedRoute><LayoutWithSidebar><PurchaseReceiptList /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/purchase-receipts/:prName" element={<ProtectedRoute><LayoutWithSidebar><PurchaseReceiptDetail /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/payment-entries" element={<ProtectedRoute><LayoutWithSidebar><PaymentEntryList /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/payment-entries/:peName" element={<ProtectedRoute><LayoutWithSidebar><PaymentEntryDetail /></LayoutWithSidebar></ProtectedRoute>} />
|
|
<Route path="/" element={<Navigate to="/projects" replace />} />
|
|
<Route path="*" element={<Navigate to="/projects" replace />} />
|
|
</Routes>
|
|
</Router>
|
|
);
|
|
|
|
export default App;
|