Compare commits
2 Commits
fa0359e45c
...
251fba0ed8
| Author | SHA1 | Date | |
|---|---|---|---|
| 251fba0ed8 | |||
| d1400f6ff9 |
66
src/components/layout/Breadcrumbs.tsx
Normal file
66
src/components/layout/Breadcrumbs.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { ChevronRight, Home } from 'lucide-react';
|
||||
|
||||
// Map URL segments to readable names
|
||||
const routeNameMap: Record<string, string> = {
|
||||
dashboard: 'Dashboard',
|
||||
attendance: 'Attendance',
|
||||
leave: 'Leave',
|
||||
approval: 'Approval',
|
||||
'attendance-summary': 'Attendance Summary',
|
||||
monthly: 'Monthly Report',
|
||||
daily: 'Daily Report',
|
||||
regularization: 'Regularization',
|
||||
'leave-summary': 'Leave Summary',
|
||||
'master-data': 'Master Data',
|
||||
company: 'Manage Company',
|
||||
branches: 'Manage Branches',
|
||||
departments: 'Manage Departments',
|
||||
designations: 'Manage Designations',
|
||||
employees: 'Manage Employees',
|
||||
'manage-admins': 'Manage Admins',
|
||||
policies: 'Policy and Rules',
|
||||
};
|
||||
|
||||
export default function Breadcrumbs() {
|
||||
const location = useLocation();
|
||||
const pathnames = location.pathname.split('/').filter(x => x);
|
||||
|
||||
// If we are exactly at /dashboard, just show a static breadcrumb
|
||||
if (pathnames.length === 0 || (pathnames.length === 1 && pathnames[0] === 'dashboard')) {
|
||||
return (
|
||||
<div className="flex items-center text-sm text-text-muted mb-4">
|
||||
<Home size={16} className="mr-2 text-text-light" />
|
||||
<span className="font-medium text-text-primary">Dashboard</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center text-sm text-text-muted mb-4">
|
||||
<Link to="/dashboard" className="hover:text-primary flex items-center">
|
||||
<Home size={16} className="mr-1 text-text-light" />
|
||||
</Link>
|
||||
|
||||
{pathnames.map((name, index) => {
|
||||
// Reconstruct the route path up to this segment
|
||||
const routeTo = `/${pathnames.slice(0, index + 1).join('/')}`;
|
||||
const isLast = index === pathnames.length - 1;
|
||||
|
||||
// Use the map for pretty names, fallback to capitalizing the string
|
||||
const displayName = routeNameMap[name] || name.charAt(0).toUpperCase() + name.slice(1);
|
||||
|
||||
return (
|
||||
<div key={routeTo} className="flex items-center">
|
||||
<ChevronRight size={16} className="mx-1 text-text-light" />
|
||||
{isLast ? (
|
||||
<span className="font-medium text-text-primary">{displayName}</span>
|
||||
) : (
|
||||
<Link to={routeTo} className="hover:text-primary">{displayName}</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -3,52 +3,50 @@ import { Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { useIsFetching } from '@tanstack/react-query';
|
||||
import Sidebar from './Sidebar';
|
||||
import Navbar from './Navbar';
|
||||
import Breadcrumbs from './Breadcrumbs'; // <-- Import Breadcrumbs
|
||||
import DashboardRouter from '../../features/dashboard/routes/DashboardRouter';
|
||||
import AttendanceRouter from '../../features/attendance/routes/AttendanceRouter';
|
||||
import LeaveRouter from '../../features/leave/routes/LeaveRouter';
|
||||
import ApprovalRouter from '../../features/leave/routes/ApprovalRouter';
|
||||
import AttendanceSummaryRouter from '../../features/attendance/routes/AttendanceSummaryRouter';
|
||||
import LeaveSummaryRouter from '../../features/leave/routes/LeaveSummaryRouter';
|
||||
import MasterDataRouter from '../../features/employee-management/routes/MasterDataRouter';
|
||||
import PolicyRouter from '../../features/dashboard/routes/PolicyRouter';
|
||||
import MasterDataRouter from '../../features/employee-management/routes/MasterDataRouter'; // New Import
|
||||
import ManageAdminsRouter from '../../features/employee-management/routes/ManageAdminsRouter';
|
||||
|
||||
export default function MainLayout() {
|
||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||
|
||||
// Returns the number of queries currently fetching data globally
|
||||
const isFetching = useIsFetching();
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-app-bg overflow-hidden">
|
||||
<Sidebar isCollapsed={isCollapsed} setIsCollapsed={setIsCollapsed} />
|
||||
|
||||
{/* Added 'relative' and 'overflow-hidden' here to contain the loading bar */}
|
||||
|
||||
<div className="flex-1 flex flex-col relative overflow-hidden">
|
||||
<Navbar />
|
||||
|
||||
{/* Global Top Loading Bar - now safely constrained to this container */}
|
||||
|
||||
{isFetching > 0 && (
|
||||
<div className="absolute top-16 left-0 right-0 h-1 bg-primary z-50 animate-pulse" />
|
||||
)}
|
||||
|
||||
<main className="flex-1 overflow-y-auto p-6">
|
||||
{/* Add Breadcrumbs here so it shows on every page */}
|
||||
<Breadcrumbs />
|
||||
|
||||
<Routes>
|
||||
<Route path="/dashboard" element={<DashboardRouter />} />
|
||||
<Route path="/attendance" element={<AttendanceRouter />} />
|
||||
<Route path="/leave" element={<LeaveRouter />} />
|
||||
<Route path="/approval" element={<ApprovalRouter />} />
|
||||
<Route path="/attendance-summary/*" element={<AttendanceSummaryRouter />} />
|
||||
<Route path="/leave-summary" element={<LeaveSummaryRouter />} />
|
||||
<Route path="/leave-summary/*" element={<LeaveSummaryRouter />} />
|
||||
<Route path="/master-data/*" element={<MasterDataRouter />} />
|
||||
<Route path="/manage-admins" element={<ManageAdminsRouter />} />
|
||||
<Route path="/policies" element={<PolicyRouter />} />
|
||||
|
||||
{/* Redirect to dashboard if no route matches */}
|
||||
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
||||
</Routes>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -81,7 +81,7 @@ export default function Navbar() {
|
||||
<div className="bg-app-card shadow-sm h-16 flex items-center justify-between px-6 border-b border-app-border relative z-30">
|
||||
|
||||
<div className="flex items-center">
|
||||
<h2 className="text-lg font-bold text-primary tracking-wide">CLRI HRMS</h2>
|
||||
<h2 className="text-lg font-bold text-default tracking-wide">CLRI HRMS</h2>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-4">
|
||||
|
||||
@ -6,14 +6,14 @@ import { Link, useLocation } from 'react-router-dom';
|
||||
import {
|
||||
LayoutDashboard, CalendarCheck, Plane, CheckCircle, BarChart3, ClipboardList,
|
||||
Menu, Users, ShieldUser, ScrollText, ChevronDown, FileText, CalendarDays, ClipboardCheck,
|
||||
Database, Building, Network, Briefcase, Tag // Added new icons
|
||||
Database, Building, Network, Briefcase, Tag
|
||||
} from 'lucide-react';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const iconMap: Record<string, any> = {
|
||||
LayoutDashboard, CalendarCheck, Plane, CheckCircle, BarChart3, ClipboardList,
|
||||
Users, ShieldUser, ScrollText, FileText, CalendarDays, ClipboardCheck,
|
||||
Database, Building, Network, Briefcase, Tag // Added new icons
|
||||
Database, Building, Network, Briefcase, Tag
|
||||
};
|
||||
|
||||
export default function Sidebar({ isCollapsed, setIsCollapsed }: { isCollapsed: boolean; setIsCollapsed: (v: boolean) => void }) {
|
||||
@ -36,6 +36,7 @@ export default function Sidebar({ isCollapsed, setIsCollapsed }: { isCollapsed:
|
||||
const isChildActive = hasChildren && item.children!.some(child => currentPath === child.path);
|
||||
const isActive = currentPath === item.path;
|
||||
|
||||
// If it's a parent item and the sidebar is collapsed, just render its children directly
|
||||
if (hasChildren && isCollapsed) {
|
||||
return item.children!.map(child => renderNavItem(child));
|
||||
}
|
||||
@ -46,6 +47,8 @@ export default function Sidebar({ isCollapsed, setIsCollapsed }: { isCollapsed:
|
||||
<button
|
||||
onClick={() => toggleDropdown(item.name)}
|
||||
className={`w-full flex items-center px-4 py-3 transition-colors duration-200 text-left ${
|
||||
isCollapsed ? 'justify-center' : ''
|
||||
} ${
|
||||
isChildActive ? 'text-white' : 'text-blue-100 hover:bg-sidebar-hover hover:text-white'
|
||||
} border-l-4 ${isChildActive ? 'border-action' : 'border-transparent'}`}
|
||||
>
|
||||
@ -59,6 +62,8 @@ export default function Sidebar({ isCollapsed, setIsCollapsed }: { isCollapsed:
|
||||
<Link
|
||||
to={item.path!}
|
||||
className={`flex items-center px-4 py-3 transition-colors duration-200 text-left ${
|
||||
isCollapsed ? 'justify-center' : ''
|
||||
} ${
|
||||
isActive ? 'bg-sidebar-active text-white border-l-4 border-action' : 'text-blue-100 hover:bg-sidebar-hover hover:text-white border-l-4 border-transparent'
|
||||
}`}
|
||||
>
|
||||
@ -67,6 +72,13 @@ export default function Sidebar({ isCollapsed, setIsCollapsed }: { isCollapsed:
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{/* Tooltip when collapsed */}
|
||||
{isCollapsed && (
|
||||
<div className="absolute left-full ml-4 top-1/2 -translate-y-1/2 z-50 bg-sidebar-active text-white text-sm px-3 py-1 rounded shadow-lg opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none whitespace-nowrap">
|
||||
{item.name}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isDropdownOpen && !isCollapsed && (
|
||||
<ul className="mt-1 ml-4 border-l border-blue-800/50 pl-2">
|
||||
{item.children!.map(child => renderNavItem(child))}
|
||||
@ -89,7 +101,8 @@ export default function Sidebar({ isCollapsed, setIsCollapsed }: { isCollapsed:
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 mt-4 overflow-y-auto">
|
||||
{/* Removed overflow-y-auto so it doesn't clip the tooltips on the right side */}
|
||||
<nav className="flex-1 mt-4 overflow-x-visible">
|
||||
<ul>
|
||||
{navItems.map(item => renderNavItem(item))}
|
||||
</ul>
|
||||
|
||||
@ -1,27 +1,8 @@
|
||||
export default function AdminDashboard() {
|
||||
return (
|
||||
return (
|
||||
<div className="bg-app-card rounded-lg shadow-sm p-6">
|
||||
<h2 className="text-2xl font-bold text-primary">Admin / SuperAdmin Dashboard</h2>
|
||||
<p className="text-text-muted mt-2">Global branch analytics and summaries.</p>
|
||||
|
||||
<div className="mt-6 grid grid-cols-4 gap-4">
|
||||
<div className="p-4 border rounded-lg bg-app-muted">
|
||||
<h3 className="font-semibold text-sm text-text-muted">Total Employees</h3>
|
||||
<p className="text-2xl text-primary">245</p>
|
||||
</div>
|
||||
<div className="p-4 border rounded-lg bg-app-muted">
|
||||
<h3 className="font-semibold text-sm text-text-muted">Present Today</h3>
|
||||
<p className="text-2xl text-present-600">198</p>
|
||||
</div>
|
||||
<div className="p-4 border rounded-lg bg-app-muted">
|
||||
<h3 className="font-semibold text-sm text-text-muted">On Leave</h3>
|
||||
<p className="text-2xl text-action">12</p>
|
||||
</div>
|
||||
<div className="p-4 border rounded-lg bg-app-muted">
|
||||
<h3 className="font-semibold text-sm text-text-muted">Mispunch</h3>
|
||||
<p className="text-2xl text-absent-500">5</p>
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-default">Admin Dashboard</h2>
|
||||
<p className="text-text-muted mt-2">Welcome Admin!</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,19 +1,8 @@
|
||||
export default function EmployeeDashboard() {
|
||||
return (
|
||||
<div className="bg-app-card rounded-lg shadow-sm p-6">
|
||||
<h2 className="text-2xl font-bold text-primary">Employee Dashboard</h2>
|
||||
<p className="text-text-muted mt-2">Welcome! Here you can mark your attendance and check your leave balance.</p>
|
||||
|
||||
<div className="mt-6 grid grid-cols-3 gap-4">
|
||||
<div className="p-4 border rounded-lg">
|
||||
<h3 className="font-semibold">Leave Balance</h3>
|
||||
<p className="text-2xl text-action">12 Days</p>
|
||||
</div>
|
||||
<div className="p-4 border rounded-lg">
|
||||
<h3 className="font-semibold">Attendance Status</h3>
|
||||
<button className="mt-2 px-4 py-1 bg-action text-white rounded">Check In</button>
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-default">Employee Dashboard</h2>
|
||||
<p className="text-text-muted mt-2">Welcome User!</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,22 +1,8 @@
|
||||
export default function ManagerDashboard() {
|
||||
return (
|
||||
<div className="bg-app-card rounded-lg shadow-sm p-6">
|
||||
<h2 className="text-2xl font-bold text-primary">Manager Dashboard</h2>
|
||||
<p className="text-text-muted mt-2">Welcome Manager! Review your team's pending requests below.</p>
|
||||
|
||||
<div className="mt-6">
|
||||
<h3 className="font-semibold text-lg">Pending Approvals</h3>
|
||||
<ul className="mt-2 space-y-2">
|
||||
<li className="p-3 border rounded flex justify-between items-center">
|
||||
<span>John Doe - Casual Leave (2 Days)</span>
|
||||
<button className="px-3 py-1 bg-primary text-white rounded text-sm">Review</button>
|
||||
</li>
|
||||
<li className="p-3 border rounded flex justify-between items-center">
|
||||
<span>Jane Smith - Late Regularization</span>
|
||||
<button className="px-3 py-1 bg-primary text-white rounded text-sm">Review</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-default">Manager Dashboard</h2>
|
||||
<p className="text-text-muted mt-2">Welcome Manager!</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
8
src/features/dashboard/pages/SuperAdminDashboard.tsx
Normal file
8
src/features/dashboard/pages/SuperAdminDashboard.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
export default function SuperAdminDashboard() {
|
||||
return (
|
||||
<div className="bg-app-card rounded-lg shadow-sm p-6">
|
||||
<h2 className="text-2xl font-bold text-default">Super Admin Dashboard</h2>
|
||||
<p className="text-text-muted mt-2">Welcome Super Admin!</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -3,6 +3,7 @@ import type { RootState } from '../../../store/store';
|
||||
import EmployeeDashboard from '../pages/EmployeeDashboard';
|
||||
import ManagerDashboard from '../pages/ManagerDashboard';
|
||||
import AdminDashboard from '../pages/AdminDashboard';
|
||||
import SuperAdminDashboard from '../pages/SuperAdminDashboard';
|
||||
|
||||
export default function DashboardRouter() {
|
||||
const role = useSelector((state: RootState) => state.role.currentRole);
|
||||
@ -13,8 +14,9 @@ export default function DashboardRouter() {
|
||||
case 'manager':
|
||||
return <ManagerDashboard />;
|
||||
case 'admin':
|
||||
case 'superadmin':
|
||||
return <AdminDashboard />;
|
||||
case 'superadmin':
|
||||
return <SuperAdminDashboard />;
|
||||
default:
|
||||
return <div>No role assigned</div>;
|
||||
}
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { RootState } from '../../../store/store';
|
||||
|
||||
export default function ManageEmployeesRouter() {
|
||||
const role = useSelector((state: RootState) => state.role.currentRole);
|
||||
|
||||
if (role === 'admin' || role === 'superadmin') {
|
||||
return (
|
||||
<div className="bg-app-card p-6 rounded-lg shadow">
|
||||
<h2 className="text-2xl font-bold text-primary">Manage Employees</h2>
|
||||
<p className="mt-2 text-text-muted">Add, edit, or remove employee records and manage branch assignments.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className="bg-app-card p-6 rounded-lg shadow text-absent-500">Access Denied: Admins only.</div>;
|
||||
}
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
@theme {
|
||||
/* Brand Colors */
|
||||
--color-default: #000000;
|
||||
--color-primary: #0a66c2;
|
||||
--color-primary-hover: #0854a0;
|
||||
--color-action: #f36f21;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user