Compare commits
No commits in common. "bc537f583f1f3cbadf9ce956681761249c21ebbf" and "2e79c232b0760776758df8c09ef20ba1771f47e5" have entirely different histories.
bc537f583f
...
2e79c232b0
@ -1,22 +1,222 @@
|
||||
[
|
||||
{ "id": "MA005", "employeeId": "EMP005", "employeeName": "Michael Brown", "leaveType": "Casual Leave", "applicationDate": "2026-07-16", "fromDate": "2026-07-28", "toDate": "2026-07-29", "leaveDays": 2, "status": "Pending", "reason": "Attending a family wedding." },
|
||||
{ "id": "MA006", "employeeId": "EMP006", "employeeName": "Emily Davis", "leaveType": "Optional Holiday", "applicationDate": "2026-07-17", "fromDate": "2026-08-01", "toDate": "2026-08-01", "leaveDays": 1, "status": "Pending", "reason": "Festival celebration." },
|
||||
{ "id": "MA007", "employeeId": "EMP007", "employeeName": "Chris Wilson", "leaveType": "Casual Leave", "applicationDate": "2026-07-18", "fromDate": "2026-08-03", "toDate": "2026-08-04", "leaveDays": 2, "status": "Approved", "reason": "Personal commitments." },
|
||||
{ "id": "MA008", "employeeId": "EMP008", "employeeName": "Sophia Johnson", "leaveType": "Optional Holiday", "applicationDate": "2026-07-19", "fromDate": "2026-08-05", "toDate": "2026-08-05", "leaveDays": 1, "status": "Rejected", "reason": "Festival observance." },
|
||||
{ "id": "MA009", "employeeId": "EMP009", "employeeName": "Daniel Lee", "leaveType": "Casual Leave", "applicationDate": "2026-07-20", "fromDate": "2026-08-06", "toDate": "2026-08-08", "leaveDays": 3, "status": "Pending", "reason": "Family emergency." },
|
||||
{ "id": "MA010", "employeeId": "EMP010", "employeeName": "Olivia Martin", "leaveType": "Optional Holiday", "applicationDate": "2026-07-21", "fromDate": "2026-08-10", "toDate": "2026-08-10", "leaveDays": 1, "status": "Approved", "reason": "Cultural celebration." },
|
||||
{ "id": "MA011", "employeeId": "EMP011", "employeeName": "James Taylor", "leaveType": "Casual Leave", "applicationDate": "2026-07-22", "fromDate": "2026-08-12", "toDate": "2026-08-13", "leaveDays": 2, "status": "Pending", "reason": "Home relocation." },
|
||||
{ "id": "MA012", "employeeId": "EMP012", "employeeName": "Emma Garcia", "leaveType": "Optional Holiday", "applicationDate": "2026-07-23", "fromDate": "2026-08-14", "toDate": "2026-08-14", "leaveDays": 1, "status": "Pending", "reason": "Religious observance." },
|
||||
{ "id": "MA013", "employeeId": "EMP013", "employeeName": "William Harris", "leaveType": "Casual Leave", "applicationDate": "2026-07-24", "fromDate": "2026-08-17", "toDate": "2026-08-18", "leaveDays": 2, "status": "Approved", "reason": "Health checkup." },
|
||||
{ "id": "MA014", "employeeId": "EMP014", "employeeName": "Ava Clark", "leaveType": "Optional Holiday", "applicationDate": "2026-07-25", "fromDate": "2026-08-19", "toDate": "2026-08-19", "leaveDays": 1, "status": "Pending", "reason": "Community event." },
|
||||
{ "id": "MA015", "employeeId": "EMP015", "employeeName": "Benjamin Lewis", "leaveType": "Casual Leave", "applicationDate": "2026-07-26", "fromDate": "2026-08-20", "toDate": "2026-08-22", "leaveDays": 3, "status": "Rejected", "reason": "Vacation with family." },
|
||||
{ "id": "MA016", "employeeId": "EMP016", "employeeName": "Mia Walker", "leaveType": "Optional Holiday", "applicationDate": "2026-07-27", "fromDate": "2026-08-24", "toDate": "2026-08-24", "leaveDays": 1, "status": "Approved", "reason": "Festival celebration." },
|
||||
{ "id": "MA017", "employeeId": "EMP017", "employeeName": "Ethan Hall", "leaveType": "Casual Leave", "applicationDate": "2026-07-28", "fromDate": "2026-08-25", "toDate": "2026-08-26", "leaveDays": 2, "status": "Pending", "reason": "Personal travel." },
|
||||
{ "id": "MA018", "employeeId": "EMP018", "employeeName": "Charlotte Allen", "leaveType": "Optional Holiday", "applicationDate": "2026-07-29", "fromDate": "2026-08-27", "toDate": "2026-08-27", "leaveDays": 1, "status": "Pending", "reason": "Special occasion." },
|
||||
{ "id": "MA019", "employeeId": "EMP019", "employeeName": "Alexander Young", "leaveType": "Casual Leave", "applicationDate": "2026-07-30", "fromDate": "2026-08-28", "toDate": "2026-08-30", "leaveDays": 3, "status": "Approved", "reason": "Family gathering." },
|
||||
{ "id": "MA020", "employeeId": "EMP020", "employeeName": "Amelia King", "leaveType": "Optional Holiday", "applicationDate": "2026-07-31", "fromDate": "2026-09-01", "toDate": "2026-09-01", "leaveDays": 1, "status": "Pending", "reason": "Festival leave." },
|
||||
{ "id": "MA021", "employeeId": "EMP021", "employeeName": "Matthew Scott", "leaveType": "Casual Leave", "applicationDate": "2026-08-01", "fromDate": "2026-09-02", "toDate": "2026-09-03", "leaveDays": 2, "status": "Rejected", "reason": "Personal errands." },
|
||||
{ "id": "MA022", "employeeId": "EMP022", "employeeName": "Harper Green", "leaveType": "Optional Holiday", "applicationDate": "2026-08-02", "fromDate": "2026-09-04", "toDate": "2026-09-04", "leaveDays": 1, "status": "Approved", "reason": "Religious holiday." },
|
||||
{ "id": "MA023", "employeeId": "EMP023", "employeeName": "David Baker", "leaveType": "Casual Leave", "applicationDate": "2026-08-03", "fromDate": "2026-09-07", "toDate": "2026-09-08", "leaveDays": 2, "status": "Pending", "reason": "Attending a seminar." },
|
||||
{ "id": "MA024", "employeeId": "EMP024", "employeeName": "Ella Adams", "leaveType": "Optional Holiday", "applicationDate": "2026-08-04", "fromDate": "2026-09-09", "toDate": "2026-09-09", "leaveDays": 1, "status": "Pending", "reason": "Festival observance." }
|
||||
{
|
||||
"id": "MA005",
|
||||
"employeeName": "Michael Brown",
|
||||
"leaveType": "Casual Leave",
|
||||
"applicationDate": "2026-07-16",
|
||||
"fromDate": "2026-07-28",
|
||||
"toDate": "2026-07-29",
|
||||
"leaveDays": 2,
|
||||
"status": "Pending",
|
||||
"reason": "Attending a family wedding."
|
||||
},
|
||||
{
|
||||
"id": "MA006",
|
||||
"employeeName": "Emily Davis",
|
||||
"leaveType": "Optional Holiday",
|
||||
"applicationDate": "2026-07-17",
|
||||
"fromDate": "2026-08-01",
|
||||
"toDate": "2026-08-01",
|
||||
"leaveDays": 1,
|
||||
"status": "Pending",
|
||||
"reason": "Festival celebration."
|
||||
},
|
||||
{
|
||||
"id": "MA007",
|
||||
"employeeName": "Chris Wilson",
|
||||
"leaveType": "Casual Leave",
|
||||
"applicationDate": "2026-07-18",
|
||||
"fromDate": "2026-08-03",
|
||||
"toDate": "2026-08-04",
|
||||
"leaveDays": 2,
|
||||
"status": "Approved",
|
||||
"reason": "Personal commitments."
|
||||
},
|
||||
{
|
||||
"id": "MA008",
|
||||
"employeeName": "Sophia Johnson",
|
||||
"leaveType": "Optional Holiday",
|
||||
"applicationDate": "2026-07-19",
|
||||
"fromDate": "2026-08-05",
|
||||
"toDate": "2026-08-05",
|
||||
"leaveDays": 1,
|
||||
"status": "Rejected",
|
||||
"reason": "Festival observance."
|
||||
},
|
||||
{
|
||||
"id": "MA009",
|
||||
"employeeName": "Daniel Lee",
|
||||
"leaveType": "Casual Leave",
|
||||
"applicationDate": "2026-07-20",
|
||||
"fromDate": "2026-08-06",
|
||||
"toDate": "2026-08-08",
|
||||
"leaveDays": 3,
|
||||
"status": "Pending",
|
||||
"reason": "Family emergency."
|
||||
},
|
||||
{
|
||||
"id": "MA010",
|
||||
"employeeName": "Olivia Martin",
|
||||
"leaveType": "Optional Holiday",
|
||||
"applicationDate": "2026-07-21",
|
||||
"fromDate": "2026-08-10",
|
||||
"toDate": "2026-08-10",
|
||||
"leaveDays": 1,
|
||||
"status": "Approved",
|
||||
"reason": "Cultural celebration."
|
||||
},
|
||||
{
|
||||
"id": "MA011",
|
||||
"employeeName": "James Taylor",
|
||||
"leaveType": "Casual Leave",
|
||||
"applicationDate": "2026-07-22",
|
||||
"fromDate": "2026-08-12",
|
||||
"toDate": "2026-08-13",
|
||||
"leaveDays": 2,
|
||||
"status": "Pending",
|
||||
"reason": "Home relocation."
|
||||
},
|
||||
{
|
||||
"id": "MA012",
|
||||
"employeeName": "Emma Garcia",
|
||||
"leaveType": "Optional Holiday",
|
||||
"applicationDate": "2026-07-23",
|
||||
"fromDate": "2026-08-14",
|
||||
"toDate": "2026-08-14",
|
||||
"leaveDays": 1,
|
||||
"status": "Pending",
|
||||
"reason": "Religious observance."
|
||||
},
|
||||
{
|
||||
"id": "MA013",
|
||||
"employeeName": "William Harris",
|
||||
"leaveType": "Casual Leave",
|
||||
"applicationDate": "2026-07-24",
|
||||
"fromDate": "2026-08-17",
|
||||
"toDate": "2026-08-18",
|
||||
"leaveDays": 2,
|
||||
"status": "Approved",
|
||||
"reason": "Health checkup."
|
||||
},
|
||||
{
|
||||
"id": "MA014",
|
||||
"employeeName": "Ava Clark",
|
||||
"leaveType": "Optional Holiday",
|
||||
"applicationDate": "2026-07-25",
|
||||
"fromDate": "2026-08-19",
|
||||
"toDate": "2026-08-19",
|
||||
"leaveDays": 1,
|
||||
"status": "Pending",
|
||||
"reason": "Community event."
|
||||
},
|
||||
{
|
||||
"id": "MA015",
|
||||
"employeeName": "Benjamin Lewis",
|
||||
"leaveType": "Casual Leave",
|
||||
"applicationDate": "2026-07-26",
|
||||
"fromDate": "2026-08-20",
|
||||
"toDate": "2026-08-22",
|
||||
"leaveDays": 3,
|
||||
"status": "Rejected",
|
||||
"reason": "Vacation with family."
|
||||
},
|
||||
{
|
||||
"id": "MA016",
|
||||
"employeeName": "Mia Walker",
|
||||
"leaveType": "Optional Holiday",
|
||||
"applicationDate": "2026-07-27",
|
||||
"fromDate": "2026-08-24",
|
||||
"toDate": "2026-08-24",
|
||||
"leaveDays": 1,
|
||||
"status": "Approved",
|
||||
"reason": "Festival celebration."
|
||||
},
|
||||
{
|
||||
"id": "MA017",
|
||||
"employeeName": "Ethan Hall",
|
||||
"leaveType": "Casual Leave",
|
||||
"applicationDate": "2026-07-28",
|
||||
"fromDate": "2026-08-25",
|
||||
"toDate": "2026-08-26",
|
||||
"leaveDays": 2,
|
||||
"status": "Pending",
|
||||
"reason": "Personal travel."
|
||||
},
|
||||
{
|
||||
"id": "MA018",
|
||||
"employeeName": "Charlotte Allen",
|
||||
"leaveType": "Optional Holiday",
|
||||
"applicationDate": "2026-07-29",
|
||||
"fromDate": "2026-08-27",
|
||||
"toDate": "2026-08-27",
|
||||
"leaveDays": 1,
|
||||
"status": "Pending",
|
||||
"reason": "Special occasion."
|
||||
},
|
||||
{
|
||||
"id": "MA019",
|
||||
"employeeName": "Alexander Young",
|
||||
"leaveType": "Casual Leave",
|
||||
"applicationDate": "2026-07-30",
|
||||
"fromDate": "2026-08-28",
|
||||
"toDate": "2026-08-30",
|
||||
"leaveDays": 3,
|
||||
"status": "Approved",
|
||||
"reason": "Family gathering."
|
||||
},
|
||||
{
|
||||
"id": "MA020",
|
||||
"employeeName": "Amelia King",
|
||||
"leaveType": "Optional Holiday",
|
||||
"applicationDate": "2026-07-31",
|
||||
"fromDate": "2026-09-01",
|
||||
"toDate": "2026-09-01",
|
||||
"leaveDays": 1,
|
||||
"status": "Pending",
|
||||
"reason": "Festival leave."
|
||||
},
|
||||
{
|
||||
"id": "MA021",
|
||||
"employeeName": "Matthew Scott",
|
||||
"leaveType": "Casual Leave",
|
||||
"applicationDate": "2026-08-01",
|
||||
"fromDate": "2026-09-02",
|
||||
"toDate": "2026-09-03",
|
||||
"leaveDays": 2,
|
||||
"status": "Rejected",
|
||||
"reason": "Personal errands."
|
||||
},
|
||||
{
|
||||
"id": "MA022",
|
||||
"employeeName": "Harper Green",
|
||||
"leaveType": "Optional Holiday",
|
||||
"applicationDate": "2026-08-02",
|
||||
"fromDate": "2026-09-04",
|
||||
"toDate": "2026-09-04",
|
||||
"leaveDays": 1,
|
||||
"status": "Approved",
|
||||
"reason": "Religious holiday."
|
||||
},
|
||||
{
|
||||
"id": "MA023",
|
||||
"employeeName": "David Baker",
|
||||
"leaveType": "Casual Leave",
|
||||
"applicationDate": "2026-08-03",
|
||||
"fromDate": "2026-09-07",
|
||||
"toDate": "2026-09-08",
|
||||
"leaveDays": 2,
|
||||
"status": "Pending",
|
||||
"reason": "Attending a seminar."
|
||||
},
|
||||
{
|
||||
"id": "MA024",
|
||||
"employeeName": "Ella Adams",
|
||||
"leaveType": "Optional Holiday",
|
||||
"applicationDate": "2026-08-04",
|
||||
"fromDate": "2026-09-09",
|
||||
"toDate": "2026-09-09",
|
||||
"leaveDays": 1,
|
||||
"status": "Pending",
|
||||
"reason": "Festival observance."
|
||||
}
|
||||
]
|
||||
@ -1,5 +0,0 @@
|
||||
[
|
||||
{ "id": "REG001", "empId": "EMP001", "empName": "Alice", "reqCheckIn": "09:30 AM", "reqCheckOut": "06:30 PM", "date": "2024-07-15", "reason": "Forgot to punch in due to system issue.", "status": "Pending" },
|
||||
{ "id": "REG002", "empId": "EMP003", "empName": "Charlie", "reqCheckIn": "10:00 AM", "reqCheckOut": "07:00 PM", "date": "2024-07-16", "reason": "Late arrival due to traffic.", "status": "Pending" },
|
||||
{ "id": "REG003", "empId": "EMP005", "empName": "Eve", "reqCheckIn": "08:45 AM", "reqCheckOut": "05:15 PM", "date": "2024-07-14", "reason": "Mispunch while swiping card.", "status": "Pending" }
|
||||
]
|
||||
@ -5,13 +5,13 @@ import { getSidebarItems, type NavItem } from './types/sidebarConfig';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import {
|
||||
LayoutDashboard, CalendarCheck, Plane, CheckCircle, BarChart3, ClipboardList,
|
||||
Menu, Users, ShieldUser, ScrollText, ChevronDown, FileText, CalendarDays, ClipboardCheck // Added ClipboardCheck
|
||||
Menu, Users, ShieldUser, ScrollText, ChevronDown, FileText, CalendarDays
|
||||
} 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 // Added ClipboardCheck
|
||||
Users, ShieldUser, ScrollText, FileText, CalendarDays
|
||||
};
|
||||
|
||||
export default function Sidebar({ isCollapsed, setIsCollapsed }: { isCollapsed: boolean; setIsCollapsed: (v: boolean) => void }) {
|
||||
@ -20,6 +20,7 @@ export default function Sidebar({ isCollapsed, setIsCollapsed }: { isCollapsed:
|
||||
const location = useLocation();
|
||||
const currentPath = location.pathname;
|
||||
|
||||
// Manage open dropdown state
|
||||
const [openDropdowns, setOpenDropdowns] = useState<Record<string, boolean>>({});
|
||||
|
||||
const toggleDropdown = (name: string) => {
|
||||
@ -31,10 +32,12 @@ export default function Sidebar({ isCollapsed, setIsCollapsed }: { isCollapsed:
|
||||
const hasChildren = item.children && item.children.length > 0;
|
||||
const isDropdownOpen = openDropdowns[item.name];
|
||||
|
||||
// Check if any child is active to keep parent highlighted
|
||||
const isChildActive = hasChildren && item.children!.some(child => currentPath === child.path);
|
||||
const isActive = currentPath === item.path;
|
||||
|
||||
if (hasChildren && isCollapsed) {
|
||||
// If collapsed, render children directly as normal items
|
||||
return item.children!.map(child => renderNavItem(child));
|
||||
}
|
||||
|
||||
@ -43,8 +46,9 @@ export default function Sidebar({ isCollapsed, setIsCollapsed }: { isCollapsed:
|
||||
{hasChildren ? (
|
||||
<button
|
||||
onClick={() => toggleDropdown(item.name)}
|
||||
className={`w-full flex items-center px-4 py-3 transition-colors duration-200 text-left ${isChildActive ? 'text-white' : 'text-blue-100 hover:bg-sidebar-hover hover:text-white'
|
||||
} border-l-4 ${isChildActive ? 'border-action' : 'border-transparent'}`}
|
||||
className={`w-full flex items-center px-4 py-3 transition-colors duration-200 text-left ${
|
||||
isChildActive ? 'text-white' : 'text-blue-100 hover:bg-sidebar-hover hover:text-white'
|
||||
} border-l-4 ${isChildActive ? 'border-action' : 'border-transparent'}`}
|
||||
>
|
||||
{Icon && <Icon size={20} className="flex-shrink-0" />}
|
||||
{!isCollapsed && <span className="ml-4 text-sm font-medium flex-grow">{item.name}</span>}
|
||||
@ -55,8 +59,9 @@ 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 ${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'
|
||||
}`}
|
||||
className={`flex items-center px-4 py-3 transition-colors duration-200 text-left ${
|
||||
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'
|
||||
}`}
|
||||
>
|
||||
{Icon && <Icon size={20} className="flex-shrink-0" />}
|
||||
{!isCollapsed && <span className="ml-4 text-sm font-medium">{item.name}</span>}
|
||||
|
||||
@ -3,7 +3,7 @@ import type { UserRole } from '../../../store/roleSlice';
|
||||
export interface NavItem {
|
||||
name: string;
|
||||
icon: string;
|
||||
path?: string;
|
||||
path?: string; // Optional for parent items
|
||||
children?: NavItem[];
|
||||
}
|
||||
|
||||
@ -18,40 +18,30 @@ export const getSidebarItems = (role: UserRole): NavItem[] => {
|
||||
return [...baseItems, { name: 'Approval', icon: 'CheckCircle', path: '/approval' }];
|
||||
}
|
||||
|
||||
// Define the items shared by both admin and superadmin
|
||||
const adminItems: NavItem[] = [
|
||||
{
|
||||
name: 'Attendance Summary',
|
||||
icon: 'BarChart3',
|
||||
children: [
|
||||
{ name: 'Dashboard', icon: 'LayoutDashboard', path: '/attendance-summary/dashboard' },
|
||||
{ name: 'Monthly Report', icon: 'FileText', path: '/attendance-summary/monthly' },
|
||||
{ name: 'Daily Report', icon: 'CalendarDays', path: '/attendance-summary/daily' },
|
||||
]
|
||||
},
|
||||
{ name: 'Leave Summary', icon: 'ClipboardList', path: '/leave-summary' },
|
||||
{ name: 'Manage Employees', icon: 'Users', path: '/manage-employees' },
|
||||
];
|
||||
|
||||
if (role === 'admin') {
|
||||
return [
|
||||
...baseItems,
|
||||
{
|
||||
name: 'Attendance Summary',
|
||||
icon: 'BarChart3',
|
||||
children: [
|
||||
{ name: 'Dashboard', icon: 'LayoutDashboard', path: '/attendance-summary/dashboard' },
|
||||
{ name: 'Monthly Report', icon: 'FileText', path: '/attendance-summary/monthly' },
|
||||
{ name: 'Daily Report', icon: 'CalendarDays', path: '/attendance-summary/daily' },
|
||||
{ name: 'Regularization', icon: 'ClipboardCheck', path: '/attendance-summary/regularization' }, // New Item
|
||||
]
|
||||
},
|
||||
{ name: 'Leave Summary', icon: 'ClipboardList', path: '/leave-summary' },
|
||||
{ name: 'Manage Employees', icon: 'Users', path: '/manage-employees' },
|
||||
];
|
||||
return [...baseItems, ...adminItems];
|
||||
}
|
||||
|
||||
if (role === 'superadmin') {
|
||||
return [
|
||||
...baseItems,
|
||||
{
|
||||
name: 'Attendance Summary',
|
||||
icon: 'BarChart3',
|
||||
children: [
|
||||
{ name: 'Dashboard', icon: 'LayoutDashboard', path: '/attendance-summary/dashboard' },
|
||||
{ name: 'Monthly Report', icon: 'FileText', path: '/attendance-summary/monthly' },
|
||||
{ name: 'Daily Report', icon: 'CalendarDays', path: '/attendance-summary/daily' },
|
||||
// SuperAdmin does not get Regularization based on your requirement
|
||||
]
|
||||
},
|
||||
{ name: 'Leave Summary', icon: 'ClipboardList', path: '/leave-summary' },
|
||||
...adminItems,
|
||||
{ name: 'Manage Admins', icon: 'ShieldUser', path: '/manage-admins' },
|
||||
{ name: 'Manage Employees', icon: 'Users', path: '/manage-employees' },
|
||||
{ name: 'Policy and Rules', icon: 'ScrollText', path: '/policies' },
|
||||
];
|
||||
}
|
||||
|
||||
@ -43,21 +43,3 @@ export const useAttendanceSummary = () => {
|
||||
queryFn: () => fetchMockData<SummaryRecord[]>('attendance/attendance_summary.json'),
|
||||
});
|
||||
};
|
||||
|
||||
export interface RegularizationRecord {
|
||||
id: string;
|
||||
empId: string;
|
||||
empName: string;
|
||||
reqCheckIn: string;
|
||||
reqCheckOut: string;
|
||||
date: string;
|
||||
reason: string;
|
||||
status: 'Pending' | 'Approved' | 'Rejected';
|
||||
}
|
||||
|
||||
export const useAdminRegularizations = () => {
|
||||
return useQuery<RegularizationRecord[]>({
|
||||
queryKey: ['adminRegularizations'],
|
||||
queryFn: () => fetchMockData<RegularizationRecord[]>('attendance/admin_regularizations.json'),
|
||||
});
|
||||
};
|
||||
@ -1,87 +0,0 @@
|
||||
import { X, Check, Ban } from 'lucide-react';
|
||||
import type { RegularizationRecord } from '../../api/useAttendanceData';
|
||||
|
||||
interface RegularizationModalProps {
|
||||
record: RegularizationRecord;
|
||||
onClose: () => void;
|
||||
onAction: (id: string, status: 'Approved' | 'Rejected') => void;
|
||||
}
|
||||
|
||||
const getStatusClass = (status: string) => {
|
||||
switch (status) {
|
||||
case 'Approved': return 'bg-present-100 text-present-700';
|
||||
case 'Pending': return 'bg-late-100 text-late-700';
|
||||
case 'Rejected': return 'bg-absent-100 text-absent-700';
|
||||
default: return 'bg-app-muted text-text-secondary';
|
||||
}
|
||||
};
|
||||
|
||||
export default function RegularizationModal({ record, onClose, onAction }: RegularizationModalProps) {
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-app-card rounded-lg shadow-xl w-full max-w-md p-6 relative">
|
||||
<button onClick={onClose} className="absolute top-4 right-4 text-text-muted hover:text-text-primary">
|
||||
<X size={24} />
|
||||
</button>
|
||||
<h3 className="text-xl font-bold text-text-primary mb-1">Regularization Request</h3>
|
||||
<p className="text-sm text-text-muted mb-6">Request ID: {record.id}</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Separated Employee ID */}
|
||||
<div className="flex justify-between border-b border-app-border pb-2">
|
||||
<span className="text-sm text-text-muted">Employee ID</span>
|
||||
<span className="text-sm font-medium text-text-primary">{record.empId}</span>
|
||||
</div>
|
||||
{/* Separated Employee Name */}
|
||||
<div className="flex justify-between border-b border-app-border pb-2">
|
||||
<span className="text-sm text-text-muted">Employee Name</span>
|
||||
<span className="text-sm font-medium text-text-primary">{record.empName}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-app-border pb-2">
|
||||
<span className="text-sm text-text-muted">Date</span>
|
||||
<span className="text-sm font-medium text-text-primary">{new Date(record.date + 'T00:00:00').toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-app-border pb-2">
|
||||
<span className="text-sm text-text-muted">Requested Check-In</span>
|
||||
<span className="text-sm font-medium text-text-primary">{record.reqCheckIn}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-app-border pb-2">
|
||||
<span className="text-sm text-text-muted">Requested Check-Out</span>
|
||||
<span className="text-sm font-medium text-text-primary">{record.reqCheckOut}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-app-border pb-2">
|
||||
<span className="text-sm text-text-muted">Current Status</span>
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getStatusClass(record.status)}`}>{record.status}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm text-text-muted block mb-1">Reason</span>
|
||||
<p className="text-sm text-text-secondary bg-app-muted p-3 rounded-lg">{record.reason}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-3 mt-6">
|
||||
{record.status === 'Pending' ? (
|
||||
<>
|
||||
<button
|
||||
onClick={() => onAction(record.id, 'Rejected')}
|
||||
className="flex items-center px-4 py-2 bg-absent-500 text-white rounded-lg hover:bg-absent-600 transition-colors text-sm font-medium"
|
||||
>
|
||||
<Ban size={16} className="mr-2" /> Reject
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onAction(record.id, 'Approved')}
|
||||
className="flex items-center px-4 py-2 bg-present-600 text-white rounded-lg hover:bg-present-700 transition-colors text-sm font-medium"
|
||||
>
|
||||
<Check size={16} className="mr-2" /> Approve
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<button onClick={onClose} className="px-4 py-2 bg-app-muted text-text-primary rounded-lg hover:bg-app-border transition-colors text-sm font-medium">
|
||||
Close
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,195 +0,0 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { ArrowUpDown, ArrowUp, ArrowDown, Eye, Search, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
|
||||
import type { RegularizationRecord } from '../../api/useAttendanceData';
|
||||
import RegularizationModal from './RegularizationModal';
|
||||
|
||||
interface RegularizationTableProps {
|
||||
records: RegularizationRecord[];
|
||||
onAction: (id: string, status: 'Approved' | 'Rejected') => void;
|
||||
}
|
||||
|
||||
type SortKey = 'empId' | 'empName' | 'date' | 'reqCheckIn' | 'reqCheckOut' | 'reason' | 'status';
|
||||
type SortDirection = 'ascending' | 'descending';
|
||||
|
||||
const getStatusClass = (status: string) => {
|
||||
switch (status) {
|
||||
case 'Approved': return 'bg-present-100 text-present-700';
|
||||
case 'Pending': return 'bg-late-100 text-late-700';
|
||||
case 'Rejected': return 'bg-absent-100 text-absent-700';
|
||||
default: return 'bg-app-muted text-text-secondary';
|
||||
}
|
||||
};
|
||||
|
||||
export default function RegularizationTable({ records, onAction }: RegularizationTableProps) {
|
||||
const [viewRecord, setViewRecord] = useState<RegularizationRecord | null>(null);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10); // Default 10 entries per page
|
||||
|
||||
// Default sort: date descending (latest first)
|
||||
const [sortConfig, setSortConfig] = useState<{ key: SortKey; direction: SortDirection }>({
|
||||
key: 'date',
|
||||
direction: 'descending'
|
||||
});
|
||||
|
||||
// Filter data based on search query
|
||||
const filteredData = useMemo(() => {
|
||||
if (!searchQuery) return records;
|
||||
return records.filter(record =>
|
||||
record.empId.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
record.empName.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
record.status.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
}, [records, searchQuery]);
|
||||
|
||||
// Sort filtered data
|
||||
const sortedData = useMemo(() => {
|
||||
const sortableData = [...filteredData];
|
||||
sortableData.sort((a, b) => {
|
||||
if (a[sortConfig.key] < b[sortConfig.key]) return sortConfig.direction === 'ascending' ? -1 : 1;
|
||||
if (a[sortConfig.key] > b[sortConfig.key]) return sortConfig.direction === 'ascending' ? 1 : -1;
|
||||
return 0;
|
||||
});
|
||||
return sortableData;
|
||||
}, [filteredData, sortConfig]);
|
||||
|
||||
// Pagination Logic
|
||||
const totalPages = Math.ceil(sortedData.length / pageSize);
|
||||
const isPaginationEnabled = sortedData.length > pageSize;
|
||||
|
||||
const paginatedData = useMemo(() => {
|
||||
const startIndex = (currentPage - 1) * pageSize;
|
||||
return sortedData.slice(startIndex, startIndex + pageSize);
|
||||
}, [sortedData, currentPage, pageSize]);
|
||||
|
||||
const pageNumbers = useMemo(() => {
|
||||
const pages = [];
|
||||
const maxVisiblePages = 5;
|
||||
let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
|
||||
let endPage = startPage + maxVisiblePages - 1;
|
||||
if (endPage > totalPages) {
|
||||
endPage = totalPages;
|
||||
startPage = Math.max(1, endPage - maxVisiblePages + 1);
|
||||
}
|
||||
for (let i = startPage; i <= endPage; i++) pages.push(i);
|
||||
return pages;
|
||||
}, [currentPage, totalPages]);
|
||||
|
||||
const requestSort = (key: SortKey) => {
|
||||
let direction: SortDirection = 'ascending';
|
||||
if (sortConfig.key === key && sortConfig.direction === 'ascending') direction = 'descending';
|
||||
setSortConfig({ key, direction });
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const handlePageSizeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setPageSize(Number(e.target.value));
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const getSortIcon = (key: SortKey) => {
|
||||
if (sortConfig.key !== key) return <ArrowUpDown size={14} className="ml-1 text-text-light" />;
|
||||
if (sortConfig.direction === 'ascending') return <ArrowUp size={14} className="ml-1 text-primary" />;
|
||||
return <ArrowDown size={14} className="ml-1 text-primary" />;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-app-card p-6 rounded-lg shadow-sm overflow-x-auto">
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between mb-6 gap-4">
|
||||
<h2 className="text-lg font-semibold text-text-primary">Pending Regularization Requests</h2>
|
||||
<div className="relative w-full md:w-64">
|
||||
<Search size={16} className="absolute left-3 top-1/2 -translate-y-1/2 text-text-light" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search id, name, status..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => { setSearchQuery(e.target.value); setCurrentPage(1); }}
|
||||
className="pl-9 pr-3 py-2 border border-app-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary text-text-secondary bg-app-card w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table className="min-w-full divide-y divide-app-border">
|
||||
<thead>
|
||||
<tr>
|
||||
{(['empId', 'empName', 'date', 'reqCheckIn', 'reqCheckOut', 'reason', 'status'] as SortKey[]).map((key) => (
|
||||
<th
|
||||
key={key}
|
||||
onClick={() => requestSort(key)}
|
||||
className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase tracking-wider cursor-pointer hover:bg-app-muted transition-colors"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
{key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase())} {getSortIcon(key)}
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase tracking-wider">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-app-border">
|
||||
{paginatedData.length > 0 ? (
|
||||
paginatedData.map((record) => (
|
||||
<tr key={record.id} className="hover:bg-app-muted transition-colors">
|
||||
<td className="px-4 py-3 text-sm text-text-primary">{record.empId}</td>
|
||||
<td className="px-4 py-3 text-sm text-text-primary">{record.empName}</td>
|
||||
<td className="px-4 py-3 text-sm text-text-secondary">{new Date(record.date + 'T00:00:00').toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })}</td>
|
||||
<td className="px-4 py-3 text-sm text-text-secondary">{record.reqCheckIn}</td>
|
||||
<td className="px-4 py-3 text-sm text-text-secondary">{record.reqCheckOut}</td>
|
||||
<td className="px-4 py-3 text-sm text-text-secondary max-w-xs truncate">{record.reason}</td>
|
||||
<td className="px-4 py-3 text-sm">
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getStatusClass(record.status)}`}>{record.status}</span>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm">
|
||||
<button onClick={() => setViewRecord(record)} className="text-primary hover:text-primary-hover flex items-center">
|
||||
<Eye size={16} className="mr-1" /> View
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={8} className="px-4 py-8 text-center text-sm text-text-muted">No matching regularization requests found.</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{/* Footer: Pagination & Page Size */}
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between mt-4 pt-4 border-t border-app-border gap-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-sm text-text-muted">Show</span>
|
||||
<select value={pageSize} onChange={handlePageSizeChange} className="px-2 py-1 border border-app-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary text-text-secondary bg-app-card">
|
||||
<option value={10}>10</option>
|
||||
<option value={15}>15</option>
|
||||
<option value={20}>20</option>
|
||||
</select>
|
||||
<span className="text-sm text-text-muted">entries</span>
|
||||
</div>
|
||||
|
||||
{isPaginationEnabled && totalPages > 1 && (
|
||||
<div className="flex items-center space-x-1">
|
||||
<span className="text-sm text-text-muted mr-2 hidden sm:inline">Page {currentPage} of {totalPages}</span>
|
||||
<button onClick={() => setCurrentPage(1)} disabled={currentPage === 1} className="p-2 border border-app-border rounded-lg text-text-secondary hover:bg-app-muted disabled:opacity-50 disabled:cursor-not-allowed transition-colors"><ChevronsLeft size={16} /></button>
|
||||
<button onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))} disabled={currentPage === 1} className="p-2 border border-app-border rounded-lg text-text-secondary hover:bg-app-muted disabled:opacity-50 disabled:cursor-not-allowed transition-colors"><ChevronLeft size={16} /></button>
|
||||
{pageNumbers.map(num => (
|
||||
<button key={num} onClick={() => setCurrentPage(num)} className={`w-8 h-8 flex items-center justify-center border rounded-lg text-sm transition-colors ${currentPage === num ? 'bg-primary text-white border-primary' : 'border-app-border text-text-secondary hover:bg-app-muted'}`}>{num}</button>
|
||||
))}
|
||||
<button onClick={() => setCurrentPage(prev => Math.min(prev + 1, totalPages))} disabled={currentPage === totalPages} className="p-2 border border-app-border rounded-lg text-text-secondary hover:bg-app-muted disabled:opacity-50 disabled:cursor-not-allowed transition-colors"><ChevronRight size={16} /></button>
|
||||
<button onClick={() => setCurrentPage(totalPages)} disabled={currentPage === totalPages} className="p-2 border border-app-border rounded-lg text-text-secondary hover:bg-app-muted disabled:opacity-50 disabled:cursor-not-allowed transition-colors"><ChevronsRight size={16} /></button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{viewRecord && (
|
||||
<RegularizationModal
|
||||
record={viewRecord}
|
||||
onClose={() => setViewRecord(null)}
|
||||
onAction={(id, status) => {
|
||||
onAction(id, status);
|
||||
setViewRecord(null);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import Loader from '../../../../components/ui/Loader';
|
||||
import ErrorState from '../../../../components/ui/ErrorState';
|
||||
import RegularizationTable from '../../components/summary/RegularizationTable';
|
||||
import { useAdminRegularizations, type RegularizationRecord } from '../../api/useAttendanceData';
|
||||
|
||||
export default function AdminRegularization() {
|
||||
const { data: records, isLoading, isError } = useAdminRegularizations();
|
||||
|
||||
// Local state to handle instant UI updates for approvals/rejections
|
||||
const [localOverrides, setLocalOverrides] = useState<Record<string, RegularizationRecord>>({});
|
||||
|
||||
const combinedRecords = useMemo(() => {
|
||||
if (!records) return [];
|
||||
return records.map(r => localOverrides[r.id] || r);
|
||||
}, [records, localOverrides]);
|
||||
|
||||
const handleAction = (id: string, status: 'Approved' | 'Rejected') => {
|
||||
const targetRecord = combinedRecords.find(r => r.id === id);
|
||||
if (!targetRecord) return;
|
||||
|
||||
setLocalOverrides(prev => ({
|
||||
...prev,
|
||||
[id]: { ...targetRecord, status }
|
||||
}));
|
||||
|
||||
toast.success(`Regularization request ${status.toLowerCase()} successfully.`);
|
||||
};
|
||||
|
||||
if (isLoading) return <Loader message="Loading regularization requests..." />;
|
||||
if (isError) return <ErrorState message="Failed to load regularization requests." />;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RegularizationTable records={combinedRecords} onAction={handleAction} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -2,7 +2,6 @@ import { Routes, Route, Navigate } from 'react-router-dom';
|
||||
import AttendanceDashboard from '../pages/summary/AttendanceDashboard';
|
||||
import MonthlyReport from '../pages/summary/MonthlyReport';
|
||||
import DailyReport from '../pages/summary/DailyReport';
|
||||
import AdminRegularization from '../pages/summary/AdminRegularization'; // New Import
|
||||
|
||||
export default function AttendanceSummaryRouter() {
|
||||
return (
|
||||
@ -11,7 +10,6 @@ export default function AttendanceSummaryRouter() {
|
||||
<Route path="/dashboard" element={<AttendanceDashboard />} />
|
||||
<Route path="/monthly" element={<MonthlyReport />} />
|
||||
<Route path="/daily" element={<DailyReport />} />
|
||||
<Route path="/regularization" element={<AdminRegularization />} /> {/* New Route */}
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
@ -17,6 +17,7 @@ const getStatusClass = (status: string) => {
|
||||
};
|
||||
|
||||
export default function ApprovalModal({ record, onClose, onAction }: ApprovalModalProps) {
|
||||
// Format dates to be highly readable (e.g., 15 Jul 2026)
|
||||
const formatReadableDate = (dateStr: string) => {
|
||||
return new Date(dateStr + 'T00:00:00').toLocaleDateString('en-US', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
};
|
||||
@ -31,12 +32,6 @@ export default function ApprovalModal({ record, onClose, onAction }: ApprovalMod
|
||||
<p className="text-sm text-text-muted mb-6">Application ID: {record.id}</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Separated Employee ID */}
|
||||
<div className="flex justify-between border-b border-app-border pb-2">
|
||||
<span className="text-sm text-text-muted">Employee ID</span>
|
||||
<span className="text-sm font-medium text-text-primary">{record.employeeId}</span>
|
||||
</div>
|
||||
{/* Separated Employee Name */}
|
||||
<div className="flex justify-between border-b border-app-border pb-2">
|
||||
<span className="text-sm text-text-muted">Employee Name</span>
|
||||
<span className="text-sm font-medium text-text-primary">{record.employeeName}</span>
|
||||
@ -50,6 +45,7 @@ export default function ApprovalModal({ record, onClose, onAction }: ApprovalMod
|
||||
<span className="text-sm font-medium text-text-primary">{formatReadableDate(record.applicationDate)}</span>
|
||||
</div>
|
||||
|
||||
{/* Updated Readable Duration */}
|
||||
<div className="flex justify-between border-b border-app-border pb-2">
|
||||
<span className="text-sm text-text-muted">Duration</span>
|
||||
<span className="text-sm font-medium text-text-primary text-right">
|
||||
@ -68,6 +64,7 @@ export default function ApprovalModal({ record, onClose, onAction }: ApprovalMod
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex justify-end space-x-3 mt-6">
|
||||
{record.status === 'Pending' ? (
|
||||
<>
|
||||
@ -8,7 +8,7 @@ interface ApprovalTableProps {
|
||||
onAction: (id: string, status: ApprovalStatus) => void;
|
||||
}
|
||||
|
||||
type SortKey = 'employeeId' | 'employeeName' | 'leaveType' | 'applicationDate' | 'leaveDays' | 'status';
|
||||
type SortKey = 'employeeName' | 'leaveType' | 'applicationDate' | 'leaveDays' | 'status';
|
||||
type SortDirection = 'ascending' | 'descending';
|
||||
|
||||
const getStatusClass = (status: string) => {
|
||||
@ -36,7 +36,6 @@ export default function ApprovalTable({ records, onAction }: ApprovalTableProps)
|
||||
const filteredData = useMemo(() => {
|
||||
if (!searchQuery) return records;
|
||||
return records.filter(record =>
|
||||
record.employeeId.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
record.employeeName.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
record.leaveType.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
record.status.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
@ -116,7 +115,7 @@ export default function ApprovalTable({ records, onAction }: ApprovalTableProps)
|
||||
<table className="min-w-full divide-y divide-app-border">
|
||||
<thead>
|
||||
<tr>
|
||||
{(['employeeId', 'employeeName', 'leaveType', 'applicationDate', 'leaveDays', 'status'] as SortKey[]).map((key) => (
|
||||
{(['employeeName', 'leaveType', 'applicationDate', 'leaveDays', 'status'] as SortKey[]).map((key) => (
|
||||
<th
|
||||
key={key}
|
||||
onClick={() => requestSort(key)}
|
||||
@ -134,7 +133,6 @@ export default function ApprovalTable({ records, onAction }: ApprovalTableProps)
|
||||
{paginatedData.length > 0 ? (
|
||||
paginatedData.map((record) => (
|
||||
<tr key={record.id} className="hover:bg-app-muted transition-colors">
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-text-primary">{record.employeeId}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-text-primary">{record.employeeName}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-text-secondary">{record.leaveType}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-text-secondary">{new Date(record.applicationDate + 'T00:00:00').toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric' })}</td>
|
||||
@ -151,7 +149,7 @@ export default function ApprovalTable({ records, onAction }: ApprovalTableProps)
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={7} className="px-6 py-8 text-center text-sm text-text-muted">No matching leave requests found.</td>
|
||||
<td colSpan={6} className="px-6 py-8 text-center text-sm text-text-muted">No matching leave requests found.</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
@ -1,7 +1,7 @@
|
||||
import { toast } from 'sonner';
|
||||
import Loader from '../../../../components/ui/Loader';
|
||||
import ErrorState from '../../../../components/ui/ErrorState';
|
||||
import ApprovalTable from '../../components/approval/ApprovalTable';
|
||||
import ApprovalTable from '../../components/Approval/ApprovalTable';
|
||||
import { useManagerApprovals, useUpdateLeaveApproval } from '../../api/useLeaveData';
|
||||
|
||||
export default function ManagerApproval() {
|
||||
@ -1,6 +1,6 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { RootState } from '../../../store/store';
|
||||
import ManagerApproval from '../pages/approval/ManagerApproval';
|
||||
import ManagerApproval from '../pages/Approval/ManagerApproval';
|
||||
|
||||
export default function ApprovalRouter() {
|
||||
const role = useSelector((state: RootState) => state.role.currentRole);
|
||||
|
||||
@ -39,10 +39,8 @@ export type LeaveSortKey = keyof LeaveHistoryRecord;
|
||||
// Add these to your existing types/leave.ts
|
||||
export type ApprovalStatus = 'Pending' | 'Approved' | 'Rejected';
|
||||
|
||||
// Add employeeId to the existing interface
|
||||
export interface ManagerLeaveApproval {
|
||||
id: string;
|
||||
employeeId: string; // Added this line
|
||||
employeeName: string;
|
||||
leaveType: string;
|
||||
applicationDate: string;
|
||||
@ -52,3 +50,4 @@ export interface ManagerLeaveApproval {
|
||||
status: ApprovalStatus;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user