Files
TenX PM 87e9346d62 feat: complete HR portal full-stack application
- NestJS backend with 11 modules: Auth, Employees, Departments, Attendance, Leaves, Payroll, Reimbursements, Announcements, Tax, Reports, Admin
- JWT authentication with refresh tokens, role-based access (employee/hr_admin/super_admin)
- MongoDB schemas with Mongoose for all entities
- PDF payslip generation with pdfkit
- OpenTelemetry tracing to SigNoz
- Automatic database seeding on first startup
- Next.js 14 frontend with App Router, Tailwind CSS
- 25 pages covering all employee, HR admin, and super admin workflows
- Multi-stage Dockerfile with nginx proxy
- test-manifest.json for E2E testing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 19:32:52 +00:00

181 lines
6.2 KiB
TypeScript

import axios from 'axios';
const BASE_URL = (process.env.NEXT_PUBLIC_API_URL || '/api-backend') + '/api/v1';
const api = axios.create({
baseURL: BASE_URL,
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor to attach JWT
api.interceptors.request.use(
(config) => {
if (typeof window !== 'undefined') {
const token = localStorage.getItem('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
}
return config;
},
(error) => Promise.reject(error),
);
// Response interceptor to handle 401 and refresh
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
if (typeof window !== 'undefined') {
const refreshToken = localStorage.getItem('refresh_token');
if (refreshToken) {
try {
const response = await axios.post(`${BASE_URL}/auth/refresh`, {
refreshToken,
});
const { accessToken } = response.data;
localStorage.setItem('access_token', accessToken);
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
return api(originalRequest);
} catch (refreshError) {
// Refresh failed, clear tokens and redirect to login
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
localStorage.removeItem('user');
if (typeof window !== 'undefined') {
window.location.href = '/login';
}
}
}
}
}
return Promise.reject(error);
},
);
export default api;
// Auth
export const authApi = {
login: (employeeId: string, password: string) =>
api.post('/auth/login', { employeeId, password }),
logout: () => api.post('/auth/logout'),
changePassword: (currentPassword: string, newPassword: string) =>
api.post('/auth/change-password', { currentPassword, newPassword }),
getMe: () => api.get('/auth/me'),
};
// Employees
export const employeesApi = {
getAll: (params?: any) => api.get('/employees', { params }),
getOne: (id: string) => api.get(`/employees/${id}`),
create: (data: any) => api.post('/employees', data),
update: (id: string, data: any) => api.put(`/employees/${id}`, data),
delete: (id: string) => api.delete(`/employees/${id}`),
importCsv: (file: File) => {
const formData = new FormData();
formData.append('file', file);
return api.post('/employees/import-csv', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
},
};
// Departments
export const departmentsApi = {
getAll: () => api.get('/departments'),
create: (data: any) => api.post('/departments', data),
update: (id: string, data: any) => api.put(`/departments/${id}`, data),
delete: (id: string) => api.delete(`/departments/${id}`),
};
// Attendance
export const attendanceApi = {
mark: (data?: any) => api.post('/attendance', data || {}),
update: (id: string, data: any) => api.put(`/attendance/${id}`, data),
getAll: (params?: any) => api.get('/attendance', { params }),
};
// Leaves
export const leavesApi = {
apply: (data: any) => api.post('/leaves', data),
getAll: (params?: any) => api.get('/leaves', { params }),
approve: (id: string, comment?: string) =>
api.put(`/leaves/${id}/approve`, { comment }),
reject: (id: string, comment: string) =>
api.put(`/leaves/${id}/reject`, { comment }),
getBalance: (employeeId: string) => api.get(`/leaves/balance/${employeeId}`),
};
// Payroll
export const payrollApi = {
generate: (month: number, year: number) =>
api.post('/payroll/generate', { month, year }),
getPayrollRuns: (params?: any) => api.get('/payroll', { params }),
getPayslips: (params?: any) => api.get('/payslips', { params }),
getPayslip: (id: string) => api.get(`/payslips/${id}`),
downloadPdf: (id: string) =>
api.get(`/payslips/${id}/pdf`, { responseType: 'blob' }),
};
// Reimbursements
export const reimbursementsApi = {
create: (data: any, receipt?: File) => {
const formData = new FormData();
Object.keys(data).forEach((key) => formData.append(key, data[key]));
if (receipt) formData.append('receipt', receipt);
return api.post('/reimbursements', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
});
},
getAll: (params?: any) => api.get('/reimbursements', { params }),
approve: (id: string, comment?: string) =>
api.put(`/reimbursements/${id}/approve`, { comment }),
reject: (id: string, comment: string) =>
api.put(`/reimbursements/${id}/reject`, { comment }),
markPaid: (id: string) => api.put(`/reimbursements/${id}/mark-paid`),
};
// Announcements
export const announcementsApi = {
getAll: () => api.get('/announcements'),
create: (data: any) => api.post('/announcements', data),
update: (id: string, data: any) => api.put(`/announcements/${id}`, data),
delete: (id: string) => api.delete(`/announcements/${id}`),
};
// Tax
export const taxApi = {
submitDeclaration: (data: any) => api.post('/tax/declarations', data),
getDeclaration: (employeeId: string) => api.get(`/tax/declarations/${employeeId}`),
getTDSProjection: (employeeId: string) => api.get(`/tax/tds-projection/${employeeId}`),
};
// Reports
export const reportsApi = {
getHeadcount: () => api.get('/reports/headcount'),
getPayrollSummary: (month: number, year: number) =>
api.get('/reports/payroll-summary', { params: { month, year } }),
getLeaveUtilization: (year: number) =>
api.get('/reports/leave-utilization', { params: { year } }),
getAttendanceSummary: (month: number, year: number) =>
api.get('/reports/attendance-summary', { params: { month, year } }),
};
// Admin
export const adminApi = {
getAccounts: () => api.get('/admin/accounts'),
createAccount: (data: any) => api.post('/admin/accounts', data),
deactivateAccount: (id: string) => api.put(`/admin/accounts/${id}/deactivate`),
getAuditLogs: (params?: any) => api.get('/audit-logs', { params }),
getOrgSettings: () => api.get('/org/settings'),
updateOrgSettings: (data: any) => api.put('/org/settings', data),
};