Compare commits
2 Commits
3d1e83cd1e
...
1abe548c1b
| Author | SHA1 | Date | |
|---|---|---|---|
| 1abe548c1b | |||
| 5b728a8414 |
@ -4,7 +4,7 @@ server {
|
|||||||
|
|
||||||
location / {
|
location / {
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html index.htm;
|
index index.html index.html;
|
||||||
# This is crucial for React Router to work on page refresh
|
# This is crucial for React Router to work on page refresh
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
const iconMap: Record<string, any> = {
|
const iconMap: Record<string, any> = {
|
||||||
LayoutDashboard, CalendarCheck, Plane, CheckCircle, BarChart3, ClipboardList,
|
LayoutDashboard, CalendarCheck, Plane, CheckCircle, BarChart3, ClipboardList,
|
||||||
Users, ShieldUser, ScrollText, FileText, CalendarDays, ClipboardCheck,
|
Users, ShieldUser, ScrollText, FileText, CalendarDays, ClipboardCheck,
|
||||||
Database, Building, Network, Briefcase, Tag, ClipboardType, ClipboardMinus, History // ADDED History
|
Database, Building, Network, Briefcase, Tag, ClipboardType, ClipboardMinus, History
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Sidebar({ isCollapsed, setIsCollapsed }: { isCollapsed: boolean; setIsCollapsed: (v: boolean) => void }) {
|
export default function Sidebar({ isCollapsed, setIsCollapsed }: { isCollapsed: boolean; setIsCollapsed: (v: boolean) => void }) {
|
||||||
@ -139,7 +139,7 @@ export default function Sidebar({ isCollapsed, setIsCollapsed }: { isCollapsed:
|
|||||||
<img
|
<img
|
||||||
src="/logo.gif"
|
src="/logo.gif"
|
||||||
alt="Company Logo"
|
alt="Company Logo"
|
||||||
className="h-14 w-5xl max-w-75 cursor-pointer "
|
className="h-14 w-4xl max-w-75 cursor-pointer "
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -41,7 +41,7 @@ export const getSidebarItems = (role: UserRole): NavItem[] => {
|
|||||||
{ name: 'Attendance Summary', icon: 'CalendarCheck', path: '/attendance' },
|
{ name: 'Attendance Summary', icon: 'CalendarCheck', path: '/attendance' },
|
||||||
{ name: 'Daily Report', icon: 'CalendarDays', path: '/attendance-summary/daily' },
|
{ name: 'Daily Report', icon: 'CalendarDays', path: '/attendance-summary/daily' },
|
||||||
{ name: 'Monthly Report', icon: 'FileText', path: '/attendance-summary/monthly' },
|
{ name: 'Monthly Report', icon: 'FileText', path: '/attendance-summary/monthly' },
|
||||||
{ name: 'Regularization History', icon: 'History', path: '/attendance-summary/regularization-history' } ,
|
{ name: 'Regularization History', icon: 'History', path: '/attendance-summary/regularization-history' },
|
||||||
...(role === 'hrmanager' ? [{ name: 'Regularization', icon: 'ClipboardCheck', path: '/attendance-summary/regularization' }] : [])
|
...(role === 'hrmanager' ? [{ name: 'Regularization', icon: 'ClipboardCheck', path: '/attendance-summary/regularization' }] : [])
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { apiClient } from '../../../api/client';
|
import { apiClient } from '../../../api/client';
|
||||||
import type { AttendanceRecord, AttendanceStatus, AttendanceSummary, DashboardMetrics, DailyReportRecord, AdminReportRecord } from '../types/attendance';
|
import type { AttendanceRecord, AttendanceStatus, AttendanceSummary, DashboardMetrics, DailyReportRecord, AdminReportRecord, DailyReportFilters, AdminReportFilters, RegularizationPayload, RegularizationRecord } from '../types/attendance';
|
||||||
|
|
||||||
// Helper to map backend statuses to frontend statuses
|
// Helper to map backend statuses to frontend statuses
|
||||||
const mapStatus = (backendStatus: string): AttendanceStatus => {
|
const mapStatus = (backendStatus: string): AttendanceStatus => {
|
||||||
@ -15,7 +15,6 @@ const mapStatus = (backendStatus: string): AttendanceStatus => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1. Fetch Employee Range Report
|
|
||||||
export const useEmployeeRangeReport = (employeeId: number | null, fromDate: string, toDate: string) => {
|
export const useEmployeeRangeReport = (employeeId: number | null, fromDate: string, toDate: string) => {
|
||||||
return useQuery<AttendanceRecord[]>({
|
return useQuery<AttendanceRecord[]>({
|
||||||
queryKey: ['employeeRangeReport', employeeId, fromDate, toDate],
|
queryKey: ['employeeRangeReport', employeeId, fromDate, toDate],
|
||||||
@ -24,6 +23,7 @@ export const useEmployeeRangeReport = (employeeId: number | null, fromDate: stri
|
|||||||
params: { employee_id: employeeId, from_date: fromDate, to_date: toDate }
|
params: { employee_id: employeeId, from_date: fromDate, to_date: toDate }
|
||||||
});
|
});
|
||||||
const history = response.data.history || [];
|
const history = response.data.history || [];
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return history.map((h: any) => ({
|
return history.map((h: any) => ({
|
||||||
date: h.workDate,
|
date: h.workDate,
|
||||||
checkIn: h.checkIn ? h.checkIn.substring(0, 5) : '-',
|
checkIn: h.checkIn ? h.checkIn.substring(0, 5) : '-',
|
||||||
@ -37,7 +37,7 @@ export const useEmployeeRangeReport = (employeeId: number | null, fromDate: stri
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2. Fetch Employee Summary
|
|
||||||
export const useAttendanceSummary = (employeeId: number, startDate: string, endDate: string) => {
|
export const useAttendanceSummary = (employeeId: number, startDate: string, endDate: string) => {
|
||||||
return useQuery<AttendanceSummary>({
|
return useQuery<AttendanceSummary>({
|
||||||
queryKey: ['attendanceSummary', employeeId, startDate, endDate],
|
queryKey: ['attendanceSummary', employeeId, startDate, endDate],
|
||||||
@ -46,6 +46,7 @@ export const useAttendanceSummary = (employeeId: number, startDate: string, endD
|
|||||||
params: { employee_id: employeeId, start_date: startDate, end_date: endDate }
|
params: { employee_id: employeeId, start_date: startDate, end_date: endDate }
|
||||||
});
|
});
|
||||||
const data = response.data;
|
const data = response.data;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const history: AttendanceRecord[] = data.history.map((h: any) => ({
|
const history: AttendanceRecord[] = data.history.map((h: any) => ({
|
||||||
date: h.workDate,
|
date: h.workDate,
|
||||||
checkIn: h.checkIn ? h.checkIn.substring(0, 5) : '-',
|
checkIn: h.checkIn ? h.checkIn.substring(0, 5) : '-',
|
||||||
@ -60,7 +61,6 @@ export const useAttendanceSummary = (employeeId: number, startDate: string, endD
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. Fetch Dashboard Metrics
|
|
||||||
export const useDashboardMetrics = (date: string, companyId: string, branchId: string, departmentId: string) => {
|
export const useDashboardMetrics = (date: string, companyId: string, branchId: string, departmentId: string) => {
|
||||||
return useQuery<DashboardMetrics>({
|
return useQuery<DashboardMetrics>({
|
||||||
queryKey: ['dashboardMetrics', date, companyId, branchId, departmentId],
|
queryKey: ['dashboardMetrics', date, companyId, branchId, departmentId],
|
||||||
@ -84,16 +84,6 @@ export const useDashboardMetrics = (date: string, companyId: string, branchId: s
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 4. Fetch Daily Report
|
|
||||||
export interface DailyReportFilters {
|
|
||||||
date: string;
|
|
||||||
companyId: string;
|
|
||||||
branchId: string;
|
|
||||||
departmentId: string;
|
|
||||||
designationId: string;
|
|
||||||
employeeName: string;
|
|
||||||
employeeCode: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useDailyReport = (filters: DailyReportFilters) => {
|
export const useDailyReport = (filters: DailyReportFilters) => {
|
||||||
return useQuery<DailyReportRecord[]>({
|
return useQuery<DailyReportRecord[]>({
|
||||||
@ -116,17 +106,6 @@ export const useDailyReport = (filters: DailyReportFilters) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 5. Fetch Admin Range Report
|
|
||||||
export interface AdminReportFilters {
|
|
||||||
fromDate: string;
|
|
||||||
toDate: string;
|
|
||||||
companyId: string;
|
|
||||||
branchId: string;
|
|
||||||
departmentId: string;
|
|
||||||
designation: string;
|
|
||||||
employeeName: string;
|
|
||||||
employeeCode: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useAdminReport = (filters: AdminReportFilters) => {
|
export const useAdminReport = (filters: AdminReportFilters) => {
|
||||||
return useQuery<AdminReportRecord[]>({
|
return useQuery<AdminReportRecord[]>({
|
||||||
@ -150,16 +129,6 @@ export const useAdminReport = (filters: AdminReportFilters) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 6. Create Regularization Request
|
|
||||||
export interface RegularizationPayload {
|
|
||||||
attendance_id: number;
|
|
||||||
employee_id: number;
|
|
||||||
regularization_type: string;
|
|
||||||
target_date: string;
|
|
||||||
requested_check_in: string;
|
|
||||||
requested_check_out: string;
|
|
||||||
reason: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useCreateRegularization = () => {
|
export const useCreateRegularization = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@ -176,20 +145,6 @@ export const useCreateRegularization = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 7. Fetch HR Manager Regularizations
|
|
||||||
export interface RegularizationRecord {
|
|
||||||
id: string;
|
|
||||||
empId: string;
|
|
||||||
empName: string;
|
|
||||||
date: string;
|
|
||||||
regularizationType: string;
|
|
||||||
reqCheckIn: string;
|
|
||||||
reqCheckOut: string;
|
|
||||||
reason: string;
|
|
||||||
status: 'Pending' | 'Approved' | 'Rejected';
|
|
||||||
branchName: string;
|
|
||||||
reviewerName?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useHRManagerRegularizations = () => {
|
export const useHRManagerRegularizations = () => {
|
||||||
return useQuery<RegularizationRecord[]>({
|
return useQuery<RegularizationRecord[]>({
|
||||||
@ -198,6 +153,7 @@ export const useHRManagerRegularizations = () => {
|
|||||||
const response = await apiClient.get('/api/ams/attendance/regularize/pending');
|
const response = await apiClient.get('/api/ams/attendance/regularize/pending');
|
||||||
const rawData = response.data.data || [];
|
const rawData = response.data.data || [];
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return rawData.map((r: any) => {
|
return rawData.map((r: any) => {
|
||||||
const targetDate = r.targetDate ? new Date(r.targetDate) : new Date();
|
const targetDate = r.targetDate ? new Date(r.targetDate) : new Date();
|
||||||
const reqCheckInDate = r.requestedCheckIn ? new Date(r.requestedCheckIn) : new Date();
|
const reqCheckInDate = r.requestedCheckIn ? new Date(r.requestedCheckIn) : new Date();
|
||||||
@ -224,7 +180,6 @@ export const useHRManagerRegularizations = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 8. Review Regularization Request (Approve/Reject)
|
|
||||||
export const useReviewRegularization = () => {
|
export const useReviewRegularization = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@ -250,6 +205,7 @@ export const useRegularizationHistory = () => {
|
|||||||
const response = await apiClient.get('/api/ams/attendance/regularize/history');
|
const response = await apiClient.get('/api/ams/attendance/regularize/history');
|
||||||
const rawData = response.data.data || [];
|
const rawData = response.data.data || [];
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return rawData.map((r: any) => {
|
return rawData.map((r: any) => {
|
||||||
const targetDate = r.targetDate ? new Date(r.targetDate) : new Date();
|
const targetDate = r.targetDate ? new Date(r.targetDate) : new Date();
|
||||||
const reqCheckInDate = r.requestedCheckIn ? new Date(r.requestedCheckIn) : new Date();
|
const reqCheckInDate = r.requestedCheckIn ? new Date(r.requestedCheckIn) : new Date();
|
||||||
@ -276,10 +232,3 @@ export const useRegularizationHistory = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Update the interface
|
|
||||||
|
|
||||||
|
|
||||||
// Update the useHRManagerRegularizations hook mapping
|
|
||||||
|
|||||||
@ -19,7 +19,6 @@ interface DailySummaryProps {
|
|||||||
export default function DailySummary({ selectedDate, data, employeeId }: DailySummaryProps) {
|
export default function DailySummary({ selectedDate, data, employeeId }: DailySummaryProps) {
|
||||||
const selectedData = data || { status: 'No Data', login: '-', logout: '-', hours: '-', attendanceId: 0 };
|
const selectedData = data || { status: 'No Data', login: '-', logout: '-', hours: '-', attendanceId: 0 };
|
||||||
|
|
||||||
// Updated condition to include Half Day
|
|
||||||
const canRegularize = selectedData.status === 'Mispunch' || selectedData.status === 'Half Day';
|
const canRegularize = selectedData.status === 'Mispunch' || selectedData.status === 'Half Day';
|
||||||
|
|
||||||
const [isApplied, setIsApplied] = useState(false);
|
const [isApplied, setIsApplied] = useState(false);
|
||||||
@ -27,6 +26,7 @@ export default function DailySummary({ selectedDate, data, employeeId }: DailySu
|
|||||||
|
|
||||||
const createRegMutation = useCreateRegularization();
|
const createRegMutation = useCreateRegularization();
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const handleApplyRegularization = (payload: any) => {
|
const handleApplyRegularization = (payload: any) => {
|
||||||
createRegMutation.mutate(payload, {
|
createRegMutation.mutate(payload, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@ -34,6 +34,7 @@ export default function DailySummary({ selectedDate, data, employeeId }: DailySu
|
|||||||
setShowModal(false);
|
setShowModal(false);
|
||||||
toast.success("Regularization request submitted successfully.");
|
toast.success("Regularization request submitted successfully.");
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error?.response?.data?.message || "Failed to submit request.");
|
toast.error(error?.response?.data?.message || "Failed to submit request.");
|
||||||
}
|
}
|
||||||
@ -117,7 +118,7 @@ export default function DailySummary({ selectedDate, data, employeeId }: DailySu
|
|||||||
selectedDate={selectedDate}
|
selectedDate={selectedDate}
|
||||||
attendanceId={selectedData.attendanceId}
|
attendanceId={selectedData.attendanceId}
|
||||||
employeeId={employeeId}
|
employeeId={employeeId}
|
||||||
status={selectedData.status} // Pass the status to the modal
|
status={selectedData.status}
|
||||||
onClose={() => setShowModal(false)}
|
onClose={() => setShowModal(false)}
|
||||||
onApply={handleApplyRegularization}
|
onApply={handleApplyRegularization}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -5,8 +5,9 @@ interface RegularizationModalProps {
|
|||||||
selectedDate: string;
|
selectedDate: string;
|
||||||
attendanceId: number;
|
attendanceId: number;
|
||||||
employeeId: number;
|
employeeId: number;
|
||||||
status: string; // Added status prop
|
status: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onApply: (payload: any) => void;
|
onApply: (payload: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ export default function RegularizationModal({ selectedDate, attendanceId, employ
|
|||||||
const payload = {
|
const payload = {
|
||||||
attendance_id: attendanceId,
|
attendance_id: attendanceId,
|
||||||
employee_id: employeeId,
|
employee_id: employeeId,
|
||||||
regularization_type: backendType, // Dynamic type
|
regularization_type: backendType,
|
||||||
target_date: selectedDate,
|
target_date: selectedDate,
|
||||||
requested_check_in: `${selectedDate} ${regData.checkIn}:00`,
|
requested_check_in: `${selectedDate} ${regData.checkIn}:00`,
|
||||||
requested_check_out: `${selectedDate} ${regData.checkOut}:00`,
|
requested_check_out: `${selectedDate} ${regData.checkOut}:00`,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { useCompanies } from '../../../masterData/api/useCompanyData';
|
|||||||
import { useBranches } from '../../../masterData/api/useBranchData';
|
import { useBranches } from '../../../masterData/api/useBranchData';
|
||||||
import { useDepartments } from '../../../masterData/api/useDepartmentData';
|
import { useDepartments } from '../../../masterData/api/useDepartmentData';
|
||||||
import { useJobRoles } from '../../../masterData/api/useJobRoleData';
|
import { useJobRoles } from '../../../masterData/api/useJobRoleData';
|
||||||
import type { DailyReportFilters } from '../../api/useAttendanceData';
|
import type { DailyReportFilters } from '../../types/attendance';
|
||||||
|
|
||||||
interface DailyReportFiltersProps {
|
interface DailyReportFiltersProps {
|
||||||
filters: DailyReportFilters;
|
filters: DailyReportFilters;
|
||||||
|
|||||||
138
src/features/attendance/components/summary/DailyReportTable.tsx
Normal file
138
src/features/attendance/components/summary/DailyReportTable.tsx
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
// src/features/attendance/components/summary/DailyReportTable.tsx
|
||||||
|
import { useState, useMemo } from 'react';
|
||||||
|
import { Loader2, ChevronsLeft, ChevronLeft, ChevronRight, ChevronsRight } from 'lucide-react';
|
||||||
|
import type { DailyReportRecord } from '../../types/attendance';
|
||||||
|
|
||||||
|
interface DailyReportTableProps {
|
||||||
|
records: DailyReportRecord[];
|
||||||
|
date: string;
|
||||||
|
isLoading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusClass = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'FULL_DAY': return 'bg-present-100 text-present-700';
|
||||||
|
case 'LATE': return 'bg-late-100 text-late-700';
|
||||||
|
case 'ABSENT': return 'bg-absent-100 text-absent-700';
|
||||||
|
case 'MISPUNCH': return 'bg-mispunch-100 text-mispunch-700';
|
||||||
|
case 'HALF_DAY': return 'bg-halfday-100 text-halfday-700';
|
||||||
|
default: return 'bg-app-muted text-text-secondary';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DailyReportTable({ records, date, isLoading = false }: DailyReportTableProps) {
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [pageSize, setPageSize] = useState(20); // Default 20 entries
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(records.length / pageSize);
|
||||||
|
|
||||||
|
const paginatedData = useMemo(() => {
|
||||||
|
const startIndex = (currentPage - 1) * pageSize;
|
||||||
|
return records.slice(startIndex, startIndex + pageSize);
|
||||||
|
}, [records, 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 handlePageSizeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
setPageSize(Number(e.target.value));
|
||||||
|
setCurrentPage(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formattedDate = new Date(date + 'T00:00:00').toLocaleDateString('en-GB', { day: '2-digit', month: 'long', year: 'numeric' });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-app-card p-6 rounded-lg shadow-sm mt-6 overflow-x-auto">
|
||||||
|
<h3 className="text-lg font-semibold text-text-primary mb-4">
|
||||||
|
Attendance Report for {formattedDate} ({records.length})
|
||||||
|
</h3>
|
||||||
|
<table className="min-w-full divide-y divide-app-border">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Emp CODE</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Name</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Company</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Branch</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Department</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Job Role</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Check-In</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Check-Out</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Hours</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-app-border">
|
||||||
|
{isLoading ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={10} className="px-4 py-8 text-center text-sm text-text-muted">
|
||||||
|
<Loader2 size={20} className="animate-spin mx-auto text-primary" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : paginatedData.length > 0 ? (
|
||||||
|
paginatedData.map((record, index) => (
|
||||||
|
<tr key={index} className="hover:bg-app-muted transition-colors">
|
||||||
|
<td className="px-4 py-3 text-sm text-text-primary">{record.employeeCode}</td>
|
||||||
|
<td className="px-4 py-3 text-sm text-text-secondary">{record.fullName}</td>
|
||||||
|
<td className="px-4 py-3 text-sm text-text-secondary">{record.companyName || '-'}</td>
|
||||||
|
<td className="px-4 py-3 text-sm text-text-secondary">{record.branchName || '-'}</td>
|
||||||
|
<td className="px-4 py-3 text-sm text-text-secondary">{record.departmentName || '-'}</td>
|
||||||
|
<td className="px-4 py-3 text-sm text-text-secondary">{record.designation || '-'}</td>
|
||||||
|
<td className="px-4 py-3 text-sm text-text-secondary">{record.checkIn ? record.checkIn.substring(0, 5) : '-'}</td>
|
||||||
|
<td className="px-4 py-3 text-sm text-text-secondary">{record.checkOut ? record.checkOut.substring(0, 5) : '-'}</td>
|
||||||
|
<td className="px-4 py-3 text-sm text-text-secondary">{record.workedHours}</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.replace('_', ' ')}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={10} className="px-4 py-8 text-center text-sm text-text-muted">
|
||||||
|
No attendance records found for this date.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{/* Pagination Controls */}
|
||||||
|
<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={20}>20</option>
|
||||||
|
<option value={50}>50</option>
|
||||||
|
<option value={100}>100</option>
|
||||||
|
</select>
|
||||||
|
<span className="text-sm text-text-muted">entries</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@ import { useCompanies } from '../../../masterData/api/useCompanyData';
|
|||||||
import { useBranches } from '../../../masterData/api/useBranchData';
|
import { useBranches } from '../../../masterData/api/useBranchData';
|
||||||
import { useDepartments } from '../../../masterData/api/useDepartmentData';
|
import { useDepartments } from '../../../masterData/api/useDepartmentData';
|
||||||
import { useJobRoles } from '../../../masterData/api/useJobRoleData';
|
import { useJobRoles } from '../../../masterData/api/useJobRoleData';
|
||||||
import type { AdminReportFilters } from '../../api/useAttendanceData';
|
import type { AdminReportFilters } from '../../types/attendance';
|
||||||
|
|
||||||
interface MonthlyReportFiltersProps {
|
interface MonthlyReportFiltersProps {
|
||||||
filters: AdminReportFilters;
|
filters: AdminReportFilters;
|
||||||
|
|||||||
@ -0,0 +1,124 @@
|
|||||||
|
import { useState, useMemo } from 'react';
|
||||||
|
import { Loader2, ChevronsLeft, ChevronLeft, ChevronRight, ChevronsRight } from 'lucide-react';
|
||||||
|
import type { AdminReportRecord } from '../../types/attendance';
|
||||||
|
|
||||||
|
interface MonthlyReportTableProps {
|
||||||
|
records: AdminReportRecord[];
|
||||||
|
fromDate: string;
|
||||||
|
toDate: string;
|
||||||
|
isLoading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MonthlyReportTable({ records, fromDate, toDate, isLoading = false }: MonthlyReportTableProps) {
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [pageSize, setPageSize] = useState(20);
|
||||||
|
|
||||||
|
const totalPages = Math.ceil(records.length / pageSize);
|
||||||
|
|
||||||
|
const paginatedData = useMemo(() => {
|
||||||
|
const startIndex = (currentPage - 1) * pageSize;
|
||||||
|
return records.slice(startIndex, startIndex + pageSize);
|
||||||
|
}, [records, 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 handlePageSizeChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
setPageSize(Number(e.target.value));
|
||||||
|
setCurrentPage(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formattedFromDate = new Date(fromDate + 'T00:00:00').toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||||
|
const formattedToDate = new Date(toDate + 'T00:00:00').toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-app-card p-6 rounded-lg shadow-sm mt-6 overflow-x-auto">
|
||||||
|
<h3 className="text-lg font-semibold text-text-primary mb-4">
|
||||||
|
Monthly Attendance Report from {formattedFromDate} to {formattedToDate} ({records.length})
|
||||||
|
</h3>
|
||||||
|
<table className="min-w-full divide-y divide-app-border">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Emp CODE</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Name</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Company</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Branch</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Department</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Job Role</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Full Days</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Half Days</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Late Arrivals</th>
|
||||||
|
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Mispunches</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody className="divide-y divide-app-border">
|
||||||
|
{isLoading ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={10} className="px-4 py-8 text-center text-sm text-text-muted">
|
||||||
|
<Loader2 size={20} className="animate-spin mx-auto text-primary" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : paginatedData.length > 0 ? (
|
||||||
|
paginatedData.map((record, index) => (
|
||||||
|
<tr key={index} className="hover:bg-app-muted transition-colors">
|
||||||
|
<td className="px-4 py-3 text-sm text-text-primary">{record.employeeCode}</td>
|
||||||
|
<td className="px-4 py-3 text-sm text-text-secondary">{record.firstName} {record.lastName}</td>
|
||||||
|
<td className="px-4 py-3 text-sm text-text-secondary">{record.companyName || '-'}</td>
|
||||||
|
<td className="px-4 py-3 text-sm text-text-secondary">{record.branchName || '-'}</td>
|
||||||
|
<td className="px-4 py-3 text-sm text-text-secondary">{record.departmentName || '-'}</td>
|
||||||
|
<td className="px-4 py-3 text-sm text-text-secondary">{record.designation || '-'}</td>
|
||||||
|
<td className="px-4 py-3 text-sm text-text-secondary">{record.rangeMetrics?.fullDays || 0}</td>
|
||||||
|
<td className="px-4 py-3 text-sm text-text-secondary">{record.rangeMetrics?.halfDays || 0}</td>
|
||||||
|
<td className="px-4 py-3 text-sm text-text-secondary">{record.rangeMetrics?.lateArrivals || 0}</td>
|
||||||
|
<td className="px-4 py-3 text-sm text-text-secondary">{record.rangeMetrics?.mispunches || 0}</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={10} className="px-4 py-8 text-center text-sm text-text-muted">
|
||||||
|
No attendance records found for this period.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{/* Pagination Controls */}
|
||||||
|
<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={20}>20</option>
|
||||||
|
<option value={50}>50</option>
|
||||||
|
<option value={100}>100</option>
|
||||||
|
</select>
|
||||||
|
<span className="text-sm text-text-muted">entries</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
import type { RegularizationRecord } from '../../api/useAttendanceData';
|
import type { RegularizationRecord } from '../../types/attendance';
|
||||||
|
|
||||||
interface RegularizationHistoryModalProps {
|
interface RegularizationHistoryModalProps {
|
||||||
record: RegularizationRecord;
|
record: RegularizationRecord;
|
||||||
@ -22,10 +22,13 @@ export default function RegularizationHistoryModal({ record, onClose }: Regulari
|
|||||||
<button onClick={onClose} className="absolute top-4 right-4 text-text-muted hover:text-text-primary">
|
<button onClick={onClose} className="absolute top-4 right-4 text-text-muted hover:text-text-primary">
|
||||||
<X size={24} />
|
<X size={24} />
|
||||||
</button>
|
</button>
|
||||||
<h3 className="text-xl font-bold text-text-primary mb-1">Regularization Details</h3>
|
<h3 className="text-xl font-bold text-text-primary mb-6">Regularization Details</h3>
|
||||||
<p className="text-sm text-text-muted mb-6">Request ID: {record.id}</p>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
<div className="flex justify-between border-b border-app-border pb-2">
|
||||||
|
<span className="text-sm text-text-muted">Request 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">
|
<div className="flex justify-between border-b border-app-border pb-2">
|
||||||
<span className="text-sm text-text-muted">Emp Id</span>
|
<span className="text-sm text-text-muted">Emp Id</span>
|
||||||
<span className="text-sm font-medium text-text-primary">{record.empId}</span>
|
<span className="text-sm font-medium text-text-primary">{record.empId}</span>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { ArrowUpDown, ArrowUp, ArrowDown, Eye, Search, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
|
import { ArrowUpDown, ArrowUp, ArrowDown, Eye, Search, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
|
||||||
import type { RegularizationRecord } from '../../api/useAttendanceData';
|
import type { RegularizationRecord } from '../../types/attendance';
|
||||||
import RegularizationHistoryModal from './RegularizationHistoryModal'; // NEW IMPORT
|
import RegularizationHistoryModal from './RegularizationHistoryModal';
|
||||||
|
|
||||||
interface RegularizationHistoryTableProps {
|
interface RegularizationHistoryTableProps {
|
||||||
records: RegularizationRecord[];
|
records: RegularizationRecord[];
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { X, Check, Ban } from 'lucide-react';
|
import { X, Check, Ban } from 'lucide-react';
|
||||||
import type { RegularizationRecord } from '../../api/useAttendanceData';
|
import type { RegularizationRecord } from '../../types/attendance';
|
||||||
|
|
||||||
interface RegularizationModalProps {
|
interface RegularizationModalProps {
|
||||||
record: RegularizationRecord;
|
record: RegularizationRecord;
|
||||||
@ -23,10 +23,13 @@ export default function RegularizationModal({ record, onClose, onAction }: Regul
|
|||||||
<button onClick={onClose} className="absolute top-4 right-4 text-text-muted hover:text-text-primary">
|
<button onClick={onClose} className="absolute top-4 right-4 text-text-muted hover:text-text-primary">
|
||||||
<X size={24} />
|
<X size={24} />
|
||||||
</button>
|
</button>
|
||||||
<h3 className="text-xl font-bold text-text-primary mb-1">Regularization Request</h3>
|
<h3 className="text-xl font-bold text-text-primary mb-6">Regularization Request</h3>
|
||||||
<p className="text-sm text-text-muted mb-6">Request ID: {record.id}</p>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
<div className="flex justify-between border-b border-app-border pb-2">
|
||||||
|
<span className="text-sm text-text-muted">Request 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">
|
<div className="flex justify-between border-b border-app-border pb-2">
|
||||||
<span className="text-sm text-text-muted">Emp Id</span>
|
<span className="text-sm text-text-muted">Emp Id</span>
|
||||||
<span className="text-sm font-medium text-text-primary">{record.empId}</span>
|
<span className="text-sm font-medium text-text-primary">{record.empId}</span>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { ArrowUpDown, ArrowUp, ArrowDown, Eye, Search, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
|
import { ArrowUpDown, ArrowUp, ArrowDown, Eye, Search, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';
|
||||||
import type { RegularizationRecord } from '../../api/useAttendanceData';
|
import type { RegularizationRecord } from '../../types/attendance';
|
||||||
import RegularizationModal from './RegularizationModal';
|
import RegularizationModal from './RegularizationModal';
|
||||||
|
|
||||||
interface RegularizationTableProps {
|
interface RegularizationTableProps {
|
||||||
@ -8,7 +8,6 @@ interface RegularizationTableProps {
|
|||||||
onAction: (id: string, status: 'Approved' | 'Rejected') => void;
|
onAction: (id: string, status: 'Approved' | 'Rejected') => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updated SortKey to include branchName and regularizationType
|
|
||||||
type SortKey = 'empId' | 'empName' | 'branchName' | 'date' | 'regularizationType' | 'reqCheckIn' | 'reqCheckOut' | 'reason' | 'status';
|
type SortKey = 'empId' | 'empName' | 'branchName' | 'date' | 'regularizationType' | 'reqCheckIn' | 'reqCheckOut' | 'reason' | 'status';
|
||||||
type SortDirection = 'ascending' | 'descending';
|
type SortDirection = 'ascending' | 'descending';
|
||||||
|
|
||||||
|
|||||||
@ -59,7 +59,7 @@ export default function AttendanceDashboard() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Professional Grid Filter Bar */}
|
{/* Grid Filter Bar */}
|
||||||
<div className="bg-app-card p-4 rounded-lg shadow-sm grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div className="bg-app-card p-4 rounded-lg shadow-sm grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<label className="text-xs text-text-muted mb-1">Date</label>
|
<label className="text-xs text-text-muted mb-1">Date</label>
|
||||||
|
|||||||
@ -2,7 +2,9 @@ import { useState } from 'react';
|
|||||||
import { FileText, Download, Loader2 } from 'lucide-react';
|
import { FileText, Download, Loader2 } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import DailyReportFilters from '../components/summary/DailyReportFilters';
|
import DailyReportFilters from '../components/summary/DailyReportFilters';
|
||||||
import { useDailyReport, type DailyReportFilters as FilterValues } from '../api/useAttendanceData';
|
import DailyReportTable from '../components/summary/DailyReportTable';
|
||||||
|
import { useDailyReport } from '../api/useAttendanceData';
|
||||||
|
import { type DailyReportFilters as FilterValues } from '../types/attendance';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import type { RootState } from '../../../store/store';
|
import type { RootState } from '../../../store/store';
|
||||||
|
|
||||||
@ -37,7 +39,7 @@ export default function DailyReport() {
|
|||||||
designation: filters.designationId,
|
designation: filters.designationId,
|
||||||
employee_name: filters.employeeName,
|
employee_name: filters.employeeName,
|
||||||
employee_code: filters.employeeCode,
|
employee_code: filters.employeeCode,
|
||||||
file_type: downloadFormat, // Pass format to backend
|
file_type: downloadFormat,
|
||||||
}).toString();
|
}).toString();
|
||||||
|
|
||||||
const response = await fetch(`/api/ams/attendance/daily-report/export?${queryParams}`, {
|
const response = await fetch(`/api/ams/attendance/daily-report/export?${queryParams}`, {
|
||||||
@ -65,22 +67,12 @@ export default function DailyReport() {
|
|||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
|
|
||||||
toast.success('Report downloaded successfully.');
|
toast.success('Report downloaded successfully.');
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Failed to download report.');
|
toast.error('Failed to download report.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStatusClass = (status: string) => {
|
|
||||||
switch (status) {
|
|
||||||
case 'FULL_DAY': return 'bg-present-100 text-present-700';
|
|
||||||
case 'LATE': return 'bg-late-100 text-late-700';
|
|
||||||
case 'ABSENT': return 'bg-absent-100 text-absent-700';
|
|
||||||
case 'MISPUNCH': return 'bg-mispunch-100 text-mispunch-700';
|
|
||||||
case 'HALF_DAY': return 'bg-halfday-100 text-halfday-700';
|
|
||||||
default: return 'bg-app-muted text-text-secondary';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="bg-app-card p-6 rounded-lg shadow-sm flex flex-col md:flex-row md:items-center justify-between gap-4">
|
<div className="bg-app-card p-6 rounded-lg shadow-sm flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||||
@ -94,7 +86,6 @@ export default function DailyReport() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Download Controls */}
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<select
|
<select
|
||||||
value={downloadFormat}
|
value={downloadFormat}
|
||||||
@ -117,62 +108,11 @@ export default function DailyReport() {
|
|||||||
|
|
||||||
<DailyReportFilters filters={filters} onFilterChange={setFilters} />
|
<DailyReportFilters filters={filters} onFilterChange={setFilters} />
|
||||||
|
|
||||||
{/* Data Table */}
|
<DailyReportTable
|
||||||
<div className="bg-app-card p-6 rounded-lg shadow-sm overflow-x-auto">
|
records={reportData || []}
|
||||||
<h3 className="text-lg font-semibold text-text-primary mb-4">
|
date={filters.date}
|
||||||
Results for {new Date(filters.date + 'T00:00:00').toLocaleDateString('en-GB', { day: '2-digit', month: 'long', year: 'numeric' })} ({reportData?.length || 0})
|
isLoading={isLoading || isFetching}
|
||||||
</h3>
|
/>
|
||||||
<table className="min-w-full divide-y divide-app-border">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Emp CODE</th>
|
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Name</th>
|
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Status</th>
|
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Check-In</th>
|
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Check-Out</th>
|
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Worked Hours</th>
|
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Company</th>
|
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Branch</th>
|
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Department</th>
|
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Job Role</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y divide-app-border">
|
|
||||||
{isLoading ? (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={10} className="px-4 py-8 text-center text-sm text-text-muted">
|
|
||||||
<Loader2 size={20} className="animate-spin mx-auto text-primary" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
) : reportData && reportData.length > 0 ? (
|
|
||||||
reportData.map((record, index) => (
|
|
||||||
<tr key={index} className="hover:bg-app-muted transition-colors">
|
|
||||||
<td className="px-4 py-3 text-sm text-text-primary">{record.employeeCode}</td>
|
|
||||||
<td className="px-4 py-3 text-sm text-text-secondary">{record.fullName}</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.replace('_', ' ')}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td className="px-4 py-3 text-sm text-text-secondary">{record.checkIn ? record.checkIn.substring(0, 5) : '-'}</td>
|
|
||||||
<td className="px-4 py-3 text-sm text-text-secondary">{record.checkOut ? record.checkOut.substring(0, 5) : '-'}</td>
|
|
||||||
<td className="px-4 py-3 text-sm text-text-secondary">{record.workedHours}</td>
|
|
||||||
<td className="px-4 py-3 text-sm text-text-secondary">{record.companyName || '-'}</td>
|
|
||||||
<td className="px-4 py-3 text-sm text-text-secondary">{record.branchName || '-'}</td>
|
|
||||||
<td className="px-4 py-3 text-sm text-text-secondary">{record.departmentName || '-'}</td>
|
|
||||||
<td className="px-4 py-3 text-sm text-text-secondary">{record.designation || '-'}</td>
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={10} className="px-4 py-8 text-center text-sm text-text-muted">
|
|
||||||
No attendance records found for this date.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -14,6 +14,7 @@ export default function HRManagerRegularization() {
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(`Regularization request ${status.toLowerCase()} successfully.`);
|
toast.success(`Regularization request ${status.toLowerCase()} successfully.`);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error?.response?.data?.error || "Failed to update regularization status.");
|
toast.error(error?.response?.data?.error || "Failed to update regularization status.");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,9 @@ import { useState } from 'react';
|
|||||||
import { Download, FileText, Loader2 } from 'lucide-react';
|
import { Download, FileText, Loader2 } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import MonthlyReportFilters from '../components/summary/MonthlyReportFilters';
|
import MonthlyReportFilters from '../components/summary/MonthlyReportFilters';
|
||||||
import { useAdminReport, type AdminReportFilters } from '../api/useAttendanceData';
|
import MonthlyReportTable from '../components/summary/MonthlyReportTable';
|
||||||
|
import { type AdminReportFilters } from '../types/attendance';
|
||||||
|
import { useAdminReport } from '../api/useAttendanceData';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import type { RootState } from '../../../store/store';
|
import type { RootState } from '../../../store/store';
|
||||||
|
|
||||||
@ -72,6 +74,7 @@ export default function MonthlyReport() {
|
|||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
|
|
||||||
toast.success('Report downloaded successfully.');
|
toast.success('Report downloaded successfully.');
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Failed to download report.');
|
toast.error('Failed to download report.');
|
||||||
}
|
}
|
||||||
@ -112,53 +115,12 @@ export default function MonthlyReport() {
|
|||||||
|
|
||||||
<MonthlyReportFilters filters={filters} onFilterChange={setFilters} />
|
<MonthlyReportFilters filters={filters} onFilterChange={setFilters} />
|
||||||
|
|
||||||
<div className="bg-app-card p-6 rounded-lg shadow-sm overflow-x-auto">
|
<MonthlyReportTable
|
||||||
<h3 className="text-lg font-semibold text-text-primary mb-4">
|
records={reportData || []}
|
||||||
Filtered Results ({reportData?.length || 0})
|
fromDate={filters.fromDate}
|
||||||
</h3>
|
toDate={filters.toDate}
|
||||||
<table className="min-w-full divide-y divide-app-border">
|
isLoading={isLoading || isFetching}
|
||||||
<thead>
|
/>
|
||||||
<tr>
|
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Emp CODE</th>
|
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Name</th>
|
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Branch</th>
|
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Department</th>
|
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Full Days</th>
|
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Half Days</th>
|
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Late Arrivals</th>
|
|
||||||
<th className="px-4 py-3 text-left text-xs font-semibold text-text-muted uppercase">Mispunches</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="divide-y divide-app-border">
|
|
||||||
{isLoading ? (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={8} className="px-4 py-8 text-center text-sm text-text-muted">
|
|
||||||
<Loader2 size={20} className="animate-spin mx-auto text-primary" />
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
) : reportData && reportData.length > 0 ? (
|
|
||||||
reportData.map((record, index) => (
|
|
||||||
<tr key={index} className="hover:bg-app-muted transition-colors">
|
|
||||||
<td className="px-4 py-3 text-sm text-text-primary">{record.employeeCode}</td>
|
|
||||||
<td className="px-4 py-3 text-sm text-text-secondary">{record.firstName} {record.lastName}</td>
|
|
||||||
<td className="px-4 py-3 text-sm text-text-secondary">{record.branchName || '-'}</td>
|
|
||||||
<td className="px-4 py-3 text-sm text-text-secondary">{record.departmentName || '-'}</td>
|
|
||||||
<td className="px-4 py-3 text-sm text-text-secondary">{record.rangeMetrics?.fullDays || 0}</td>
|
|
||||||
<td className="px-4 py-3 text-sm text-text-secondary">{record.rangeMetrics?.halfDays || 0}</td>
|
|
||||||
<td className="px-4 py-3 text-sm text-text-secondary">{record.rangeMetrics?.lateArrivals || 0}</td>
|
|
||||||
<td className="px-4 py-3 text-sm text-text-secondary">{record.rangeMetrics?.mispunches || 0}</td>
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<tr>
|
|
||||||
<td colSpan={8} className="px-4 py-8 text-center text-sm text-text-muted">
|
|
||||||
No attendance records found for this period.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -51,8 +51,10 @@ export interface AdminReportRecord {
|
|||||||
employeeCode: string;
|
employeeCode: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
|
companyName: string;
|
||||||
branchName: string;
|
branchName: string;
|
||||||
departmentName: string;
|
departmentName: string;
|
||||||
|
designation: string;
|
||||||
rangeMetrics: {
|
rangeMetrics: {
|
||||||
fullDays: number;
|
fullDays: number;
|
||||||
halfDays: number;
|
halfDays: number;
|
||||||
@ -60,3 +62,49 @@ export interface AdminReportRecord {
|
|||||||
lateArrivals: number;
|
lateArrivals: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DailyReportFilters {
|
||||||
|
date: string;
|
||||||
|
companyId: string;
|
||||||
|
branchId: string;
|
||||||
|
departmentId: string;
|
||||||
|
designationId: string;
|
||||||
|
employeeName: string;
|
||||||
|
employeeCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminReportFilters {
|
||||||
|
fromDate: string;
|
||||||
|
toDate: string;
|
||||||
|
companyId: string;
|
||||||
|
branchId: string;
|
||||||
|
departmentId: string;
|
||||||
|
designation: string;
|
||||||
|
employeeName: string;
|
||||||
|
employeeCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegularizationPayload {
|
||||||
|
attendance_id: number;
|
||||||
|
employee_id: number;
|
||||||
|
regularization_type: string;
|
||||||
|
target_date: string;
|
||||||
|
requested_check_in: string;
|
||||||
|
requested_check_out: string;
|
||||||
|
reason: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface RegularizationRecord {
|
||||||
|
id: string;
|
||||||
|
empId: string;
|
||||||
|
empName: string;
|
||||||
|
date: string;
|
||||||
|
regularizationType: string;
|
||||||
|
reqCheckIn: string;
|
||||||
|
reqCheckOut: string;
|
||||||
|
reason: string;
|
||||||
|
status: 'Pending' | 'Approved' | 'Rejected';
|
||||||
|
branchName: string;
|
||||||
|
reviewerName?: string;
|
||||||
|
}
|
||||||
@ -1,86 +1,8 @@
|
|||||||
import { Building, Network, Users, ShieldUser, ArrowRight, ScrollText } from 'lucide-react';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
export default function DirectorDashboard() {
|
export default function DirectorDashboard() {
|
||||||
const stats = [
|
|
||||||
{ title: 'Total Companies', value: '3', icon: Building, color: 'text-primary', bg: 'bg-primary/10' },
|
|
||||||
{ title: 'Total Branches', value: '12', icon: Network, color: 'text-primary', bg: 'bg-primary/10' },
|
|
||||||
{ title: 'Global Employees', value: '450', icon: Users, color: 'text-present-700', bg: 'bg-present-100' },
|
|
||||||
{ title: 'Total HRManagers', value: '5', icon: ShieldUser, color: 'text-late-700', bg: 'bg-late-100' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const companies = [
|
|
||||||
{ name: 'CLRI', employees: 245, status: 'Active' },
|
|
||||||
{ name: 'TechNova Solutions', employees: 120, status: 'Active' },
|
|
||||||
{ name: 'Infotech Ltd', employees: 85, status: 'Active' },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="bg-app-card p-6 rounded-lg shadow-sm">
|
<div className="bg-app-card p-6 rounded-lg shadow-sm">
|
||||||
<h2 className="text-2xl font-bold text-text-primary">Welcome back, Director!</h2>
|
<h2 className="text-2xl font-bold text-text-primary">Welcome back, Director!</h2>
|
||||||
<p className="text-text-muted mt-1">Here is the global organizational overview.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
||||||
{stats.map((stat, index) => (
|
|
||||||
<div key={index} className="bg-app-card p-6 rounded-lg shadow-sm flex items-center justify-between transition-transform hover:scale-[1.02]">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-text-muted font-medium">{stat.title}</p>
|
|
||||||
<p className={`text-2xl font-bold mt-2 ${stat.color}`}>{stat.value}</p>
|
|
||||||
</div>
|
|
||||||
<div className={`p-3 rounded-full ${stat.bg}`}>
|
|
||||||
<stat.icon className={stat.color} size={28} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
||||||
<div className="bg-app-card p-6 rounded-lg shadow-sm">
|
|
||||||
<h3 className="text-lg font-semibold text-text-primary mb-4">Company Overview</h3>
|
|
||||||
<div className="space-y-3">
|
|
||||||
{companies.map((c, idx) => (
|
|
||||||
<div key={idx} className="flex items-center justify-between p-3 border border-app-border rounded-lg">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Building size={18} className="text-text-light mr-3" />
|
|
||||||
<span className="text-sm font-medium text-text-primary">{c.name}</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-4">
|
|
||||||
<span className="text-sm text-text-secondary">{c.employees} Employees</span>
|
|
||||||
<span className="px-2 py-1 rounded-full text-xs font-medium bg-present-100 text-present-700">{c.status}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="bg-app-card p-6 rounded-lg shadow-sm">
|
|
||||||
<h3 className="text-lg font-semibold text-text-primary mb-4">Director Actions</h3>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<Link to="/manage-hrmanagers" className="flex items-center justify-between p-4 border border-app-border rounded-lg hover:bg-app-muted transition-colors">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<ShieldUser size={20} className="text-primary mr-3" />
|
|
||||||
<span className="text-sm font-medium text-text-primary">Manage HR Managers</span>
|
|
||||||
</div>
|
|
||||||
<ArrowRight size={18} className="text-text-light" />
|
|
||||||
</Link>
|
|
||||||
<Link to="/policies" className="flex items-center justify-between p-4 border border-app-border rounded-lg hover:bg-app-muted transition-colors">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<ScrollText size={20} className="text-primary mr-3" />
|
|
||||||
<span className="text-sm font-medium text-text-primary">Policy and Rules</span>
|
|
||||||
</div>
|
|
||||||
<ArrowRight size={18} className="text-text-light" />
|
|
||||||
</Link>
|
|
||||||
<Link to="/master-data/company" className="flex items-center justify-between p-4 border border-app-border rounded-lg hover:bg-app-muted transition-colors">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Building size={20} className="text-primary mr-3" />
|
|
||||||
<span className="text-sm font-medium text-text-primary">Manage Master Data</span>
|
|
||||||
</div>
|
|
||||||
<ArrowRight size={18} className="text-text-light" />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,48 +1,8 @@
|
|||||||
import { CalendarCheck, Plane, Clock } from 'lucide-react';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
export default function EmployeeDashboard() {
|
export default function EmployeeDashboard() {
|
||||||
const stats = [
|
|
||||||
{ title: 'Attendance Status', value: 'Present', icon: CalendarCheck, color: 'text-present-700', bg: 'bg-present-100' },
|
|
||||||
{ title: 'Leave Balance', value: '12 Days', icon: Plane, color: 'text-primary', bg: 'bg-primary/10' },
|
|
||||||
{ title: 'Check-In Time', value: '09:05 AM', icon: Clock, color: 'text-late-700', bg: 'bg-late-100' },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="bg-app-card p-6 rounded-lg shadow-sm">
|
<div className="bg-app-card p-6 rounded-lg shadow-sm">
|
||||||
<h2 className="text-2xl font-bold text-text-primary">Welcome back, Employee!</h2>
|
<h2 className="text-2xl font-bold text-text-primary">Welcome back, Employee!</h2>
|
||||||
<p className="text-text-muted mt-1">Here is your attendance and leave summary for today.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
||||||
{stats.map((stat, index) => (
|
|
||||||
<div key={index} className="bg-app-card p-6 rounded-lg shadow-sm flex items-center justify-between transition-transform hover:scale-[1.02]">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-text-muted font-medium">{stat.title}</p>
|
|
||||||
<p className={`text-2xl font-bold mt-2 ${stat.color}`}>{stat.value}</p>
|
|
||||||
</div>
|
|
||||||
<div className={`p-3 rounded-full ${stat.bg}`}>
|
|
||||||
<stat.icon className={stat.color} size={28} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
||||||
<div className="bg-app-card p-6 rounded-lg shadow-sm">
|
|
||||||
<h3 className="text-lg font-semibold text-text-primary mb-4">Quick Actions</h3>
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<Link to="/leave" className="flex items-center p-4 border border-app-border rounded-lg hover:bg-app-muted transition-colors">
|
|
||||||
<Plane size={20} className="text-primary mr-3" />
|
|
||||||
<span className="text-sm font-medium text-text-primary">Check Leave Balance</span>
|
|
||||||
</Link>
|
|
||||||
<Link to="/attendance" className="flex items-center p-4 border border-app-border rounded-lg hover:bg-app-muted transition-colors">
|
|
||||||
<CalendarCheck size={20} className="text-present-700 mr-3" />
|
|
||||||
<span className="text-sm font-medium text-text-primary">View Attendance</span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,58 +1,8 @@
|
|||||||
import { Users, CalendarCheck, ClipboardCheck, Plane, Building } from 'lucide-react';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
export default function HRManagerDashboard() {
|
export default function HRManagerDashboard() {
|
||||||
const stats = [
|
|
||||||
{ title: 'Total Employees', value: '245', icon: Users, color: 'text-primary', bg: 'bg-primary/10' },
|
|
||||||
{ title: 'Present Today', value: '198', icon: CalendarCheck, color: 'text-present-700', bg: 'bg-present-100' },
|
|
||||||
{ title: 'Pending Regularizations', value: '4', icon: ClipboardCheck, color: 'text-late-700', bg: 'bg-late-100' },
|
|
||||||
{ title: 'On Leave', value: '12', icon: Plane, color: 'text-absent-700', bg: 'bg-absent-100' },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="bg-app-card p-6 rounded-lg shadow-sm">
|
<div className="bg-app-card p-6 rounded-lg shadow-sm">
|
||||||
<h2 className="text-2xl font-bold text-text-primary">Welcome back, HRManager!</h2>
|
<h2 className="text-2xl font-bold text-text-primary">Welcome back, HRManager!</h2>
|
||||||
<p className="text-text-muted mt-1">Here is the organizational overview for today.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
||||||
{stats.map((stat, index) => (
|
|
||||||
<div key={index} className="bg-app-card p-6 rounded-lg shadow-sm flex items-center justify-between transition-transform hover:scale-[1.02]">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-text-muted font-medium">{stat.title}</p>
|
|
||||||
<p className={`text-2xl font-bold mt-2 ${stat.color}`}>{stat.value}</p>
|
|
||||||
</div>
|
|
||||||
<div className={`p-3 rounded-full ${stat.bg}`}>
|
|
||||||
<stat.icon className={stat.color} size={28} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
||||||
<div className="bg-app-card p-6 rounded-lg shadow-sm">
|
|
||||||
<h3 className="text-lg font-semibold text-text-primary mb-4">Quick Links</h3>
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
<Link to="/master-data/employees" className="flex items-center p-4 border border-app-border rounded-lg hover:bg-app-muted transition-colors">
|
|
||||||
<Users size={20} className="text-primary mr-3" />
|
|
||||||
<span className="text-sm font-medium text-text-primary">Manage Employees</span>
|
|
||||||
</Link>
|
|
||||||
<Link to="/attendance-summary/dashboard" className="flex items-center p-4 border border-app-border rounded-lg hover:bg-app-muted transition-colors">
|
|
||||||
<CalendarCheck size={20} className="text-present-700 mr-3" />
|
|
||||||
<span className="text-sm font-medium text-text-primary">Attendance Summary</span>
|
|
||||||
</Link>
|
|
||||||
<Link to="/leave-summary/applications" className="flex items-center p-4 border border-app-border rounded-lg hover:bg-app-muted transition-colors">
|
|
||||||
<ClipboardCheck size={20} className="text-late-700 mr-3" />
|
|
||||||
<span className="text-sm font-medium text-text-primary">Leave Applications</span>
|
|
||||||
</Link>
|
|
||||||
<Link to="/master-data/company" className="flex items-center p-4 border border-app-border rounded-lg hover:bg-app-muted transition-colors">
|
|
||||||
<Building size={20} className="text-primary mr-3" />
|
|
||||||
<span className="text-sm font-medium text-text-primary">Master Data</span>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,61 +1,8 @@
|
|||||||
import { CheckCircle, Users, Plane, ArrowRight } from 'lucide-react';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
export default function ManagerDashboard() {
|
export default function ManagerDashboard() {
|
||||||
const stats = [
|
|
||||||
{ title: 'Pending Approvals', value: '5', icon: CheckCircle, color: 'text-late-700', bg: 'bg-late-100' },
|
|
||||||
{ title: 'Team Present Today', value: '18 / 20', icon: Users, color: 'text-present-700', bg: 'bg-present-100' },
|
|
||||||
{ title: 'Team On Leave', value: '2', icon: Plane, color: 'text-absent-700', bg: 'bg-absent-100' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const recentApprovals = [
|
|
||||||
{ id: 'LA001', name: 'Alice Williams', type: 'Casual Leave', days: 2 },
|
|
||||||
{ id: 'LA002', name: 'Bob Smith', type: 'Sick Leave', days: 1 },
|
|
||||||
{ id: 'LA004', name: 'Diana Prince', type: 'Casual Leave', days: 3 },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="bg-app-card p-6 rounded-lg shadow-sm">
|
<div className="bg-app-card p-6 rounded-lg shadow-sm">
|
||||||
<h2 className="text-2xl font-bold text-text-primary">Welcome back, Manager!</h2>
|
<h2 className="text-2xl font-bold text-text-primary">Welcome back, Manager!</h2>
|
||||||
<p className="text-text-muted mt-1">Here is your team overview for today.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
||||||
{stats.map((stat, index) => (
|
|
||||||
<div key={index} className="bg-app-card p-6 rounded-lg shadow-sm flex items-center justify-between transition-transform hover:scale-[1.02]">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm text-text-muted font-medium">{stat.title}</p>
|
|
||||||
<p className={`text-2xl font-bold mt-2 ${stat.color}`}>{stat.value}</p>
|
|
||||||
</div>
|
|
||||||
<div className={`p-3 rounded-full ${stat.bg}`}>
|
|
||||||
<stat.icon className={stat.color} size={28} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
||||||
<div className="bg-app-card p-6 rounded-lg shadow-sm">
|
|
||||||
<div className="flex justify-between items-center mb-4">
|
|
||||||
<h3 className="text-lg font-semibold text-text-primary">Pending Leave Requests</h3>
|
|
||||||
<Link to="/approval" className="text-primary text-sm font-medium hover:text-primary-hover flex items-center">
|
|
||||||
View All <ArrowRight size={14} className="ml-1" />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-3">
|
|
||||||
{recentApprovals.map((req) => (
|
|
||||||
<div key={req.id} className="flex items-center justify-between p-3 border border-app-border rounded-lg">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm font-medium text-text-primary">{req.name}</p>
|
|
||||||
<p className="text-xs text-text-muted">{req.type} • {req.days} Day(s)</p>
|
|
||||||
</div>
|
|
||||||
<Link to="/approval" className="px-3 py-1 bg-primary/10 text-primary text-xs font-medium rounded-full">Review</Link>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
// src/features/leave/api/useLeaveData.ts
|
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { apiClient, fetchMockData } from '../../../api/client';
|
import { apiClient } from '../../../api/client';
|
||||||
import type { RootState } from '../../../store/store';
|
import type { RootState } from '../../../store/store';
|
||||||
import type {
|
import type {
|
||||||
LeaveSummary,
|
LeaveSummary,
|
||||||
@ -12,15 +11,9 @@ import type {
|
|||||||
LeaveApplicationSummary,
|
LeaveApplicationSummary,
|
||||||
LeaveBalanceRecord
|
LeaveBalanceRecord
|
||||||
} from '../types/leave';
|
} from '../types/leave';
|
||||||
// Add this import at the top of the file
|
|
||||||
import { useEmployeeById } from '../../masterData/api/useEmployeeData';
|
import { useEmployeeById } from '../../masterData/api/useEmployeeData';
|
||||||
|
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// 1. ADMIN SETUP & CONFIGURATION
|
|
||||||
// ==========================================
|
|
||||||
|
|
||||||
// Create Leave Type
|
|
||||||
export const useCreateLeaveType = () => {
|
export const useCreateLeaveType = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@ -34,8 +27,8 @@ export const useCreateLeaveType = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get Leave Types
|
|
||||||
export const useLeaveTypes = (companyId: number = 1) => {
|
export const useLeaveTypes = (companyId: number = 1) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return useQuery<any[]>({
|
return useQuery<any[]>({
|
||||||
queryKey: ['leaveTypes', companyId],
|
queryKey: ['leaveTypes', companyId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@ -45,10 +38,11 @@ export const useLeaveTypes = (companyId: number = 1) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create Policy Rule
|
|
||||||
export const useCreatePolicyRule = () => {
|
export const useCreatePolicyRule = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
mutationFn: async (data: any) => {
|
mutationFn: async (data: any) => {
|
||||||
const response = await apiClient.post('/api/lms/config/rules', data);
|
const response = await apiClient.post('/api/lms/config/rules', data);
|
||||||
return response.data;
|
return response.data;
|
||||||
@ -59,10 +53,11 @@ export const useCreatePolicyRule = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create Company Holiday (Bulk)
|
|
||||||
export const useCreateCompanyHoliday = () => {
|
export const useCreateCompanyHoliday = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
mutationFn: async (holidays: any[]) => {
|
mutationFn: async (holidays: any[]) => {
|
||||||
const response = await apiClient.post('/api/lms/config/holidays', holidays);
|
const response = await apiClient.post('/api/lms/config/holidays', holidays);
|
||||||
return response.data;
|
return response.data;
|
||||||
@ -73,7 +68,6 @@ export const useCreateCompanyHoliday = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update Work Settings
|
|
||||||
export const useUpdateWorkSettings = () => {
|
export const useUpdateWorkSettings = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (data: { company_id: number; branch_id: number; weekly_off_days: number[]; effective_from: string }) => {
|
mutationFn: async (data: { company_id: number; branch_id: number; weekly_off_days: number[]; effective_from: string }) => {
|
||||||
@ -83,12 +77,6 @@ export const useUpdateWorkSettings = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// 2. EMPLOYEE ACTIONS
|
|
||||||
// ==========================================
|
|
||||||
|
|
||||||
// Get Leave Balances (Summary)
|
|
||||||
export const useLeaveSummary = () => {
|
export const useLeaveSummary = () => {
|
||||||
return useQuery<LeaveSummary[]>({
|
return useQuery<LeaveSummary[]>({
|
||||||
queryKey: ['leaveSummary'],
|
queryKey: ['leaveSummary'],
|
||||||
@ -97,10 +85,9 @@ export const useLeaveSummary = () => {
|
|||||||
params: { year: new Date().getFullYear() }
|
params: { year: new Date().getFullYear() }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Map backend response to frontend LeaveSummary interface
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
// Inside useLeaveSummary
|
|
||||||
return (response.data.balances || []).map((b: any) => ({
|
return (response.data.balances || []).map((b: any) => ({
|
||||||
leaveTypeId: b.leaveTypeId, // Ensure this is mapped!
|
leaveTypeId: b.leaveTypeId,
|
||||||
leaveType: b.leaveTypeName,
|
leaveType: b.leaveTypeName,
|
||||||
credited: b.grantedDays,
|
credited: b.grantedDays,
|
||||||
utilized: b.usedDays,
|
utilized: b.usedDays,
|
||||||
@ -111,10 +98,6 @@ export const useLeaveSummary = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// ... (keep other hooks)
|
|
||||||
|
|
||||||
// Apply for Leave
|
|
||||||
export const useApplyLeave = () => {
|
export const useApplyLeave = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const employeeId = useSelector((state: RootState) => Number(state.role.mockUserId));
|
const employeeId = useSelector((state: RootState) => Number(state.role.mockUserId));
|
||||||
@ -128,12 +111,11 @@ export const useApplyLeave = () => {
|
|||||||
throw new Error("User profile is still loading. Please try again in a moment.");
|
throw new Error("User profile is still loading. Please try again in a moment.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// All values are now fully dynamic based on the user's real data
|
|
||||||
const reqPayload = {
|
const reqPayload = {
|
||||||
employee_id: employeeId,
|
employee_id: employeeId,
|
||||||
leave_type_id: payload.leaveTypeId,
|
leave_type_id: payload.leaveTypeId,
|
||||||
company_id: empDetail.companyId, // Dynamic from EMS
|
company_id: empDetail.companyId,
|
||||||
branch_id: empDetail.branchId, // Dynamic from EMS
|
branch_id: empDetail.branchId,
|
||||||
date_from: payload.fromDate,
|
date_from: payload.fromDate,
|
||||||
date_to: payload.toDate,
|
date_to: payload.toDate,
|
||||||
is_half_day: payload.leaveDays === 0.5,
|
is_half_day: payload.leaveDays === 0.5,
|
||||||
@ -149,9 +131,6 @@ export const useApplyLeave = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// ... (keep the rest of the file)
|
|
||||||
|
|
||||||
// Get Leave History
|
|
||||||
export const useLeaveHistory = () => {
|
export const useLeaveHistory = () => {
|
||||||
return useQuery<LeaveHistoryRecord[]>({
|
return useQuery<LeaveHistoryRecord[]>({
|
||||||
queryKey: ['leaveHistory'],
|
queryKey: ['leaveHistory'],
|
||||||
@ -160,11 +139,11 @@ export const useLeaveHistory = () => {
|
|||||||
params: { year: new Date().getFullYear() }
|
params: { year: new Date().getFullYear() }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Map backend response to frontend LeaveHistoryRecord interface
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return (response.data.history || []).map((h: any) => ({
|
return (response.data.history || []).map((h: any) => ({
|
||||||
id: String(h.applicationId),
|
id: String(h.applicationId),
|
||||||
leaveType: h.leaveTypeName,
|
leaveType: h.leaveTypeName,
|
||||||
applicationDate: new Date().toISOString().split('T')[0], // Backend doesn't return applicationDate yet
|
applicationDate: new Date().toISOString().split('T')[0],
|
||||||
fromDate: h.dateFrom,
|
fromDate: h.dateFrom,
|
||||||
toDate: h.dateTo,
|
toDate: h.dateTo,
|
||||||
leaveDays: h.numberOfDays,
|
leaveDays: h.numberOfDays,
|
||||||
@ -175,8 +154,9 @@ export const useLeaveHistory = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get Valid Optional Holidays
|
|
||||||
export const useValidOptionalHolidays = () => {
|
export const useValidOptionalHolidays = () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return useQuery<any[]>({
|
return useQuery<any[]>({
|
||||||
queryKey: ['optionalHolidays'],
|
queryKey: ['optionalHolidays'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@ -187,18 +167,13 @@ export const useValidOptionalHolidays = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// 3. MANAGER ACTIONS
|
|
||||||
// ==========================================
|
|
||||||
|
|
||||||
// Get Pending Leaves for Team (Generic for Manager, HR, Director)
|
|
||||||
export const usePendingApprovals = () => {
|
export const usePendingApprovals = () => {
|
||||||
return useQuery<ManagerLeaveApproval[]>({
|
return useQuery<ManagerLeaveApproval[]>({
|
||||||
queryKey: ['pendingApprovals'],
|
queryKey: ['pendingApprovals'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await apiClient.get('/api/lms/manager/pending');
|
const response = await apiClient.get('/api/lms/manager/pending');
|
||||||
|
|
||||||
// Map backend response to frontend ManagerLeaveApproval interface
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return (response.data.data || []).map((p: any) => ({
|
return (response.data.data || []).map((p: any) => ({
|
||||||
id: String(p.applicationId),
|
id: String(p.applicationId),
|
||||||
employeeId: p.employeeCode || String(p.employeeId),
|
employeeId: p.employeeCode || String(p.employeeId),
|
||||||
@ -215,12 +190,11 @@ export const usePendingApprovals = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Aliases for role-specific hooks to keep existing components working
|
|
||||||
export const useManagerApprovals = usePendingApprovals;
|
export const useManagerApprovals = usePendingApprovals;
|
||||||
export const useHRManagerApprovals = usePendingApprovals;
|
export const useHRManagerApprovals = usePendingApprovals;
|
||||||
export const useDirectorApprovals = usePendingApprovals;
|
export const useDirectorApprovals = usePendingApprovals;
|
||||||
|
|
||||||
// Approve / Reject Leave
|
|
||||||
export const useUpdateLeaveApproval = () => {
|
export const useUpdateLeaveApproval = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@ -237,10 +211,6 @@ export const useUpdateLeaveApproval = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// 4. REPORTS & MOCK FALLBACKS
|
|
||||||
// ==========================================
|
|
||||||
|
|
||||||
// Get Ledger Report (OB-CB)
|
// Get Ledger Report (OB-CB)
|
||||||
export const useLeaveBalance = (params: { month: number; year: number; companyId: number }, enabled: boolean) => {
|
export const useLeaveBalance = (params: { month: number; year: number; companyId: number }, enabled: boolean) => {
|
||||||
return useQuery<LeaveBalanceRecord[]>({
|
return useQuery<LeaveBalanceRecord[]>({
|
||||||
@ -253,7 +223,7 @@ export const useLeaveBalance = (params: { month: number; year: number; companyId
|
|||||||
const rawData = response.data.data || [];
|
const rawData = response.data.data || [];
|
||||||
const employeeMap: Record<string, LeaveBalanceRecord> = {};
|
const employeeMap: Record<string, LeaveBalanceRecord> = {};
|
||||||
|
|
||||||
// Group raw data by employeeId
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
rawData.forEach((item: any) => {
|
rawData.forEach((item: any) => {
|
||||||
if (!employeeMap[item.employeeId]) {
|
if (!employeeMap[item.employeeId]) {
|
||||||
employeeMap[item.employeeId] = {
|
employeeMap[item.employeeId] = {
|
||||||
@ -282,26 +252,26 @@ export const useLeaveBalance = (params: { month: number; year: number; companyId
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get Leave Applications (Real API for HR/Director)
|
|
||||||
export const useLeaveApplications = () => {
|
export const useLeaveApplications = () => {
|
||||||
return useQuery<LeaveApplicationSummary[]>({
|
return useQuery<LeaveApplicationSummary[]>({
|
||||||
queryKey: ['leaveApplications'],
|
queryKey: ['leaveApplications'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await apiClient.get('/api/lms/hr/applications');
|
const response = await apiClient.get('/api/lms/hr/applications');
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return (response.data.data || []).map((p: any) => ({
|
return (response.data.data || []).map((p: any) => ({
|
||||||
id: String(p.applicationId),
|
id: String(p.applicationId),
|
||||||
employeeId: p.employeeCode,
|
employeeId: p.employeeCode,
|
||||||
employeeName: `${p.firstName} ${p.lastName}`.trim(),
|
employeeName: `${p.firstName} ${p.lastName}`.trim(),
|
||||||
jobRole: p.jobTitle, // Mapped from jobTitle
|
jobRole: p.jobTitle,
|
||||||
department: p.departmentName,
|
department: p.departmentName,
|
||||||
managerName: p.managerName, // Mapped from backend
|
managerName: p.managerName,
|
||||||
leaveType: p.leaveType,
|
leaveType: p.leaveType,
|
||||||
fromDate: p.dateFrom,
|
fromDate: p.dateFrom,
|
||||||
toDate: p.dateTo,
|
toDate: p.dateTo,
|
||||||
leaveDays: p.numberOfDays,
|
leaveDays: p.numberOfDays,
|
||||||
status: p.status,
|
status: p.status,
|
||||||
reason: p.reason, // Mapped from backend
|
reason: p.reason,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export default function ApprovalModal({ record, onClose, onAction }: ApprovalMod
|
|||||||
return new Date(dateStr + 'T00:00:00').toLocaleDateString('en-US', { day: '2-digit', month: 'short', year: 'numeric' });
|
return new Date(dateStr + 'T00:00:00').toLocaleDateString('en-US', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if the status is pending (case-insensitive)
|
// Check if the status is pending
|
||||||
const isPending = record.status.toUpperCase().includes('PENDING');
|
const isPending = record.status.toUpperCase().includes('PENDING');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -30,10 +30,13 @@ export default function ApprovalModal({ record, onClose, onAction }: ApprovalMod
|
|||||||
<button onClick={onClose} className="absolute top-4 right-4 text-text-muted hover:text-text-primary">
|
<button onClick={onClose} className="absolute top-4 right-4 text-text-muted hover:text-text-primary">
|
||||||
<X size={24} />
|
<X size={24} />
|
||||||
</button>
|
</button>
|
||||||
<h3 className="text-xl font-bold text-text-primary mb-1">Leave Application Details</h3>
|
<h3 className="text-xl font-bold text-text-primary mb-6">Leave Application Details</h3>
|
||||||
<p className="text-sm text-text-muted mb-6">Application ID: {record.id}</p>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
<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">
|
<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 text-text-muted">Employee ID</span>
|
||||||
<span className="text-sm font-medium text-text-primary">{record.employeeId}</span>
|
<span className="text-sm font-medium text-text-primary">{record.employeeId}</span>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useApplyLeave } from '../../api/useLeaveData';
|
import { useApplyLeave } from '../../../api/useLeaveData';
|
||||||
import type { LeaveSummary, LeaveRequestPayload, DayMode } from '../../types/leave';
|
import type { LeaveSummary, LeaveRequestPayload, DayMode } from '../../../types/leave';
|
||||||
|
|
||||||
interface ApplyLeaveFormProps {
|
interface ApplyLeaveFormProps {
|
||||||
summaryData: LeaveSummary[];
|
summaryData: LeaveSummary[];
|
||||||
@ -43,8 +43,8 @@ export default function LeaveApplicationModal({ record, onClose }: LeaveApplicat
|
|||||||
<span className="text-sm font-medium text-text-primary">{record.employeeName}</span>
|
<span className="text-sm font-medium text-text-primary">{record.employeeName}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between border-b border-app-border pb-2">
|
<div className="flex justify-between border-b border-app-border pb-2">
|
||||||
<span className="text-sm text-text-muted">Job Role</span> {/* Changed */}
|
<span className="text-sm text-text-muted">Job Role</span>
|
||||||
<span className="text-sm font-medium text-text-primary">{record.jobRole}</span> {/* Changed */}
|
<span className="text-sm font-medium text-text-primary">{record.jobRole}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between border-b border-app-border pb-2">
|
<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 text-text-muted">Department</span>
|
||||||
@ -69,7 +69,6 @@ export default function LeaveApplicationModal({ record, onClose }: LeaveApplicat
|
|||||||
<span className="text-sm text-text-muted">Status</span>
|
<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>
|
<span className={`px-2 py-1 rounded-full text-xs font-medium ${getStatusClass(record.status)}`}>{record.status}</span>
|
||||||
</div>
|
</div>
|
||||||
{/* Added Reason Field */}
|
|
||||||
<div className="border-b border-app-border pb-2">
|
<div className="border-b border-app-border pb-2">
|
||||||
<span className="text-sm text-text-muted block mb-1">Reason</span>
|
<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 || 'No reason provided.'}</p>
|
<p className="text-sm text-text-secondary bg-app-muted p-3 rounded-lg">{record.reason || 'No reason provided.'}</p>
|
||||||
|
|||||||
@ -7,8 +7,6 @@ interface ViewLeaveModalProps {
|
|||||||
onWithdraw: (record: LeaveHistoryRecord) => void;
|
onWithdraw: (record: LeaveHistoryRecord) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inside ViewLeaveModal.tsx
|
|
||||||
|
|
||||||
const getStatusClass = (status: string) => {
|
const getStatusClass = (status: string) => {
|
||||||
const normalizedStatus = status.toUpperCase();
|
const normalizedStatus = status.toUpperCase();
|
||||||
if (normalizedStatus.includes('APPROVED')) return 'bg-present-100 text-present-700';
|
if (normalizedStatus.includes('APPROVED')) return 'bg-present-100 text-present-700';
|
||||||
@ -25,10 +23,13 @@ export default function ViewLeaveModal({ record, onClose, onWithdraw }: ViewLeav
|
|||||||
<button onClick={onClose} className="absolute top-4 right-4 text-text-muted hover:text-text-primary">
|
<button onClick={onClose} className="absolute top-4 right-4 text-text-muted hover:text-text-primary">
|
||||||
<X size={24} />
|
<X size={24} />
|
||||||
</button>
|
</button>
|
||||||
<h3 className="text-xl font-bold text-text-primary mb-1">Leave Details</h3>
|
<h3 className="text-xl font-bold text-text-primary mb-6">Leave Details</h3>
|
||||||
<p className="text-sm text-text-muted mb-6">Application ID: {record.id}</p>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
<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">
|
<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 text-text-muted">Leave Type</span>
|
||||||
<span className="text-sm font-medium text-text-primary">{record.leaveType}</span>
|
<span className="text-sm font-medium text-text-primary">{record.leaveType}</span>
|
||||||
@ -52,7 +53,6 @@ export default function ViewLeaveModal({ record, onClose, onWithdraw }: ViewLeav
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end space-x-3 mt-6">
|
<div className="flex justify-end space-x-3 mt-6">
|
||||||
{/* Update this condition to check uppercase PENDING */}
|
|
||||||
{record.status.toUpperCase() === 'PENDING' && (
|
{record.status.toUpperCase() === 'PENDING' && (
|
||||||
<button onClick={() => onWithdraw(record)} className="px-4 py-2 bg-absent-500 text-white rounded-lg hover:bg-absent-600 transition-colors text-sm font-medium">
|
<button onClick={() => onWithdraw(record)} className="px-4 py-2 bg-absent-500 text-white rounded-lg hover:bg-absent-600 transition-colors text-sm font-medium">
|
||||||
Withdraw Application
|
Withdraw Application
|
||||||
|
|||||||
@ -7,7 +7,6 @@ interface LeaveApplicationsTableProps {
|
|||||||
records: LeaveApplicationSummary[];
|
records: LeaveApplicationSummary[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updated to use jobRole instead of designation
|
|
||||||
type SortKey = 'id' | 'employeeId' | 'employeeName' | 'jobRole' | 'department' | 'managerName' | 'leaveType' | 'leaveDays' | 'status';
|
type SortKey = 'id' | 'employeeId' | 'employeeName' | 'jobRole' | 'department' | 'managerName' | 'leaveType' | 'leaveDays' | 'status';
|
||||||
type SortDirection = 'ascending' | 'descending';
|
type SortDirection = 'ascending' | 'descending';
|
||||||
|
|
||||||
@ -36,7 +35,7 @@ export default function LeaveApplicationsTable({ records }: LeaveApplicationsTab
|
|||||||
record.id.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
record.id.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
record.employeeId.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
record.employeeId.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
record.employeeName.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
record.employeeName.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
record.jobRole.toLowerCase().includes(searchQuery.toLowerCase()) || // Changed
|
record.jobRole.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
record.department.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
record.department.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
record.managerName.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
record.managerName.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
record.leaveType.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
record.leaveType.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
@ -97,7 +96,7 @@ export default function LeaveApplicationsTable({ records }: LeaveApplicationsTab
|
|||||||
if (key === 'id') return 'App ID';
|
if (key === 'id') return 'App ID';
|
||||||
if (key === 'employeeId') return 'Emp CODE';
|
if (key === 'employeeId') return 'Emp CODE';
|
||||||
if (key === 'employeeName') return 'Employee Name';
|
if (key === 'employeeName') return 'Employee Name';
|
||||||
if (key === 'jobRole') return 'Job Role'; // Changed
|
if (key === 'jobRole') return 'Job Role';
|
||||||
if (key === 'managerName') return 'Manager Name';
|
if (key === 'managerName') return 'Manager Name';
|
||||||
if (key === 'leaveType') return 'Leave Type';
|
if (key === 'leaveType') return 'Leave Type';
|
||||||
if (key === 'leaveDays') return 'Leave Days';
|
if (key === 'leaveDays') return 'Leave Days';
|
||||||
@ -141,7 +140,7 @@ export default function LeaveApplicationsTable({ records }: LeaveApplicationsTab
|
|||||||
<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-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.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.employeeName}</td>
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-text-secondary">{record.jobRole}</td> {/* Changed */}
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-text-secondary">{record.jobRole}</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.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.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.leaveType}</td>
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { useCompanies } from '../../masterData/api/useCompanyData';
|
|||||||
export default function BalanceReport() {
|
export default function BalanceReport() {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const [filters, setFilters] = useState<BalanceFilterValues>({
|
const [filters, setFilters] = useState<BalanceFilterValues>({
|
||||||
companyId: '1', // Default to company 1
|
companyId: '1',
|
||||||
month: today.getMonth() + 1,
|
month: today.getMonth() + 1,
|
||||||
year: today.getFullYear(),
|
year: today.getFullYear(),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
// src/features/leave/pages/leave/MyLeave.tsx
|
|
||||||
import Loader from '../../../components/ui/Loader';
|
import Loader from '../../../components/ui/Loader';
|
||||||
import ErrorState from '../../../components/ui/ErrorState';
|
import ErrorState from '../../../components/ui/ErrorState';
|
||||||
import LeaveSummaryTable from '../components/leave/table/LeaveSummaryTable';
|
import LeaveSummaryTable from '../components/leave/table/LeaveSummaryTable';
|
||||||
import ApplyLeaveForm from '../components/leave/ApplyLeaveForm';
|
import ApplyLeaveForm from '../components/leave/form/ApplyLeaveForm';
|
||||||
import LeaveHistoryTable from '../components/leave/table/LeaveHistoryTable';
|
import LeaveHistoryTable from '../components/leave/table/LeaveHistoryTable';
|
||||||
import { useLeaveSummary, useLeaveHistory } from '../api/useLeaveData';
|
import { useLeaveSummary, useLeaveHistory } from '../api/useLeaveData';
|
||||||
|
|
||||||
export default function MyLeave() {
|
export default function MyLeave() {
|
||||||
// The hooks automatically use the Mock SSO headers injected by the Axios interceptor!
|
// The hooks automatically use the Mock SSO headers injected by the Axios interceptor
|
||||||
const { data: summaryData, isLoading: summaryLoading, isError: summaryError } = useLeaveSummary();
|
const { data: summaryData, isLoading: summaryLoading, isError: summaryError } = useLeaveSummary();
|
||||||
const { data: historyData, isLoading: historyLoading, isError: historyError } = useLeaveHistory();
|
const { data: historyData, isLoading: historyLoading, isError: historyError } = useLeaveHistory();
|
||||||
|
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export interface LeaveApplicationSummary {
|
|||||||
id: string;
|
id: string;
|
||||||
employeeId: string;
|
employeeId: string;
|
||||||
employeeName: string;
|
employeeName: string;
|
||||||
jobRole: string; // Changed from designation
|
jobRole: string;
|
||||||
department: string;
|
department: string;
|
||||||
managerName: string;
|
managerName: string;
|
||||||
leaveType: string;
|
leaveType: string;
|
||||||
@ -64,7 +64,7 @@ export interface LeaveApplicationSummary {
|
|||||||
toDate: string;
|
toDate: string;
|
||||||
leaveDays: number;
|
leaveDays: number;
|
||||||
status: string;
|
status: string;
|
||||||
reason: string; // Added reason
|
reason: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LeaveBalanceRecord {
|
export interface LeaveBalanceRecord {
|
||||||
|
|||||||
@ -1,18 +1,8 @@
|
|||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { apiClient } from '../../../api/client';
|
import { apiClient } from '../../../api/client';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import type { Branch } from '../types/masterData';
|
||||||
|
|
||||||
export interface Branch {
|
|
||||||
id: number;
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
companyId: number | null;
|
|
||||||
companyCode: string;
|
|
||||||
companyName: string;
|
|
||||||
status: 'Active' | 'Disabled';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. GET: Fetch all branches
|
|
||||||
export const useBranches = () => {
|
export const useBranches = () => {
|
||||||
return useQuery<Branch[]>({
|
return useQuery<Branch[]>({
|
||||||
queryKey: ['branches'],
|
queryKey: ['branches'],
|
||||||
@ -33,7 +23,6 @@ export const useBranches = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2. POST: Create a branch
|
|
||||||
export const useCreateBranch = () => {
|
export const useCreateBranch = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@ -53,7 +42,6 @@ export const useCreateBranch = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. PUT: Update a branch
|
|
||||||
export const useUpdateBranch = () => {
|
export const useUpdateBranch = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@ -74,7 +62,6 @@ export const useUpdateBranch = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 4. DELETE: Delete a branch
|
|
||||||
export const useDeleteBranch = () => {
|
export const useDeleteBranch = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
|
|||||||
@ -1,15 +1,8 @@
|
|||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { apiClient } from '../../../api/client';
|
import { apiClient } from '../../../api/client';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import type { Company } from '../types/masterData';
|
||||||
|
|
||||||
export interface Company {
|
|
||||||
id: number;
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
status: 'Active' | 'Disabled';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. GET: Fetch all companies
|
|
||||||
export const useCompanies = () => {
|
export const useCompanies = () => {
|
||||||
return useQuery<Company[]>({
|
return useQuery<Company[]>({
|
||||||
queryKey: ['companies'],
|
queryKey: ['companies'],
|
||||||
@ -17,6 +10,7 @@ export const useCompanies = () => {
|
|||||||
const response = await apiClient.get('/api/ems/companies');
|
const response = await apiClient.get('/api/ems/companies');
|
||||||
const rawData = response.data.data || [];
|
const rawData = response.data.data || [];
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
return rawData.map((c: any) => ({
|
return rawData.map((c: any) => ({
|
||||||
id: c.companyId,
|
id: c.companyId,
|
||||||
code: c.companyCode,
|
code: c.companyCode,
|
||||||
@ -27,7 +21,6 @@ export const useCompanies = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2. POST: Create a company
|
|
||||||
export const useCreateCompany = () => {
|
export const useCreateCompany = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@ -44,13 +37,14 @@ export const useCreateCompany = () => {
|
|||||||
queryClient.invalidateQueries({ queryKey: ['companies'] });
|
queryClient.invalidateQueries({ queryKey: ['companies'] });
|
||||||
toast.success('Company created successfully');
|
toast.success('Company created successfully');
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error?.response?.data?.message || 'Failed to create company');
|
toast.error(error?.response?.data?.message || 'Failed to create company');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. PUT: Update a company
|
|
||||||
export const useUpdateCompany = () => {
|
export const useUpdateCompany = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@ -70,7 +64,6 @@ export const useUpdateCompany = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 4. DELETE: Delete a company
|
|
||||||
export const useDeleteCompany = () => {
|
export const useDeleteCompany = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
|
|||||||
@ -1,19 +1,8 @@
|
|||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { apiClient } from '../../../api/client';
|
import { apiClient } from '../../../api/client';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import type { Department } from '../types/masterData';
|
||||||
|
|
||||||
export interface Department {
|
|
||||||
id: number;
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
branchId: number | null;
|
|
||||||
branchName: string;
|
|
||||||
companyId: number | null;
|
|
||||||
companyName: string;
|
|
||||||
status: 'Active' | 'Disabled';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. GET: Fetch all departments
|
|
||||||
export const useDepartments = () => {
|
export const useDepartments = () => {
|
||||||
return useQuery<Department[]>({
|
return useQuery<Department[]>({
|
||||||
queryKey: ['departments'],
|
queryKey: ['departments'],
|
||||||
@ -35,7 +24,6 @@ export const useDepartments = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2. POST: Create a department
|
|
||||||
export const useCreateDepartment = () => {
|
export const useCreateDepartment = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@ -58,7 +46,6 @@ export const useCreateDepartment = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. PUT: Update a department
|
|
||||||
export const useUpdateDepartment = () => {
|
export const useUpdateDepartment = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@ -81,7 +68,6 @@ export const useUpdateDepartment = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 4. DELETE: Delete a department
|
|
||||||
export const useDeleteDepartment = () => {
|
export const useDeleteDepartment = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
|
|||||||
@ -1,69 +1,8 @@
|
|||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { apiClient } from '../../../api/client';
|
import { apiClient } from '../../../api/client';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import type { Employee, EmployeeDetail, Manager } from '../types/masterData';
|
||||||
|
|
||||||
export interface Employee {
|
|
||||||
id: number;
|
|
||||||
code: string;
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
email: string;
|
|
||||||
jobId: number | null;
|
|
||||||
jobName: string;
|
|
||||||
deptId: number | null;
|
|
||||||
deptName: string;
|
|
||||||
branchId: number | null;
|
|
||||||
branchName: string;
|
|
||||||
companyId: number | null;
|
|
||||||
companyName: string;
|
|
||||||
status: 'Active' | 'Disabled';
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EmployeeDetail {
|
|
||||||
employeeId: number;
|
|
||||||
employeeCode: string;
|
|
||||||
isActive: boolean;
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
dob: string;
|
|
||||||
gender: string;
|
|
||||||
personalEmail: string;
|
|
||||||
personalPhone: string;
|
|
||||||
workEmail: string;
|
|
||||||
dateJoining: string;
|
|
||||||
probationDays: number;
|
|
||||||
contractStatus: string;
|
|
||||||
salaryStructureId: number;
|
|
||||||
department: string;
|
|
||||||
departmentId: number;
|
|
||||||
jobName: string;
|
|
||||||
jobId: number;
|
|
||||||
managerFirstName: string;
|
|
||||||
managerLastName: string;
|
|
||||||
managerEmployeeCode: string;
|
|
||||||
addresses: Array<{
|
|
||||||
type: string;
|
|
||||||
doorNumber: string;
|
|
||||||
landmark: string;
|
|
||||||
line: string;
|
|
||||||
pincode: string;
|
|
||||||
district: string;
|
|
||||||
state: string;
|
|
||||||
}>;
|
|
||||||
companyName: string;
|
|
||||||
companyId: number;
|
|
||||||
branchName: string;
|
|
||||||
branchId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Manager {
|
|
||||||
id: number;
|
|
||||||
code: string;
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. GET: Fetch all employees (List view)
|
|
||||||
export const useEmployees = () => {
|
export const useEmployees = () => {
|
||||||
return useQuery<Employee[]>({
|
return useQuery<Employee[]>({
|
||||||
queryKey: ['employees'],
|
queryKey: ['employees'],
|
||||||
@ -91,7 +30,6 @@ export const useEmployees = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1.5. GET: Fetch single employee by ID
|
|
||||||
export const useEmployeeById = (id: number | null) => {
|
export const useEmployeeById = (id: number | null) => {
|
||||||
return useQuery<EmployeeDetail>({
|
return useQuery<EmployeeDetail>({
|
||||||
queryKey: ['employee', id],
|
queryKey: ['employee', id],
|
||||||
@ -103,7 +41,6 @@ export const useEmployeeById = (id: number | null) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1.6. GET: Fetch Managers by Department ID
|
|
||||||
export const useManagers = (deptId: number | null) => {
|
export const useManagers = (deptId: number | null) => {
|
||||||
return useQuery<Manager[]>({
|
return useQuery<Manager[]>({
|
||||||
queryKey: ['managers', deptId],
|
queryKey: ['managers', deptId],
|
||||||
@ -123,10 +60,10 @@ export const useManagers = (deptId: number | null) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2. POST: Create an employee
|
|
||||||
export const useCreateEmployee = () => {
|
export const useCreateEmployee = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
mutationFn: async (data: any) => {
|
mutationFn: async (data: any) => {
|
||||||
const payload = {
|
const payload = {
|
||||||
firstName: data.firstName,
|
firstName: data.firstName,
|
||||||
@ -151,6 +88,7 @@ export const useCreateEmployee = () => {
|
|||||||
try {
|
try {
|
||||||
const response = await apiClient.post('/api/ems/employees', payload);
|
const response = await apiClient.post('/api/ems/employees', payload);
|
||||||
return response.data;
|
return response.data;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const backendError = error?.response?.data?.message || error?.response?.data?.error || "Unknown backend error";
|
const backendError = error?.response?.data?.message || error?.response?.data?.error || "Unknown backend error";
|
||||||
throw new Error(backendError, { cause: error });
|
throw new Error(backendError, { cause: error });
|
||||||
@ -163,12 +101,13 @@ export const useCreateEmployee = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. PUT: Update an employee profile data
|
|
||||||
export const useUpdateEmployee = () => {
|
export const useUpdateEmployee = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
mutationFn: async (data: any) => {
|
mutationFn: async (data: any) => {
|
||||||
// Removed isActive from here, status is handled by PATCH endpoint
|
// status is handled by PATCH endpoint
|
||||||
const payload = {
|
const payload = {
|
||||||
firstName: data.firstName,
|
firstName: data.firstName,
|
||||||
lastName: data.lastName,
|
lastName: data.lastName,
|
||||||
@ -189,6 +128,7 @@ export const useUpdateEmployee = () => {
|
|||||||
try {
|
try {
|
||||||
const response = await apiClient.put(`/api/ems/employees/${data.id}`, payload);
|
const response = await apiClient.put(`/api/ems/employees/${data.id}`, payload);
|
||||||
return response.data;
|
return response.data;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const backendError = error?.response?.data?.message || error?.response?.data?.error || "Unknown backend error";
|
const backendError = error?.response?.data?.message || error?.response?.data?.error || "Unknown backend error";
|
||||||
throw new Error(backendError, { cause: error });
|
throw new Error(backendError, { cause: error });
|
||||||
@ -203,7 +143,7 @@ export const useUpdateEmployee = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 4. PATCH: Update Employee Status (Active/Disabled)
|
|
||||||
export const useUpdateEmployeeStatus = () => {
|
export const useUpdateEmployeeStatus = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@ -211,6 +151,7 @@ export const useUpdateEmployeeStatus = () => {
|
|||||||
try {
|
try {
|
||||||
const response = await apiClient.patch(`/api/ems/employees/${id}/status`, { isActive });
|
const response = await apiClient.patch(`/api/ems/employees/${id}/status`, { isActive });
|
||||||
return response.data;
|
return response.data;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const backendError = error?.response?.data?.message || error?.response?.data?.error || "Unknown backend error";
|
const backendError = error?.response?.data?.message || error?.response?.data?.error || "Unknown backend error";
|
||||||
throw new Error(backendError, { cause: error });
|
throw new Error(backendError, { cause: error });
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { fetchMockData } from '../../../api/client';
|
import { fetchMockData } from '../../../api/client';
|
||||||
import type { Employee } from './useEmployeeData';
|
import type { HRManagerEmployee } from '../types/masterData';
|
||||||
export interface HRManagerEmployee extends Employee {
|
|
||||||
name: any;
|
|
||||||
isHRManager: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useHRManagerEmployees = () => {
|
export const useHRManagerEmployees = () => {
|
||||||
return useQuery<HRManagerEmployee[]>({
|
return useQuery<HRManagerEmployee[]>({
|
||||||
|
|||||||
@ -1,22 +1,8 @@
|
|||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { apiClient } from '../../../api/client';
|
import { apiClient } from '../../../api/client';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import type { JobRole } from '../types/masterData';
|
||||||
|
|
||||||
export interface JobRole {
|
|
||||||
id: number;
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
deptId: number | null;
|
|
||||||
deptName: string;
|
|
||||||
branchId: number | null;
|
|
||||||
branchName: string;
|
|
||||||
companyId: number | null;
|
|
||||||
companyName: string;
|
|
||||||
status: 'Active' | 'Disabled';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. GET: Fetch all job roles
|
|
||||||
export const useJobRoles = () => {
|
export const useJobRoles = () => {
|
||||||
return useQuery<JobRole[]>({
|
return useQuery<JobRole[]>({
|
||||||
queryKey: ['jobRoles'],
|
queryKey: ['jobRoles'],
|
||||||
@ -41,7 +27,6 @@ export const useJobRoles = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2. POST: Create a job role
|
|
||||||
export const useCreateJobRole = () => {
|
export const useCreateJobRole = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@ -62,7 +47,6 @@ export const useCreateJobRole = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. PUT: Update a job role
|
|
||||||
export const useUpdateJobRole = () => {
|
export const useUpdateJobRole = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
@ -83,7 +67,6 @@ export const useUpdateJobRole = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 4. DELETE: Delete a job role
|
|
||||||
export const useDeleteJobRole = () => {
|
export const useDeleteJobRole = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
|
|||||||
@ -1,13 +1,6 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { apiClient } from '../../../api/client';
|
import { apiClient } from '../../../api/client';
|
||||||
|
import type { MasterDataSummary } from '../types/masterData';
|
||||||
export interface MasterDataSummary {
|
|
||||||
totalCompanies: number;
|
|
||||||
totalBranches: number;
|
|
||||||
totalDepartments: number;
|
|
||||||
totalJobRoles: number;
|
|
||||||
totalEmployees: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useMasterDataSummary = () => {
|
export const useMasterDataSummary = () => {
|
||||||
return useQuery<MasterDataSummary>({
|
return useQuery<MasterDataSummary>({
|
||||||
|
|||||||
@ -1,22 +1,10 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { fetchMockData } from '../../../api/client';
|
import { fetchMockData } from '../../../api/client';
|
||||||
|
import type { CompanyPolicy } from '../types/masterData';
|
||||||
export interface Policy {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
category: string;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CompanyPolicy {
|
|
||||||
companyId: string;
|
|
||||||
companyName: string;
|
|
||||||
policies: Policy[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const usePolicies = () => {
|
export const usePolicies = () => {
|
||||||
return useQuery<CompanyPolicy[]>({
|
return useQuery<CompanyPolicy[]>({
|
||||||
queryKey: ['policies'],
|
queryKey: ['policies'],
|
||||||
queryFn: () => fetchMockData<CompanyPolicy[]>('master-data/policies.json'),
|
queryFn: () => fetchMockData<CompanyPolicy[]>('policies.json'),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -1,7 +1,6 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
import type { Branch } from '../../api/useBranchData';
|
import type { Branch, Company } from '../../types/masterData';
|
||||||
import type { Company } from '../../api/useCompanyData';
|
|
||||||
|
|
||||||
interface BranchFormModalProps {
|
interface BranchFormModalProps {
|
||||||
branch: Branch | null;
|
branch: Branch | null;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { ArrowUpDown, ArrowUp, ArrowDown, Search, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, Pencil, Trash2 } from 'lucide-react';
|
import { ArrowUpDown, ArrowUp, ArrowDown, Search, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, Pencil, Trash2 } from 'lucide-react';
|
||||||
import type { Branch } from '../../api/useBranchData';
|
import type { Branch } from '../../types/masterData';
|
||||||
|
|
||||||
interface BranchTableProps {
|
interface BranchTableProps {
|
||||||
records: Branch[];
|
records: Branch[];
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
import type { Company } from '../../api/useCompanyData';
|
import type { Company } from '../../types/masterData';
|
||||||
|
|
||||||
interface CompanyFormModalProps {
|
interface CompanyFormModalProps {
|
||||||
company: Company | null; // null means Add mode, object means Edit mode
|
company: Company | null;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSave: (name: string, status: 'Active' | 'Disabled') => void;
|
onSave: (name: string, status: 'Active' | 'Disabled') => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { ArrowUpDown, ArrowUp, ArrowDown, Search, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, Pencil, Trash2 } from 'lucide-react';
|
import { ArrowUpDown, ArrowUp, ArrowDown, Search, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, Pencil, Trash2 } from 'lucide-react';
|
||||||
import type { Company } from '../../api/useCompanyData';
|
import type { Company } from '../../types/masterData';
|
||||||
|
|
||||||
interface CompanyTableProps {
|
interface CompanyTableProps {
|
||||||
records: Company[];
|
records: Company[];
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import { useState, useEffect, useMemo } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
import type { Department } from '../../api/useDepartmentData';
|
import type { Department, Company, Branch } from '../../types/masterData';
|
||||||
import type { Company } from '../../api/useCompanyData';
|
|
||||||
import type { Branch } from '../../api/useBranchData';
|
|
||||||
|
|
||||||
interface DepartmentFormModalProps {
|
interface DepartmentFormModalProps {
|
||||||
department: Department | null;
|
department: Department | null;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { ArrowUpDown, ArrowUp, ArrowDown, Search, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, Pencil, Trash2 } from 'lucide-react';
|
import { ArrowUpDown, ArrowUp, ArrowDown, Search, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, Pencil, Trash2 } from 'lucide-react';
|
||||||
import type { Department } from '../../api/useDepartmentData';
|
import type { Department } from '../../types/masterData';
|
||||||
|
|
||||||
interface DepartmentTableProps {
|
interface DepartmentTableProps {
|
||||||
records: Department[];
|
records: Department[];
|
||||||
@ -8,7 +8,7 @@ interface DepartmentTableProps {
|
|||||||
onDelete: (department: Department) => void;
|
onDelete: (department: Department) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Removed 'id', added 'code'
|
|
||||||
type SortKey = 'code' | 'name' | 'branchName' | 'companyName' | 'status';
|
type SortKey = 'code' | 'name' | 'branchName' | 'companyName' | 'status';
|
||||||
type SortDirection = 'ascending' | 'descending';
|
type SortDirection = 'ascending' | 'descending';
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
import { useState, useEffect, useMemo } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
import type { Employee } from '../../api/useEmployeeData';
|
import type { Employee, Company, Branch, Department, JobRole } from '../../types/masterData';
|
||||||
import { useEmployeeById, useManagers } from '../../api/useEmployeeData';
|
import { useEmployeeById, useManagers } from '../../api/useEmployeeData';
|
||||||
import type { Company } from '../../api/useCompanyData';
|
|
||||||
import type { Branch } from '../../api/useBranchData';
|
|
||||||
import type { Department } from '../../api/useDepartmentData';
|
|
||||||
import type { JobRole } from '../../api/useJobRoleData';
|
|
||||||
|
|
||||||
interface EmployeeFormModalProps {
|
interface EmployeeFormModalProps {
|
||||||
employee: Employee | null;
|
employee: Employee | null;
|
||||||
@ -14,6 +11,7 @@ interface EmployeeFormModalProps {
|
|||||||
departments: Department[];
|
departments: Department[];
|
||||||
jobRoles: JobRole[];
|
jobRoles: JobRole[];
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onSave: (data: any) => void;
|
onSave: (data: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +58,7 @@ export default function EmployeeFormModal({ employee, companies, branches, depar
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (employee && empDetail && employee.id !== loadedEmpId) {
|
if (employee && empDetail && employee.id !== loadedEmpId) {
|
||||||
|
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||||
setLoadedEmpId(employee.id);
|
setLoadedEmpId(employee.id);
|
||||||
|
|
||||||
setFirstName(empDetail.firstName || '');
|
setFirstName(empDetail.firstName || '');
|
||||||
@ -130,7 +129,7 @@ export default function EmployeeFormModal({ employee, companies, branches, depar
|
|||||||
if (!companyId) return [];
|
if (!companyId) return [];
|
||||||
const selectedCompany = companies.find(c => String(c.id) === companyId);
|
const selectedCompany = companies.find(c => String(c.id) === companyId);
|
||||||
if (!selectedCompany) return [];
|
if (!selectedCompany) return [];
|
||||||
// Filter by companyName OR companyId just to be safe
|
// Filter by companyName OR companyId
|
||||||
return branches.filter(b => b.companyName === selectedCompany.name || String(b.companyId) === companyId);
|
return branches.filter(b => b.companyName === selectedCompany.name || String(b.companyId) === companyId);
|
||||||
}, [branches, companyId, companies]);
|
}, [branches, companyId, companies]);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { ArrowUpDown, ArrowUp, ArrowDown, Search, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, Eye } from 'lucide-react';
|
import { ArrowUpDown, ArrowUp, ArrowDown, Search, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, Eye } from 'lucide-react';
|
||||||
import type { Employee } from '../../api/useEmployeeData';
|
import type { Employee } from '../../types/masterData';
|
||||||
|
|
||||||
interface EmployeeTableProps {
|
interface EmployeeTableProps {
|
||||||
records: Employee[];
|
records: Employee[];
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { X, Pencil } from 'lucide-react';
|
import { X, Pencil } from 'lucide-react';
|
||||||
import type { Employee } from '../../api/useEmployeeData';
|
import type { Employee } from '../../types/masterData';
|
||||||
import { useEmployeeById } from '../../api/useEmployeeData';
|
import { useEmployeeById } from '../../api/useEmployeeData';
|
||||||
|
|
||||||
interface EmployeeViewModalProps {
|
interface EmployeeViewModalProps {
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
import { useState, useEffect, useMemo } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
import { X, Pencil } from 'lucide-react';
|
import { X, Pencil } from 'lucide-react';
|
||||||
import type { JobRole } from '../../api/useJobRoleData';
|
import type { JobRole, Company, Branch, Department } from '../../types/masterData';
|
||||||
import type { Company } from '../../api/useCompanyData';
|
|
||||||
import type { Branch } from '../../api/useBranchData';
|
|
||||||
import type { Department } from '../../api/useDepartmentData';
|
|
||||||
|
|
||||||
interface JobRoleFormModalProps {
|
interface JobRoleFormModalProps {
|
||||||
mode: 'view' | 'edit' | 'add';
|
mode: 'view' | 'edit' | 'add';
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { ArrowUpDown, ArrowUp, ArrowDown, Search, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, Eye, Trash2 } from 'lucide-react';
|
import { ArrowUpDown, ArrowUp, ArrowDown, Search, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, Eye, Trash2 } from 'lucide-react';
|
||||||
import type { JobRole } from '../../api/useJobRoleData';
|
import type { JobRole } from '../../types/masterData';
|
||||||
|
|
||||||
interface JobRoleTableProps {
|
interface JobRoleTableProps {
|
||||||
records: JobRole[];
|
records: JobRole[];
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
// src/features/employee-management/pages/ManageBranches.tsx
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Network, Plus } from 'lucide-react';
|
import { Network, Plus } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
@ -7,8 +6,9 @@ import ErrorState from '../../../components/ui/ErrorState';
|
|||||||
import ConfirmationModal from '../../../components/ui/ConfirmationModal';
|
import ConfirmationModal from '../../../components/ui/ConfirmationModal';
|
||||||
import BranchTable from '../components/branch/BranchTable';
|
import BranchTable from '../components/branch/BranchTable';
|
||||||
import BranchFormModal from '../components/branch/BranchFormModal';
|
import BranchFormModal from '../components/branch/BranchFormModal';
|
||||||
import { useBranches, useCreateBranch, useUpdateBranch, useDeleteBranch, type Branch } from '../api/useBranchData';
|
import { useBranches, useCreateBranch, useUpdateBranch, useDeleteBranch } from '../api/useBranchData';
|
||||||
import { useCompanies } from '../api/useCompanyData';
|
import { useCompanies } from '../api/useCompanyData';
|
||||||
|
import type { Branch } from '../types/masterData';
|
||||||
|
|
||||||
export default function ManageBranches() {
|
export default function ManageBranches() {
|
||||||
const { data: branches, isLoading: branchesLoading, isError: branchesError } = useBranches();
|
const { data: branches, isLoading: branchesLoading, isError: branchesError } = useBranches();
|
||||||
@ -46,6 +46,7 @@ export default function ManageBranches() {
|
|||||||
toast.success("Branch deleted successfully.");
|
toast.success("Branch deleted successfully.");
|
||||||
setDeleteTarget(null);
|
setDeleteTarget(null);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error.message || "Failed to delete branch.");
|
toast.error(error.message || "Failed to delete branch.");
|
||||||
setDeleteTarget(null);
|
setDeleteTarget(null);
|
||||||
@ -69,6 +70,7 @@ export default function ManageBranches() {
|
|||||||
toast.success("Branch updated successfully.");
|
toast.success("Branch updated successfully.");
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error.message || "Failed to update branch.");
|
toast.error(error.message || "Failed to update branch.");
|
||||||
},
|
},
|
||||||
@ -82,6 +84,7 @@ export default function ManageBranches() {
|
|||||||
toast.success("Branch added successfully.");
|
toast.success("Branch added successfully.");
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error.message || "Failed to add branch.");
|
toast.error(error.message || "Failed to add branch.");
|
||||||
},
|
},
|
||||||
@ -119,7 +122,7 @@ export default function ManageBranches() {
|
|||||||
{isModalOpen && (
|
{isModalOpen && (
|
||||||
<BranchFormModal
|
<BranchFormModal
|
||||||
branch={editingBranch}
|
branch={editingBranch}
|
||||||
companies={activeCompanies} // Pass only active companies here
|
companies={activeCompanies}
|
||||||
onClose={() => setIsModalOpen(false)}
|
onClose={() => setIsModalOpen(false)}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -6,7 +6,8 @@ import ErrorState from '../../../components/ui/ErrorState';
|
|||||||
import ConfirmationModal from '../../../components/ui/ConfirmationModal';
|
import ConfirmationModal from '../../../components/ui/ConfirmationModal';
|
||||||
import CompanyTable from '../components/company/CompanyTable';
|
import CompanyTable from '../components/company/CompanyTable';
|
||||||
import CompanyFormModal from '../components/company/CompanyFormModal';
|
import CompanyFormModal from '../components/company/CompanyFormModal';
|
||||||
import { useCompanies, useCreateCompany, useUpdateCompany, useDeleteCompany, type Company } from '../api/useCompanyData';
|
import { useCompanies, useCreateCompany, useUpdateCompany, useDeleteCompany } from '../api/useCompanyData';
|
||||||
|
import type { Company } from '../types/masterData';
|
||||||
|
|
||||||
export default function ManageCompanies() {
|
export default function ManageCompanies() {
|
||||||
const { data: companies, isLoading, isError } = useCompanies();
|
const { data: companies, isLoading, isError } = useCompanies();
|
||||||
@ -39,6 +40,7 @@ export default function ManageCompanies() {
|
|||||||
toast.success("Company deleted successfully.");
|
toast.success("Company deleted successfully.");
|
||||||
setDeleteTarget(null);
|
setDeleteTarget(null);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
// Display the specific backend error message
|
// Display the specific backend error message
|
||||||
toast.error(error.message || "Failed to delete company.");
|
toast.error(error.message || "Failed to delete company.");
|
||||||
@ -57,6 +59,7 @@ export default function ManageCompanies() {
|
|||||||
toast.success("Company updated successfully.");
|
toast.success("Company updated successfully.");
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error.message || "Failed to update company.");
|
toast.error(error.message || "Failed to update company.");
|
||||||
},
|
},
|
||||||
@ -70,6 +73,7 @@ export default function ManageCompanies() {
|
|||||||
toast.success("Company added successfully.");
|
toast.success("Company added successfully.");
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error.message || "Failed to add company.");
|
toast.error(error.message || "Failed to add company.");
|
||||||
},
|
},
|
||||||
|
|||||||
@ -6,9 +6,10 @@ import ErrorState from '../../../components/ui/ErrorState';
|
|||||||
import ConfirmationModal from '../../../components/ui/ConfirmationModal';
|
import ConfirmationModal from '../../../components/ui/ConfirmationModal';
|
||||||
import DepartmentTable from '../components/department/DepartmentTable';
|
import DepartmentTable from '../components/department/DepartmentTable';
|
||||||
import DepartmentFormModal from '../components/department/DepartmentFormModal';
|
import DepartmentFormModal from '../components/department/DepartmentFormModal';
|
||||||
import { useDepartments, useCreateDepartment, useUpdateDepartment, useDeleteDepartment, type Department } from '../api/useDepartmentData';
|
import { useDepartments, useCreateDepartment, useUpdateDepartment, useDeleteDepartment } from '../api/useDepartmentData';
|
||||||
import { useCompanies } from '../api/useCompanyData';
|
import { useCompanies } from '../api/useCompanyData';
|
||||||
import { useBranches } from '../api/useBranchData';
|
import { useBranches } from '../api/useBranchData';
|
||||||
|
import type { Department } from '../types/masterData';
|
||||||
|
|
||||||
export default function ManageDepartments() {
|
export default function ManageDepartments() {
|
||||||
const { data: departments, isLoading: depLoading, isError: depError } = useDepartments();
|
const { data: departments, isLoading: depLoading, isError: depError } = useDepartments();
|
||||||
@ -47,6 +48,7 @@ export default function ManageDepartments() {
|
|||||||
toast.success("Department deleted successfully.");
|
toast.success("Department deleted successfully.");
|
||||||
setDeleteTarget(null);
|
setDeleteTarget(null);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error.message || "Failed to delete department.");
|
toast.error(error.message || "Failed to delete department.");
|
||||||
setDeleteTarget(null);
|
setDeleteTarget(null);
|
||||||
@ -70,6 +72,7 @@ export default function ManageDepartments() {
|
|||||||
toast.success("Department updated successfully.");
|
toast.success("Department updated successfully.");
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error.message || "Failed to update department.");
|
toast.error(error.message || "Failed to update department.");
|
||||||
},
|
},
|
||||||
@ -88,6 +91,7 @@ export default function ManageDepartments() {
|
|||||||
toast.success("Department added successfully.");
|
toast.success("Department added successfully.");
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error.message || "Failed to add department.");
|
toast.error(error.message || "Failed to add department.");
|
||||||
},
|
},
|
||||||
|
|||||||
@ -6,11 +6,12 @@ import ErrorState from '../../../components/ui/ErrorState';
|
|||||||
import EmployeeTable from '../components/employee/EmployeeTable';
|
import EmployeeTable from '../components/employee/EmployeeTable';
|
||||||
import EmployeeFormModal from '../components/employee/EmployeeFormModal';
|
import EmployeeFormModal from '../components/employee/EmployeeFormModal';
|
||||||
import EmployeeViewModal from '../components/employee/EmployeeViewModal';
|
import EmployeeViewModal from '../components/employee/EmployeeViewModal';
|
||||||
import { useEmployees, useCreateEmployee, useUpdateEmployee, useUpdateEmployeeStatus, type Employee } from '../api/useEmployeeData';
|
import { useEmployees, useCreateEmployee, useUpdateEmployee, useUpdateEmployeeStatus } from '../api/useEmployeeData';
|
||||||
import { useCompanies } from '../api/useCompanyData';
|
import { useCompanies } from '../api/useCompanyData';
|
||||||
import { useBranches } from '../api/useBranchData';
|
import { useBranches } from '../api/useBranchData';
|
||||||
import { useDepartments } from '../api/useDepartmentData';
|
import { useDepartments } from '../api/useDepartmentData';
|
||||||
import { useJobRoles } from '../api/useJobRoleData';
|
import { useJobRoles } from '../api/useJobRoleData';
|
||||||
|
import type { Employee } from '../types/masterData';
|
||||||
|
|
||||||
export default function ManageEmployees() {
|
export default function ManageEmployees() {
|
||||||
const { data: employees, isLoading: empLoading, isError: empError } = useEmployees();
|
const { data: employees, isLoading: empLoading, isError: empError } = useEmployees();
|
||||||
@ -47,6 +48,7 @@ export default function ManageEmployees() {
|
|||||||
setIsFormModalOpen(true);
|
setIsFormModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const handleSave = async (data: any) => {
|
const handleSave = async (data: any) => {
|
||||||
if (editingEmployee) {
|
if (editingEmployee) {
|
||||||
try {
|
try {
|
||||||
@ -63,6 +65,7 @@ export default function ManageEmployees() {
|
|||||||
|
|
||||||
toast.success("Employee updated successfully.");
|
toast.success("Employee updated successfully.");
|
||||||
setIsFormModalOpen(false);
|
setIsFormModalOpen(false);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
toast.error(error.message || "Failed to save employee data.");
|
toast.error(error.message || "Failed to save employee data.");
|
||||||
}
|
}
|
||||||
@ -72,6 +75,7 @@ export default function ManageEmployees() {
|
|||||||
toast.success("Employee added successfully.");
|
toast.success("Employee added successfully.");
|
||||||
setIsFormModalOpen(false);
|
setIsFormModalOpen(false);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error.message || "Failed to add employee.");
|
toast.error(error.message || "Failed to add employee.");
|
||||||
},
|
},
|
||||||
|
|||||||
@ -6,10 +6,11 @@ import ErrorState from '../../../components/ui/ErrorState';
|
|||||||
import ConfirmationModal from '../../../components/ui/ConfirmationModal';
|
import ConfirmationModal from '../../../components/ui/ConfirmationModal';
|
||||||
import JobRoleTable from '../components/jobRole/JobRoleTable';
|
import JobRoleTable from '../components/jobRole/JobRoleTable';
|
||||||
import JobRoleFormModal from '../components/jobRole/JobRoleFormModal';
|
import JobRoleFormModal from '../components/jobRole/JobRoleFormModal';
|
||||||
import { useJobRoles, useCreateJobRole, useUpdateJobRole, useDeleteJobRole, type JobRole } from '../api/useJobRoleData';
|
import { useJobRoles, useCreateJobRole, useUpdateJobRole, useDeleteJobRole } from '../api/useJobRoleData';
|
||||||
import { useCompanies } from '../api/useCompanyData';
|
import { useCompanies } from '../api/useCompanyData';
|
||||||
import { useBranches } from '../api/useBranchData';
|
import { useBranches } from '../api/useBranchData';
|
||||||
import { useDepartments } from '../api/useDepartmentData';
|
import { useDepartments } from '../api/useDepartmentData';
|
||||||
|
import type { JobRole } from '../types/masterData';
|
||||||
|
|
||||||
type ModalMode = 'view' | 'edit' | 'add';
|
type ModalMode = 'view' | 'edit' | 'add';
|
||||||
|
|
||||||
@ -59,6 +60,7 @@ export default function ManageJobRoles() {
|
|||||||
toast.success("Job Role deleted successfully.");
|
toast.success("Job Role deleted successfully.");
|
||||||
setDeleteTarget(null);
|
setDeleteTarget(null);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error.message || "Failed to delete job role.");
|
toast.error(error.message || "Failed to delete job role.");
|
||||||
setDeleteTarget(null);
|
setDeleteTarget(null);
|
||||||
@ -82,6 +84,7 @@ export default function ManageJobRoles() {
|
|||||||
toast.success("Job Role updated successfully.");
|
toast.success("Job Role updated successfully.");
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error.message || "Failed to update job role.");
|
toast.error(error.message || "Failed to update job role.");
|
||||||
},
|
},
|
||||||
@ -100,6 +103,7 @@ export default function ManageJobRoles() {
|
|||||||
toast.success("Job Role added successfully.");
|
toast.success("Job Role added successfully.");
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
},
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
toast.error(error.message || "Failed to add job role.");
|
toast.error(error.message || "Failed to add job role.");
|
||||||
},
|
},
|
||||||
|
|||||||
130
src/features/masterData/types/masterData.ts
Normal file
130
src/features/masterData/types/masterData.ts
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
export interface Branch {
|
||||||
|
id: number;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
companyId: number | null;
|
||||||
|
companyCode: string;
|
||||||
|
companyName: string;
|
||||||
|
status: 'Active' | 'Disabled';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Company {
|
||||||
|
id: number;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
status: 'Active' | 'Disabled';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Department {
|
||||||
|
id: number;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
branchId: number | null;
|
||||||
|
branchName: string;
|
||||||
|
companyId: number | null;
|
||||||
|
companyName: string;
|
||||||
|
status: 'Active' | 'Disabled';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Employee {
|
||||||
|
id: number;
|
||||||
|
code: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
email: string;
|
||||||
|
jobId: number | null;
|
||||||
|
jobName: string;
|
||||||
|
deptId: number | null;
|
||||||
|
deptName: string;
|
||||||
|
branchId: number | null;
|
||||||
|
branchName: string;
|
||||||
|
companyId: number | null;
|
||||||
|
companyName: string;
|
||||||
|
status: 'Active' | 'Disabled';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmployeeDetail {
|
||||||
|
employeeId: number;
|
||||||
|
employeeCode: string;
|
||||||
|
isActive: boolean;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
dob: string;
|
||||||
|
gender: string;
|
||||||
|
personalEmail: string;
|
||||||
|
personalPhone: string;
|
||||||
|
workEmail: string;
|
||||||
|
dateJoining: string;
|
||||||
|
probationDays: number;
|
||||||
|
contractStatus: string;
|
||||||
|
salaryStructureId: number;
|
||||||
|
department: string;
|
||||||
|
departmentId: number;
|
||||||
|
jobName: string;
|
||||||
|
jobId: number;
|
||||||
|
managerFirstName: string;
|
||||||
|
managerLastName: string;
|
||||||
|
managerEmployeeCode: string;
|
||||||
|
addresses: Array<{
|
||||||
|
type: string;
|
||||||
|
doorNumber: string;
|
||||||
|
landmark: string;
|
||||||
|
line: string;
|
||||||
|
pincode: string;
|
||||||
|
district: string;
|
||||||
|
state: string;
|
||||||
|
}>;
|
||||||
|
companyName: string;
|
||||||
|
companyId: number;
|
||||||
|
branchName: string;
|
||||||
|
branchId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Manager {
|
||||||
|
id: number;
|
||||||
|
code: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HRManagerEmployee extends Employee {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
name: any;
|
||||||
|
isHRManager: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JobRole {
|
||||||
|
id: number;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
deptId: number | null;
|
||||||
|
deptName: string;
|
||||||
|
branchId: number | null;
|
||||||
|
branchName: string;
|
||||||
|
companyId: number | null;
|
||||||
|
companyName: string;
|
||||||
|
status: 'Active' | 'Disabled';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface MasterDataSummary {
|
||||||
|
totalCompanies: number;
|
||||||
|
totalBranches: number;
|
||||||
|
totalDepartments: number;
|
||||||
|
totalJobRoles: number;
|
||||||
|
totalEmployees: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Policy {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
category: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompanyPolicy {
|
||||||
|
companyId: string;
|
||||||
|
companyName: string;
|
||||||
|
policies: Policy[];
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user