Compare commits

..

2 Commits

11 changed files with 111 additions and 84 deletions

View 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>
);
}

View File

@ -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>
);
}
}

View File

@ -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">

View File

@ -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>

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View 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>
);
}

View File

@ -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>;
}

View File

@ -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>;
}

View File

@ -2,6 +2,7 @@
@theme {
/* Brand Colors */
--color-default: #000000;
--color-primary: #0a66c2;
--color-primary-hover: #0854a0;
--color-action: #f36f21;