/* ═══ CYPHER-HR Admin Module ═══ */
const Admin = {
async showDashboard() {
App.setView(this.layout('dashboard', await this.dashboardContent()));
this.bindNav();
},
layout(active, content) {
const navItems = [
{ id: 'dashboard', icon: 'dashboard', label: 'Dashboard' },
{ id: 'employees', icon: 'users', label: 'Employees' },
{ id: 'leaves', icon: 'calendar', label: 'Leave Requests' },
{ id: 'policies', icon: 'settings', label: 'Policies' },
{ id: 'reports', icon: 'report', label: 'Reports' },
];
return `
`;
},
bindNav() {
document.querySelectorAll('.nav-item[data-view]').forEach(el => {
el.addEventListener('click', () => this.navigate(el.dataset.view));
});
},
async navigate(view) {
document.querySelectorAll('.nav-item').forEach(el => el.classList.remove('active'));
document.querySelector(`[data-view="${view}"]`)?.classList.add('active');
document.querySelector('.topbar h2').textContent =
{ dashboard: 'Dashboard', employees: 'Employees', leaves: 'Leave Requests', policies: 'Policies', reports: 'Reports' }[view];
const area = document.getElementById('contentArea');
area.style.opacity = '0';
setTimeout(async () => {
if (view === 'dashboard') area.innerHTML = await this.dashboardContent();
else if (view === 'employees') area.innerHTML = await this.employeesContent();
else if (view === 'leaves') area.innerHTML = await this.leavesContent();
else if (view === 'policies') area.innerHTML = await this.policiesContent();
else if (view === 'reports') area.innerHTML = await Reports.adminView();
area.style.opacity = '1';
}, 200);
},
async dashboardContent() {
try {
const stats = await App.api('/api/dashboard/stats');
const leaves = await App.api('/api/leaves');
const pending = leaves.filter(l => l.status === 'pending').slice(0, 5);
return `
${icon('users', 28)}
${stats.totalEmployees}Total Employees
${icon('check', 28)}
${stats.activeEmployees}Active Employees
${icon('clock', 28)}
${stats.pendingLeaves}Pending Requests
${icon('calendar', 28)}
${stats.approvedToday}Approved Today
${pending.length === 0 ? '
No pending requests
' :
`
| Employee | Leave Type | Dates | Status | Actions |
${pending.map(l => `
${App.avatar(l)}${l.first_name} ${l.last_name} |
${l.leave_type} |
${App.formatDate(l.start_date)} - ${App.formatDate(l.end_date)} |
${App.statusBadge(l.status)} |
|
`).join('')}
`}
`;
} catch (err) { return `Error loading dashboard: ${err.message}
`; }
},
async employeesContent() {
try {
const employees = await App.api('/api/employees');
return `
${employees.length === 0 ? '
No employees yet. Add your first employee!
' :
employees.map(emp => `
${icon('briefcase',14)} ${emp.department || '—'}
${icon('calendar',14)} ${emp.position || '—'}
`).join('')}
`;
} catch (err) { return `${err.message}
`; }
},
filterEmployees(q) {
document.querySelectorAll('.employee-card').forEach(c => {
c.style.display = c.dataset.name.toLowerCase().includes(q.toLowerCase()) ? '' : 'none';
});
},
showAddEmployee() {
showModal('Add Employee', `
`,
``
);
},
async addEmployee(e) {
e.preventDefault();
try {
await App.api('/api/employees', {
method: 'POST',
body: JSON.stringify({
first_name: document.getElementById('empFirst').value,
last_name: document.getElementById('empLast').value,
email: document.getElementById('empEmail').value,
password: document.getElementById('empPassword').value,
department: document.getElementById('empDept').value,
position: document.getElementById('empPos').value,
phone: document.getElementById('empPhone').value
})
});
closeModal();
App.toast('Employee added successfully');
this.navigate('employees');
} catch (err) {
const el = document.getElementById('addEmpError');
el.textContent = err.message; el.style.display = 'block';
}
},
async deleteEmployee(id, name) {
if (!confirm(`Delete employee "${name}"? This cannot be undone.`)) return;
try {
await App.api(`/api/employees/${id}`, { method: 'DELETE' });
App.toast('Employee deleted');
this.navigate('employees');
} catch (err) { App.toast(err.message, 'error'); }
},
async toggleEmployee(id) {
try {
await App.api(`/api/employees/${id}/toggle`, { method: 'PUT' });
App.toast('Employee status updated');
this.navigate('employees');
} catch (err) { App.toast(err.message, 'error'); }
},
showEditEmployee(emp) {
showModal('Edit Employee', `
`,
`
`
);
},
async editEmployee(e, id) {
e.preventDefault();
try {
await App.api(`/api/employees/${id}`, {
method: 'PUT',
body: JSON.stringify({
first_name: document.getElementById('editFirst').value,
last_name: document.getElementById('editLast').value,
email: document.getElementById('editEmail').value,
department: document.getElementById('editDept').value,
position: document.getElementById('editPos').value,
phone: document.getElementById('editPhone').value
})
});
closeModal();
App.toast('Employee updated successfully');
this.navigate('employees');
} catch (err) {
const el = document.getElementById('editEmpError');
el.textContent = err.message; el.style.display = 'block';
}
},
showResetPassword(id, name) {
showModal('Reset Password', `
Set a new password for ${name}
`,
`
`
);
},
async resetPassword(e, id) {
e.preventDefault();
const pwd = document.getElementById('newPassword').value;
const confirm = document.getElementById('confirmPassword').value;
const errEl = document.getElementById('resetPwdError');
if (pwd !== confirm) {
errEl.textContent = 'Passwords do not match';
errEl.style.display = 'block';
return;
}
try {
await App.api(`/api/employees/${id}/reset-password`, {
method: 'PUT',
body: JSON.stringify({ new_password: pwd })
});
closeModal();
App.toast('Password reset successfully');
} catch (err) {
errEl.textContent = err.message; errEl.style.display = 'block';
}
},
async showBalances(userId, name) {
try {
const balances = await App.api(`/api/balances?user_id=${userId}`);
showModal(`Leave Allowances — ${name}`, `
${balances.map(b => `
${b.leave_type}
${b.balance}
/ ${b.monthly_limit} max
`).join('')}
`);
} catch (err) { App.toast(err.message, 'error'); }
},
async adjustBalance(balId, delta, userId, name) {
try {
const balances = await App.api(`/api/balances?user_id=${userId}`);
const bal = balances.find(b => b.id === balId);
if (!bal) return;
const newBal = Math.max(0, bal.balance + delta);
await App.api(`/api/balances/${balId}`, { method: 'PUT', body: JSON.stringify({ balance: newBal }) });
closeModal();
this.showBalances(userId, name);
} catch (err) { App.toast(err.message, 'error'); }
},
async leavesContent() {
return Leaves.adminView();
},
async policiesContent() {
try {
const policies = await App.api('/api/policies');
return `
${policies.map(p => `
`).join('')}
`;
} catch (err) { return `${err.message}
`; }
},
async togglePolicy(id, enabled, limit) {
try {
await App.api(`/api/policies/${id}`, { method: 'PUT', body: JSON.stringify({ is_enabled: enabled, monthly_limit: limit }) });
App.toast('Policy updated');
} catch (err) { App.toast(err.message, 'error'); }
},
async updatePolicyLimit(id, limit, enabled) {
try {
await App.api(`/api/policies/${id}`, { method: 'PUT', body: JSON.stringify({ monthly_limit: parseInt(limit), is_enabled: enabled }) });
App.toast('Limit updated');
} catch (err) { App.toast(err.message, 'error'); }
}
};