Compare commits
No commits in common. "2567456e538a2dcfb46ce6d8b59fa6246e9e7c30" and "251fba0ed8bdf680a7cb147cf05b3536e8999ca4" have entirely different histories.
2567456e53
...
251fba0ed8
@ -1,14 +0,0 @@
|
||||
[
|
||||
{ "id": "LA001", "employeeId": "EMP001", "employeeName": "Alice Williams", "designation": "Software Engineer", "department": "IT", "managerName": "Bob Smith", "leaveType": "Casual Leave", "leaveDays": 2, "status": "Approved" },
|
||||
{ "id": "LA002", "employeeId": "EMP002", "employeeName": "Bob Smith", "designation": "Senior Software Engineer", "department": "IT", "managerName": "Charlie Brown", "leaveType": "Sick Leave", "leaveDays": 1, "status": "Pending" },
|
||||
{ "id": "LA003", "employeeId": "EMP003", "employeeName": "Charlie Brown", "designation": "Trainer", "department": "Training", "managerName": "Diana Prince", "leaveType": "Optional Holiday", "leaveDays": 1, "status": "Rejected" },
|
||||
{ "id": "LA004", "employeeId": "EMP004", "employeeName": "Diana Prince", "designation": "Data Analyst", "department": "Analytics", "managerName": "Evan Wright", "leaveType": "Casual Leave", "leaveDays": 3, "status": "Approved" },
|
||||
{ "id": "LA005", "employeeId": "EMP005", "employeeName": "Evan Wright", "designation": "QA Tester", "department": "Software Dev", "managerName": "Fiona Gallagher", "leaveType": "Sick Leave", "leaveDays": 2, "status": "Pending" },
|
||||
{ "id": "LA006", "employeeId": "EMP006", "employeeName": "Fiona Gallagher", "designation": "HR Executive", "department": "HR", "managerName": "George Costanza", "leaveType": "Casual Leave", "leaveDays": 1, "status": "Withdrawn" },
|
||||
{ "id": "LA007", "employeeId": "EMP007", "employeeName": "George Costanza", "designation": "Placement Officer", "department": "Placement", "managerName": "Hannah Abbott", "leaveType": "Optional Holiday", "leaveDays": 1, "status": "Approved" },
|
||||
{ "id": "LA008", "employeeId": "EMP008", "employeeName": "Hannah Abbott", "designation": "Support Engineer", "department": "Support", "managerName": "Ian Malcolm", "leaveType": "Sick Leave", "leaveDays": 4, "status": "Pending" },
|
||||
{ "id": "LA009", "employeeId": "EMP009", "employeeName": "Ian Malcolm", "designation": "Lead Trainer", "department": "Training", "managerName": "Jane Porter", "leaveType": "Casual Leave", "leaveDays": 2, "status": "Approved" },
|
||||
{ "id": "LA010", "employeeId": "EMP010", "employeeName": "Jane Porter", "designation": "Project Manager", "department": "Software Dev", "managerName": "Kevin Hart", "leaveType": "Optional Holiday", "leaveDays": 1, "status": "Rejected" },
|
||||
{ "id": "LA011", "employeeId": "EMP011", "employeeName": "Kevin Hart", "designation": "Software Engineer", "department": "IT", "managerName": "Laura Croft", "leaveType": "Sick Leave", "leaveDays": 1, "status": "Pending" },
|
||||
{ "id": "LA012", "employeeId": "EMP012", "employeeName": "Laura Croft", "designation": "Data Analyst", "department": "Analytics", "managerName": "Alice Williams", "leaveType": "Casual Leave", "leaveDays": 2, "status": "Approved" }
|
||||
]
|
||||
@ -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, Receipt // Added Receipt
|
||||
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, Receipt // Added Receipt
|
||||
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));
|
||||
}
|
||||
@ -71,6 +72,7 @@ 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}
|
||||
@ -99,6 +101,7 @@ export default function Sidebar({ isCollapsed, setIsCollapsed }: { isCollapsed:
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 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))}
|
||||
|
||||
@ -29,14 +29,7 @@ export const getSidebarItems = (role: UserRole): NavItem[] => {
|
||||
...(role === 'admin' ? [{ name: 'Regularization', icon: 'ClipboardCheck', path: '/attendance-summary/regularization' }] : [])
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Leave Summary',
|
||||
icon: 'ClipboardList',
|
||||
children: [
|
||||
{ name: 'Leave Applications', icon: 'FileText', path: '/leave-summary/applications' },
|
||||
{ name: 'Ledger Report', icon: 'Receipt', path: '/leave-summary/ledger' },
|
||||
]
|
||||
},
|
||||
{ name: 'Leave Summary', icon: 'ClipboardList', path: '/leave-summary' },
|
||||
{
|
||||
name: 'Master Data',
|
||||
icon: 'Database',
|
||||
|
||||
@ -4,7 +4,6 @@ import { fetchMockData, postMockData } from '../../../api/client';
|
||||
import type { RootState } from '../../../store/store';
|
||||
import type { UserRole } from '../../../store/roleSlice';
|
||||
import type { LeaveSummary, LeaveRequestPayload, LeaveHistoryRecord, ManagerLeaveApproval, ApprovalStatus } from '../types/leave';
|
||||
import type { LeaveApplicationSummary } from '../types/leave';
|
||||
|
||||
const LEAVE_ENDPOINTS: Record<UserRole, { summary: string, history: string }> = {
|
||||
employee: {
|
||||
@ -82,10 +81,3 @@ export const useUpdateLeaveApproval = () => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useLeaveApplications = () => {
|
||||
return useQuery<LeaveApplicationSummary[]>({
|
||||
queryKey: ['leaveApplications'],
|
||||
queryFn: () => fetchMockData<LeaveApplicationSummary[]>('leave/leave_applications.json'),
|
||||
});
|
||||
};
|
||||
@ -1,75 +0,0 @@
|
||||
import { X } from 'lucide-react';
|
||||
import type { LeaveApplicationSummary } from '../../types/leave';
|
||||
|
||||
interface LeaveApplicationModalProps {
|
||||
record: LeaveApplicationSummary;
|
||||
onClose: () => 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';
|
||||
case 'Withdrawn': return 'bg-app-muted text-text-secondary';
|
||||
default: return 'bg-app-muted text-text-secondary';
|
||||
}
|
||||
};
|
||||
|
||||
export default function LeaveApplicationModal({ record, onClose }: LeaveApplicationModalProps) {
|
||||
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 max-h-[90vh] overflow-y-auto">
|
||||
<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-6">Leave Application Details</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between border-b border-app-border pb-2">
|
||||
<span className="text-sm text-text-muted">Application ID</span>
|
||||
<span className="text-sm font-medium text-text-primary">{record.id}</span>
|
||||
</div>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-app-border pb-2">
|
||||
<span className="text-sm text-text-muted">Designation</span>
|
||||
<span className="text-sm font-medium text-text-primary">{record.designation}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-app-border pb-2">
|
||||
<span className="text-sm text-text-muted">Department</span>
|
||||
<span className="text-sm font-medium text-text-primary">{record.department}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-app-border pb-2">
|
||||
<span className="text-sm text-text-muted">Manager Name</span>
|
||||
<span className="text-sm font-medium text-text-primary">{record.managerName}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-app-border pb-2">
|
||||
<span className="text-sm text-text-muted">Leave Type</span>
|
||||
<span className="text-sm font-medium text-text-primary">{record.leaveType}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-app-border pb-2">
|
||||
<span className="text-sm text-text-muted">Leave Days</span>
|
||||
<span className="text-sm font-medium text-text-primary">{record.leaveDays}</span>
|
||||
</div>
|
||||
<div className="flex justify-between border-b border-app-border pb-2">
|
||||
<span className="text-sm text-text-muted">Status</span>
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getStatusClass(record.status)}`}>{record.status}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-6">
|
||||
<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,200 +0,0 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { ArrowUpDown, ArrowUp, ArrowDown, Eye, Search, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
|
||||
import type { LeaveApplicationSummary } from '../../types/leave';
|
||||
import LeaveApplicationModal from './LeaveApplicationModal';
|
||||
|
||||
interface LeaveApplicationsTableProps {
|
||||
records: LeaveApplicationSummary[];
|
||||
}
|
||||
|
||||
type SortKey = 'id' | 'employeeId' | 'employeeName' | 'designation' | 'department' | 'managerName' | 'leaveType' | 'leaveDays' | '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';
|
||||
case 'Withdrawn': return 'bg-app-muted text-text-secondary';
|
||||
default: return 'bg-app-muted text-text-secondary';
|
||||
}
|
||||
};
|
||||
|
||||
export default function LeaveApplicationsTable({ records }: LeaveApplicationsTableProps) {
|
||||
const [viewRecord, setViewRecord] = useState<LeaveApplicationSummary | null>(null);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
const [sortConfig, setSortConfig] = useState<{ key: SortKey; direction: SortDirection }>({
|
||||
key: 'employeeName', direction: 'ascending'
|
||||
});
|
||||
|
||||
const filteredData = useMemo(() => {
|
||||
if (!searchQuery) return records;
|
||||
return records.filter(record =>
|
||||
record.id.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
record.employeeId.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
record.employeeName.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
record.designation.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
record.department.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
record.managerName.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
record.leaveType.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
record.status.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
}, [records, searchQuery]);
|
||||
|
||||
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]);
|
||||
|
||||
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" />;
|
||||
};
|
||||
|
||||
const formatHeader = (key: string) => {
|
||||
if (key === 'id') return 'App ID';
|
||||
if (key === 'employeeId') return 'Emp ID';
|
||||
if (key === 'employeeName') return 'Employee Name';
|
||||
if (key === 'managerName') return 'Manager Name';
|
||||
if (key === 'leaveType') return 'Leave Type';
|
||||
if (key === 'leaveDays') return 'Leave Days';
|
||||
return key.charAt(0).toUpperCase() + key.slice(1);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-app-card p-6 rounded-lg shadow-sm">
|
||||
<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">Leave Applications</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, role..."
|
||||
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>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-app-border">
|
||||
<thead>
|
||||
<tr>
|
||||
{(['id', 'employeeId', 'employeeName', 'designation', 'department', 'managerName', 'leaveType', 'leaveDays', 'status'] as SortKey[]).map((key) => (
|
||||
<th key={key} onClick={() => requestSort(key)} className="px-6 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">
|
||||
{formatHeader(key)} {getSortIcon(key)}
|
||||
</div>
|
||||
</th>
|
||||
))}
|
||||
<th className="px-6 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-6 py-4 whitespace-nowrap text-sm text-text-primary">{record.id}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-text-secondary">{record.employeeId}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-text-secondary">{record.employeeName}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-text-secondary">{record.designation}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-text-secondary">{record.department}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-text-secondary">{record.managerName}</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">{record.leaveDays}</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm">
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getStatusClass(record.status)}`}>{record.status}</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap 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={10} className="px-6 py-8 text-center text-sm text-text-muted">No leave applications found.</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<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 && (
|
||||
<LeaveApplicationModal
|
||||
record={viewRecord}
|
||||
onClose={() => setViewRecord(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
import Loader from '../../../../components/ui/Loader';
|
||||
import ErrorState from '../../../../components/ui/ErrorState';
|
||||
import LeaveApplicationsTable from '../../components/leave/LeaveApplicationsTable';
|
||||
import { useLeaveApplications } from '../../api/useLeaveData';
|
||||
|
||||
export default function LeaveApplications() {
|
||||
const { data: records, isLoading, isError } = useLeaveApplications();
|
||||
|
||||
if (isLoading) return <Loader message="Loading leave applications..." />;
|
||||
if (isError) return <ErrorState message="Failed to load leave applications." />;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{records && <LeaveApplicationsTable records={records} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,19 +1,14 @@
|
||||
import { Routes, Route, Navigate } from 'react-router-dom';
|
||||
import LeaveApplications from '../pages/leave/LeaveApplications';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { RootState } from '../../../store/store';
|
||||
|
||||
const LedgerPlaceholder = () => (
|
||||
<div className="bg-app-card p-6 rounded-lg shadow-sm">
|
||||
<h2 className="text-xl font-bold text-text-primary">Ledger Report</h2>
|
||||
<p className="text-text-muted mt-2">Ledger Report UI will be implemented here.</p>
|
||||
</div>
|
||||
);
|
||||
const AdminLeaveSummary = () => <div className="bg-app-card p-6 rounded-lg shadow"><h2 className="text-2xl font-bold text-primary">Leave Summary Report</h2><p className="mt-2 text-text-muted">Analyze leave trends across the organization.</p></div>;
|
||||
|
||||
export default function LeaveSummaryRouter() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate to="/leave-summary/applications" replace />} />
|
||||
<Route path="/applications" element={<LeaveApplications />} />
|
||||
<Route path="/ledger" element={<LedgerPlaceholder />} />
|
||||
</Routes>
|
||||
);
|
||||
const role = useSelector((state: RootState) => state.role.currentRole);
|
||||
|
||||
if (role === 'admin' || role === 'superadmin') {
|
||||
return <AdminLeaveSummary />;
|
||||
}
|
||||
|
||||
return <div className="bg-app-card p-6 rounded-lg shadow text-absent-500">Access Denied: Admins only.</div>;
|
||||
}
|
||||
@ -52,16 +52,3 @@ export interface ManagerLeaveApproval {
|
||||
status: ApprovalStatus;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
// Add this to your existing leave types
|
||||
export interface LeaveApplicationSummary {
|
||||
id: string;
|
||||
employeeId: string;
|
||||
employeeName: string;
|
||||
designation: string;
|
||||
department: string;
|
||||
managerName: string;
|
||||
leaveType: string;
|
||||
leaveDays: number;
|
||||
status: 'Approved' | 'Pending' | 'Rejected' | 'Withdrawn';
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user