Compare commits

..

3 Commits

29 changed files with 221 additions and 50 deletions

View File

@ -0,0 +1,6 @@
[
{ "date": "2026-07-01", "timeIn": "08:50 AM", "timeOut": "06:00 PM", "loggedHours": "9h 10m", "status": "Present" },
{ "date": "2026-07-02", "timeIn": "09:00 AM", "timeOut": "06:00 PM", "loggedHours": "9h 00m", "status": "Present" },
{ "date": "2026-07-07", "timeIn": "-", "timeOut": "-", "loggedHours": "-", "status": "Holiday" },
{ "date": "2026-07-15", "timeIn": "-", "timeOut": "-", "loggedHours": "-", "status": "Absent" }
]

View File

@ -0,0 +1,7 @@
[
{ "date": "2026-07-01", "timeIn": "09:05 AM", "timeOut": "06:15 PM", "loggedHours": "9h 10m", "status": "Present" },
{ "date": "2026-07-02", "timeIn": "10:30 AM", "timeOut": "06:30 PM", "loggedHours": "8h 00m", "status": "Late" },
{ "date": "2026-07-03", "timeIn": "-", "timeOut": "-", "loggedHours": "-", "status": "Leave" },
{ "date": "2026-07-04", "timeIn": "09:00 AM", "timeOut": "05:05 PM", "loggedHours": "8h 05m", "status": "Present" },
{ "date": "2026-07-08", "timeIn": "09:15 AM", "timeOut": "02:00 PM", "loggedHours": "4h 45m", "status": "Half Day" }
]

View File

@ -0,0 +1,5 @@
[
{ "date": "2026-07-01", "timeIn": "09:30 AM", "timeOut": "06:30 PM", "loggedHours": "9h 00m", "status": "Late" },
{ "date": "2026-07-03", "timeIn": "08:45 AM", "timeOut": "05:30 PM", "loggedHours": "8h 45m", "status": "WFH" },
{ "date": "2026-07-10", "timeIn": "09:00 AM", "timeOut": "-", "loggedHours": "-", "status": "Mispunch" }
]

View File

@ -7,4 +7,4 @@ export const fetchMockData = async <T>(endpoint: string, delay = 500): Promise<T
throw new Error(`API Error: Failed to fetch ${endpoint}`); throw new Error(`API Error: Failed to fetch ${endpoint}`);
} }
return response.json() as Promise<T>; return response.json() as Promise<T>;
}; };

View File

@ -3,15 +3,15 @@ import { Routes, Route, Navigate } from 'react-router-dom';
import { useIsFetching } from '@tanstack/react-query'; import { useIsFetching } from '@tanstack/react-query';
import Sidebar from './Sidebar'; import Sidebar from './Sidebar';
import Navbar from './Navbar'; import Navbar from './Navbar';
import DashboardRouter from '../../features/routes/DashboardRouter'; import DashboardRouter from '../../features/dashboard/routes/DashboardRouter';
import AttendanceRouter from '../../features/routes/AttendanceRouter'; import AttendanceRouter from '../../features/attendance/routes/AttendanceRouter';
import LeaveRouter from '../../features/routes/LeaveRouter'; import LeaveRouter from '../../features/leave/routes/LeaveRouter';
import ApprovalRouter from '../../features/routes/ApprovalRouter'; import ApprovalRouter from '../../features/leave/routes/ApprovalRouter';
import AttendanceSummaryRouter from '../../features/routes/AttendanceSummaryRouter'; import AttendanceSummaryRouter from '../../features/attendance/routes/AttendanceSummaryRouter';
import LeaveSummaryRouter from '../../features/routes/LeaveSummaryRouter'; import LeaveSummaryRouter from '../../features/leave/routes/LeaveSummaryRouter';
import ManageEmployeesRouter from '../../features/routes/ManageEmployeesRouter'; import ManageEmployeesRouter from '../../features/dashboard/routes/ManageEmployeesRouter';
import ManageAdminsRouter from '../../features/routes/ManageAdminsRouter'; import ManageAdminsRouter from '../../features/dashboard/routes/ManageAdminsRouter';
import PolicyRouter from '../../features/routes/PolicyRouter'; import PolicyRouter from '../../features/dashboard/routes/PolicyRouter';
export default function MainLayout() { export default function MainLayout() {
const [isCollapsed, setIsCollapsed] = useState(false); const [isCollapsed, setIsCollapsed] = useState(false);

View File

@ -0,0 +1,29 @@
import { useQuery } from '@tanstack/react-query';
import { useSelector } from 'react-redux';
import { fetchMockData } from '../../../api/client';
import type { RootState } from '../../../store/store';
import type { UserRole } from '../../../store/roleSlice';
import type { AttendanceRecord } from '../../../types/attendance';
// 1. Define endpoints per role (Easy to swap to real API URLs later)
const ATTENDANCE_ENDPOINTS: Record<UserRole, string> = {
employee: 'employee_attendance',
manager: 'manager_attendance',
admin: 'admin_attendance',
superadmin: 'superadmin_attendance',
// Future real backend example:
// employee: '/api/v1/attendance/me',
// manager: '/api/v1/attendance/team',
};
// 2. Create a single dynamic hook
export const useAttendanceData = () => {
const role = useSelector((state: RootState) => state.role.currentRole);
return useQuery<AttendanceRecord[]>({
// Cache key includes the role so React Query caches them separately
queryKey: ['attendanceRecords', role],
queryFn: () => fetchMockData<AttendanceRecord[]>(ATTENDANCE_ENDPOINTS[role]),
});
};

View File

@ -1,6 +1,6 @@
import { useState, useMemo } from 'react'; import { useState, useMemo } from 'react';
import { ArrowUpDown, ArrowUp, ArrowDown, Calendar, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react'; import { ArrowUpDown, ArrowUp, ArrowDown, Calendar, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
import type { AttendanceRecord, SortKey } from '../types/attendance'; import type { AttendanceRecord, SortKey } from '../../../types/attendance';
type SortDirection = 'ascending' | 'descending'; type SortDirection = 'ascending' | 'descending';

View File

@ -0,0 +1,44 @@
import { useState, useMemo } from 'react';
import Calendar from '../components/Calendar';
import AttendanceRecordsTable from '../components/AttendanceRecordsTable';
import DailySummary from '../components/DailySummary';
import Loader from '../../../components/ui/Loader';
import ErrorState from '../../../components/ui/ErrorState';
import { useAttendanceData } from '../api/useAttendanceData';
const getTodayDate = () => {
const today = new Date();
return `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
};
export default function AdminAttendance() {
const [selectedDate, setSelectedDate] = useState(getTodayDate());
const { data: attendanceRecords, isLoading, isError } = useAttendanceData();
const calendarData = useMemo(() => {
const map: Record<string, any> = {};
if (attendanceRecords) {
attendanceRecords.forEach(record => {
map[record.date] = {
status: record.status, login: record.timeIn, logout: record.timeOut, hours: record.loggedHours
};
});
}
return map;
}, [attendanceRecords]);
if (isLoading) return <Loader message="Loading my attendance..." />;
if (isError) return <ErrorState message="Failed to load attendance data." />;
return (
<div className="flex flex-col gap-6">
<div className="flex flex-col lg:flex-row gap-6">
<div className="flex-1">
<Calendar data={calendarData} selectedDate={selectedDate} onDateSelect={setSelectedDate} />
</div>
<DailySummary selectedDate={selectedDate} data={calendarData[selectedDate] || null} />
</div>
{attendanceRecords && <AttendanceRecordsTable records={attendanceRecords} title="My Attendance Records" />}
</div>
);
}

View File

@ -1,10 +1,10 @@
import { useState, useMemo } from 'react'; import { useState, useMemo } from 'react';
import Calendar from '../../../components/Calendar'; import Calendar from '../components/Calendar';
import AttendanceRecordsTable from '../../../components/AttendanceRecordsTable'; import AttendanceRecordsTable from '../components/AttendanceRecordsTable';
import DailySummary from '../../../components/DailySummary'; import DailySummary from '../components/DailySummary';
import Loader from '../../../components/ui/Loader'; // <-- Changed from FadeLoader to Loader import Loader from '../../../components/ui/Loader';
import ErrorState from '../../../components/ui/ErrorState'; import ErrorState from '../../../components/ui/ErrorState';
import { useAttendanceData } from './hooks/useAttendanceData'; import { useAttendanceData } from '../api/useAttendanceData';
const getTodayDate = () => { const getTodayDate = () => {
const today = new Date(); const today = new Date();

View File

@ -0,0 +1,44 @@
import { useState, useMemo } from 'react';
import Calendar from '../components/Calendar';
import AttendanceRecordsTable from '../components/AttendanceRecordsTable';
import DailySummary from '../components/DailySummary';
import Loader from '../../../components/ui/Loader';
import ErrorState from '../../../components/ui/ErrorState';
import { useAttendanceData } from '../api/useAttendanceData';
const getTodayDate = () => {
const today = new Date();
return `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
};
export default function ManagerAttendance() {
const [selectedDate, setSelectedDate] = useState(getTodayDate());
const { data: attendanceRecords, isLoading, isError } = useAttendanceData();
const calendarData = useMemo(() => {
const map: Record<string, any> = {};
if (attendanceRecords) {
attendanceRecords.forEach(record => {
map[record.date] = {
status: record.status, login: record.timeIn, logout: record.timeOut, hours: record.loggedHours
};
});
}
return map;
}, [attendanceRecords]);
if (isLoading) return <Loader message="Loading my attendance..." />;
if (isError) return <ErrorState message="Failed to load attendance data." />;
return (
<div className="flex flex-col gap-6">
<div className="flex flex-col lg:flex-row gap-6">
<div className="flex-1">
<Calendar data={calendarData} selectedDate={selectedDate} onDateSelect={setSelectedDate} />
</div>
<DailySummary selectedDate={selectedDate} data={calendarData[selectedDate] || null} />
</div>
{attendanceRecords && <AttendanceRecordsTable records={attendanceRecords} title="My Attendance Records" />}
</div>
);
}

View File

@ -0,0 +1,44 @@
import { useState, useMemo } from 'react';
import Calendar from '../components/Calendar';
import AttendanceRecordsTable from '../components/AttendanceRecordsTable';
import DailySummary from '../components/DailySummary';
import Loader from '../../../components/ui/Loader';
import ErrorState from '../../../components/ui/ErrorState';
import { useAttendanceData } from '../api/useAttendanceData';
const getTodayDate = () => {
const today = new Date();
return `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
};
export default function SuperAdminAttendance() {
const [selectedDate, setSelectedDate] = useState(getTodayDate());
const { data: attendanceRecords, isLoading, isError } = useAttendanceData();
const calendarData = useMemo(() => {
const map: Record<string, any> = {};
if (attendanceRecords) {
attendanceRecords.forEach(record => {
map[record.date] = {
status: record.status, login: record.timeIn, logout: record.timeOut, hours: record.loggedHours
};
});
}
return map;
}, [attendanceRecords]);
if (isLoading) return <Loader message="Loading my attendance..." />;
if (isError) return <ErrorState message="Failed to load attendance data." />;
return (
<div className="flex flex-col gap-6">
<div className="flex flex-col lg:flex-row gap-6">
<div className="flex-1">
<Calendar data={calendarData} selectedDate={selectedDate} onDateSelect={setSelectedDate} />
</div>
<DailySummary selectedDate={selectedDate} data={calendarData[selectedDate] || null} />
</div>
{attendanceRecords && <AttendanceRecordsTable records={attendanceRecords} title="My Attendance Records" />}
</div>
);
}

View File

@ -0,0 +1,18 @@
import { useSelector } from 'react-redux';
import type { RootState } from '../../../store/store';
import EmployeeAttendance from '../pages/EmployeeAttendance';
import ManagerAttendance from '../pages/ManagerAttendance';
import AdminAttendance from '../pages/AdminAttendance';
import SuperAdminAttendance from '../pages/SuperAdminAttendance';
export default function AttendanceRouter() {
const role = useSelector((state: RootState) => state.role.currentRole);
switch (role) {
case 'employee': return <EmployeeAttendance />;
case 'manager': return <ManagerAttendance />;
case 'admin': return <AdminAttendance />;
case 'superadmin': return <SuperAdminAttendance />;
default: return <div>Unauthorized</div>;
}
}

View File

@ -1,8 +1,8 @@
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import type { RootState } from '../../store/store'; import type { RootState } from '../../../store/store';
import EmployeeDashboard from '../roles/employee/EmployeeDashboard'; import EmployeeDashboard from '../pages/EmployeeDashboard';
import ManagerDashboard from '../roles/manager/ManagerDashboard'; import ManagerDashboard from '../pages/ManagerDashboard';
import AdminDashboard from '../roles/admin/AdminDashboard'; import AdminDashboard from '../pages/AdminDashboard';
export default function DashboardRouter() { export default function DashboardRouter() {
const role = useSelector((state: RootState) => state.role.currentRole); const role = useSelector((state: RootState) => state.role.currentRole);

View File

@ -1,5 +1,5 @@
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import type { RootState } from '../../store/store'; import type { RootState } from '../../../store/store';
const ManagerApproval = () => <div className="bg-app-card p-6 rounded-lg shadow"><h2 className="text-2xl font-bold text-primary">Pending Approvals</h2><p className="mt-2 text-text-muted">Approve or reject team leave and regularization requests.</p></div>; const ManagerApproval = () => <div className="bg-app-card p-6 rounded-lg shadow"><h2 className="text-2xl font-bold text-primary">Pending Approvals</h2><p className="mt-2 text-text-muted">Approve or reject team leave and regularization requests.</p></div>;

View File

@ -1,5 +1,5 @@
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import type { RootState } from '../../store/store'; import type { RootState } from '../../../store/store';
const EmployeeLeave = () => <div className="bg-app-card p-6 rounded-lg shadow"><h2 className="text-2xl font-bold text-primary">My Leaves</h2><p className="mt-2 text-text-muted">Apply for leave and view your leave history.</p></div>; const EmployeeLeave = () => <div className="bg-app-card p-6 rounded-lg shadow"><h2 className="text-2xl font-bold text-primary">My Leaves</h2><p className="mt-2 text-text-muted">Apply for leave and view your leave history.</p></div>;
const ManagerLeave = () => <div className="bg-app-card p-6 rounded-lg shadow"><h2 className="text-2xl font-bold text-primary">Team Leave Calendar</h2><p className="mt-2 text-text-muted">See who is on leave in your team.</p></div>; const ManagerLeave = () => <div className="bg-app-card p-6 rounded-lg shadow"><h2 className="text-2xl font-bold text-primary">Team Leave Calendar</h2><p className="mt-2 text-text-muted">See who is on leave in your team.</p></div>;
@ -11,7 +11,7 @@ export default function LeaveRouter() {
switch (role) { switch (role) {
case 'employee': return <EmployeeLeave />; case 'employee': return <EmployeeLeave />;
case 'manager': return <ManagerLeave />; case 'manager': return <ManagerLeave />;
case 'admin': case 'admin': return <ManagerLeave />;
case 'superadmin': return <AdminLeave />; case 'superadmin': return <AdminLeave />;
default: return <div>Unauthorized</div>; default: return <div>Unauthorized</div>;
} }

View File

@ -1,10 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import { fetchMockData } from '../../../../api/client';
import type { AttendanceRecord } from '../../../../types/attendance';
export const useAttendanceData = () => {
return useQuery<AttendanceRecord[]>({
queryKey: ['attendanceRecords', 'employee'],
queryFn: () => fetchMockData<AttendanceRecord[]>('attendance'),
});
};

View File

@ -1,17 +0,0 @@
import { useSelector } from 'react-redux';
import type { RootState } from '../../store/store';
import EmployeeAttendance from '../roles/employee/EmployeeAttendance';
// import ManagerAttendance from '../../roles/manager/ManagerAttendance';
// import AdminAttendance from '../roles/admin/AdminAttendance';
export default function AttendanceRouter() {
const role = useSelector((state: RootState) => state.role.currentRole);
switch (role) {
case 'employee': return <EmployeeAttendance />;
// case 'manager': return <ManagerAttendance />;
// case 'admin':
// case 'superadmin': return <AdminAttendance />;
default: return <div>Unauthorized</div>;
}
}

View File

@ -4,6 +4,7 @@ export type AttendanceStatus = 'Present' | 'WFH' | 'Absent' | 'Leave' | 'Mispunc
export interface AttendanceRecord { export interface AttendanceRecord {
date: string; date: string;
employeeName?: string; // Optional for employee view, required for admin/manager
timeIn: string; timeIn: string;
timeOut: string; timeOut: string;
loggedHours: string; loggedHours: string;