Files
ai-recruit-site-template/public/js/admin.js
Mikael Westöö 61269bb9a4 Remove .html extensions from all links and URLs
- Updated all navigation links in HTML files
- Updated all hrefs and window.location redirects in JavaScript
- All links now use clean URLs without .html extensions
- Improves SEO and provides cleaner user experience
2026-01-23 22:40:26 +01:00

291 lines
10 KiB
JavaScript

// Admin JavaScript for Ryans Recruit Firm
// Check authentication on all admin pages except login
if (!window.location.pathname.includes('login')) {
checkAuth();
}
async function checkAuth() {
try {
const response = await fetch('/api/admin/check');
const data = await response.json();
if (!data.loggedIn) {
window.location.href = '/admin/login';
return;
}
// Update admin name in header if exists
const adminNameEl = document.getElementById('admin-name');
if (adminNameEl && data.admin) {
adminNameEl.textContent = data.admin.fullName;
}
} catch (err) {
console.error('Auth check failed:', err);
window.location.href = '/admin/login';
}
}
// Login page handlers
if (window.location.pathname.includes('login')) {
checkFirstAdmin();
document.getElementById('login-form')?.addEventListener('submit', handleLogin);
}
async function checkFirstAdmin() {
try {
const response = await fetch('/api/admin/check-first');
const data = await response.json();
if (data.isFirstAdmin) {
document.getElementById('first-admin-notice').style.display = 'block';
document.getElementById('full-name-group').style.display = 'block';
document.querySelector('button[type="submit"]').textContent = 'Create Admin Account';
}
} catch (err) {
console.error('Error checking first admin:', err);
}
}
async function handleLogin(e) {
e.preventDefault();
const form = e.target;
const submitBtn = form.querySelector('button[type="submit"]');
const originalText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="loading"></span> Processing...';
const formData = new FormData(form);
const data = Object.fromEntries(formData);
try {
const response = await fetch('/api/admin/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (response.ok) {
showAlert('Login successful!', 'success');
setTimeout(() => window.location.href = '/admin/dashboard', 500);
} else {
showAlert(result.error || 'Login failed', 'error');
}
} catch (err) {
console.error('Login error:', err);
showAlert('Login failed. Please try again.', 'error');
} finally {
submitBtn.disabled = false;
submitBtn.textContent = originalText;
}
}
// Dashboard page
if (window.location.pathname.includes('dashboard')) {
loadDashboardStats();
}
async function loadDashboardStats() {
try {
const response = await fetch('/api/admin/stats');
const stats = await response.json();
document.getElementById('stat-new-applications').textContent = stats.new_applications || 0;
document.getElementById('stat-total-applications').textContent = stats.total_applications || 0;
document.getElementById('stat-total-applicants').textContent = stats.total_applicants || 0;
document.getElementById('stat-active-jobs').textContent = stats.active_jobs || 0;
if (stats.unread_messages > 0) {
document.getElementById('stat-unread-messages').textContent = stats.unread_messages;
document.getElementById('stat-unread-messages').parentElement.style.display = 'block';
}
} catch (err) {
console.error('Error loading stats:', err);
}
}
// Applications page
if (window.location.pathname.includes('applicants')) {
loadApplications();
}
async function loadApplications(filters = {}) {
const container = document.getElementById('applications-container');
const loading = document.getElementById('loading');
try {
const params = new URLSearchParams(filters);
const response = await fetch(`/api/admin/applications?${params}`);
const applications = await response.json();
if (loading) loading.style.display = 'none';
if (applications.length === 0) {
container.innerHTML = '<div class="text-center"><p>No applications found.</p></div>';
return;
}
container.innerHTML = `
<table>
<thead>
<tr>
<th>Applicant</th>
<th>Job</th>
<th>Experience</th>
<th>Applied</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
${applications.map(app => `
<tr>
<td>
<strong>${escapeHtml(app.full_name)}</strong><br>
<small>${escapeHtml(app.email)}</small>
</td>
<td>${escapeHtml(app.job_title || 'General Application')}</td>
<td>${app.years_of_experience || 'N/A'} years</td>
<td>${new Date(app.applied_at).toLocaleDateString()}</td>
<td><span class="tag tag-${getStatusColor(app.status)}">${escapeHtml(app.status)}</span></td>
<td>
<a href="/admin/applicants?id=${app.id}" class="btn btn-sm btn-primary">View</a>
<a href="/api/admin/applications/${app.id}/cv" class="btn btn-sm btn-secondary" target="_blank">CV</a>
</td>
</tr>
`).join('')}
</tbody>
</table>
`;
} catch (err) {
console.error('Error loading applications:', err);
if (loading) loading.style.display = 'none';
container.innerHTML = '<div class="alert alert-error">Failed to load applications</div>';
}
}
// Jobs management page
if (window.location.pathname.includes('jobs') && window.location.pathname.includes('admin')) {
loadAdminJobs();
}
async function loadAdminJobs() {
const container = document.getElementById('jobs-container');
const loading = document.getElementById('loading');
try {
const response = await fetch('/api/admin/jobs');
const jobs = await response.json();
if (loading) loading.style.display = 'none';
container.innerHTML = `
<div style="margin-bottom: 2rem;">
<button onclick="showJobForm()" class="btn btn-success">+ Create New Job</button>
</div>
<table>
<thead>
<tr>
<th>Title</th>
<th>Department</th>
<th>Location</th>
<th>Status</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
${jobs.map(job => `
<tr>
<td><strong>${escapeHtml(job.title)}</strong></td>
<td>${escapeHtml(job.department || 'N/A')}</td>
<td>${escapeHtml(job.location || 'N/A')}</td>
<td><span class="tag ${job.is_active ? 'tag-success' : 'tag-error'}">${job.is_active ? 'Active' : 'Inactive'}</span></td>
<td>${new Date(job.created_at).toLocaleDateString()}</td>
<td>
<button onclick="editJob(${job.id})" class="btn btn-sm btn-primary">Edit</button>
<button onclick="toggleJobStatus(${job.id}, ${!job.is_active})" class="btn btn-sm btn-secondary">${job.is_active ? 'Deactivate' : 'Activate'}</button>
</td>
</tr>
`).join('')}
</tbody>
</table>
`;
} catch (err) {
console.error('Error loading jobs:', err);
if (loading) loading.style.display = 'none';
container.innerHTML = '<div class="alert alert-error">Failed to load jobs</div>';
}
}
async function toggleJobStatus(jobId, isActive) {
try {
const response = await fetch(`/api/admin/jobs/${jobId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ isActive })
});
if (response.ok) {
showAlert(`Job ${isActive ? 'activated' : 'deactivated'} successfully`, 'success');
loadAdminJobs();
} else {
showAlert('Failed to update job status', 'error');
}
} catch (err) {
console.error('Error updating job:', err);
showAlert('Failed to update job status', 'error');
}
}
// Logout
async function logout() {
try {
await fetch('/api/admin/logout', { method: 'POST' });
window.location.href = '/admin/login';
} catch (err) {
console.error('Logout error:', err);
window.location.href = '/admin/login';
}
}
// Utility functions
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function getStatusColor(status) {
const colors = {
'new': 'primary',
'reviewing': 'warning',
'interview': 'info',
'hired': 'success',
'rejected': 'error'
};
return colors[status] || 'primary';
}
function showAlert(message, type = 'info') {
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type}`;
alertDiv.textContent = message;
alertDiv.style.position = 'fixed';
alertDiv.style.top = '20px';
alertDiv.style.right = '20px';
alertDiv.style.zIndex = '10000';
alertDiv.style.minWidth = '300px';
document.body.appendChild(alertDiv);
setTimeout(() => {
alertDiv.remove();
}, 5000);
}