Compare commits
No commits in common. "a24f0e9cbf235b3d36942e4809f3ef029d52eb74" and "31fb8bba85cab0cb6cb6ab2b6f84077d3f556c82" have entirely different histories.
a24f0e9cbf
...
31fb8bba85
@ -1,6 +0,0 @@
|
|||||||
[
|
|
||||||
{ "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" }
|
|
||||||
]
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
[
|
|
||||||
{ "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" }
|
|
||||||
]
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
[
|
|
||||||
{ "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" }
|
|
||||||
]
|
|
||||||
@ -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>;
|
||||||
};
|
};
|
||||||
@ -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';
|
||||||
|
|
||||||
@ -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/dashboard/routes/DashboardRouter';
|
import DashboardRouter from '../../features/routes/DashboardRouter';
|
||||||
import AttendanceRouter from '../../features/attendance/routes/AttendanceRouter';
|
import AttendanceRouter from '../../features/routes/AttendanceRouter';
|
||||||
import LeaveRouter from '../../features/leave/routes/LeaveRouter';
|
import LeaveRouter from '../../features/routes/LeaveRouter';
|
||||||
import ApprovalRouter from '../../features/leave/routes/ApprovalRouter';
|
import ApprovalRouter from '../../features/routes/ApprovalRouter';
|
||||||
import AttendanceSummaryRouter from '../../features/attendance/routes/AttendanceSummaryRouter';
|
import AttendanceSummaryRouter from '../../features/routes/AttendanceSummaryRouter';
|
||||||
import LeaveSummaryRouter from '../../features/leave/routes/LeaveSummaryRouter';
|
import LeaveSummaryRouter from '../../features/routes/LeaveSummaryRouter';
|
||||||
import ManageEmployeesRouter from '../../features/dashboard/routes/ManageEmployeesRouter';
|
import ManageEmployeesRouter from '../../features/routes/ManageEmployeesRouter';
|
||||||
import ManageAdminsRouter from '../../features/dashboard/routes/ManageAdminsRouter';
|
import ManageAdminsRouter from '../../features/routes/ManageAdminsRouter';
|
||||||
import PolicyRouter from '../../features/dashboard/routes/PolicyRouter';
|
import PolicyRouter from '../../features/routes/PolicyRouter';
|
||||||
|
|
||||||
export default function MainLayout() {
|
export default function MainLayout() {
|
||||||
const [isCollapsed, setIsCollapsed] = useState(false);
|
const [isCollapsed, setIsCollapsed] = useState(false);
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
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]),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
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>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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';
|
import Loader from '../../../components/ui/Loader'; // <-- Changed from FadeLoader to Loader
|
||||||
import ErrorState from '../../../components/ui/ErrorState';
|
import ErrorState from '../../../components/ui/ErrorState';
|
||||||
import { useAttendanceData } from '../api/useAttendanceData';
|
import { useAttendanceData } from './hooks/useAttendanceData';
|
||||||
|
|
||||||
const getTodayDate = () => {
|
const getTodayDate = () => {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
10
src/features/roles/employee/hooks/useAttendanceData.ts
Normal file
10
src/features/roles/employee/hooks/useAttendanceData.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
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'),
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -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>;
|
||||||
|
|
||||||
17
src/features/routes/AttendanceRouter.tsx
Normal file
17
src/features/routes/AttendanceRouter.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
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>;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 '../pages/EmployeeDashboard';
|
import EmployeeDashboard from '../roles/employee/EmployeeDashboard';
|
||||||
import ManagerDashboard from '../pages/ManagerDashboard';
|
import ManagerDashboard from '../roles/manager/ManagerDashboard';
|
||||||
import AdminDashboard from '../pages/AdminDashboard';
|
import AdminDashboard from '../roles/admin/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);
|
||||||
@ -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': return <ManagerLeave />;
|
case 'admin':
|
||||||
case 'superadmin': return <AdminLeave />;
|
case 'superadmin': return <AdminLeave />;
|
||||||
default: return <div>Unauthorized</div>;
|
default: return <div>Unauthorized</div>;
|
||||||
}
|
}
|
||||||
@ -4,7 +4,6 @@ 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;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user