// Function to load company logo in the header
async function loadHeaderLogo() {
try {
const response = await fetch('/company/logo');
const data = await response.json();
const siteLogoImg = document.getElementById('site-company-logo');
if (siteLogoImg && data.logo) {
siteLogoImg.src = data.logo; // Now uses local URL
siteLogoImg.style.display = 'inline-block';
} else if (siteLogoImg) {
siteLogoImg.style.display = 'none';
}
} catch (error) {
console.error('Error loading header logo:', error);
const siteLogoImg = document.getElementById('site-company-logo');
if (siteLogoImg) siteLogoImg.style.display = 'none';
}
}
// Call the function to load the header logo when the DOM is loaded
document.addEventListener('DOMContentLoaded', loadHeaderLogo);
// Function to create a job card
function createJobCard(job) {
return `
Salary:${job.salary || ''}
Category:${job.category || ''}
Type:${job.type || ''}
Education:${job.education || ''}
Location:${job.location || ''}
Requirements:${(job.requirements && job.requirements.length > 0) ? job.requirements.map(r => `- ${r}
`).join('') : '- None
'}
`;
}
// Store all jobs fetched initially
let allJobs = [];
// Function to load jobs
async function loadJobs() {
try {
const response = await fetch('/jobs');
if (!response.ok) {
throw new Error('Failed to fetch jobs');
}
const jobs = await response.json();
allJobs = jobs; // Store all fetched jobs
displayJobs(allJobs, 'jobs-grid'); // Display all jobs initially
} catch (error) {
console.error('Error loading jobs:', error);
document.getElementById('jobs-grid').innerHTML = 'Failed to load jobs. Please try again later.
';
}
}
// Function to display a list of jobs in a specific grid
function displayJobs(jobsToDisplay, gridId) {
const jobCardsContainer = document.getElementById(gridId);
jobCardsContainer.innerHTML = jobsToDisplay.map(job => {
// Calculate experience level for display
let expLevel = '';
if (job.experience) expLevel = getExperienceLevel(job.experience);
return createJobCard({ ...job, experience: expLevel });
}).join('');
// Add click event listeners to job cards
jobCardsContainer.querySelectorAll('.job-card').forEach((card, idx) => {
card.addEventListener('click', () => {
const job = jobsToDisplay[idx];
showJobDetails(job);
openJobModal();
});
});
}
// Filter options are now handled by static HTML checkboxes for categories
// --- Modern Backend-Driven Filtering, Search, and Infinite Scroll ---
const locationFilter = document.getElementById('location');
const experienceFilter = document.getElementById('experience');
const typeFilter = document.getElementById('type');
const timingFilter = document.getElementById('timing');
const educationFilter = document.getElementById('education');
const jobsGrid = document.getElementById('jobs-grid');
const allJobsGrid = document.getElementById('all-jobs-grid');
const noJobsFallback = document.getElementById('no-jobs-fallback');
let jobs = [];
let skip = 0;
const limit = 15; // fetch latest 15 per page
let loading = false;
let allLoaded = false;
let lastQuery = '';
function getFiltersQuery() {
const params = new URLSearchParams();
const searchBar = document.getElementById('search-bar');
if (searchBar && searchBar.value) params.append('search', searchBar.value.trim());
// Handle multiple type selection from checkboxes
const selectedTypes = Array.from(document.querySelectorAll('input[name="type"]:checked'))
.map(checkbox => checkbox.value);
if (selectedTypes.length > 0) {
selectedTypes.forEach(type => {
params.append('types[]', type);
});
}
// Handle multiple education selection from checkboxes
const selectedEducation = Array.from(document.querySelectorAll('input[name="education"]:checked'))
.map(checkbox => checkbox.value);
if (selectedEducation.length > 0) {
selectedEducation.forEach(edu => {
params.append('education[]', edu);
});
}
// Handle multiple experience selection from checkboxes
const selectedExperience = Array.from(document.querySelectorAll('input[name="experience"]:checked'))
.map(checkbox => checkbox.value);
if (selectedExperience.length > 0) {
selectedExperience.forEach(exp => {
params.append('experience[]', exp);
});
}
// Handle multiple category selection from checkboxes
const selectedCategories = Array.from(document.querySelectorAll('input[name="category"]:checked'))
.map(checkbox => checkbox.value);
if (selectedCategories.length > 0) {
selectedCategories.forEach(category => {
params.append('categories[]', category);
});
}
params.append('skip', skip);
params.append('limit', limit);
return params.toString();
}
function showSpinner() {
let spinner = document.getElementById('infinite-scroll-spinner');
if (!spinner) {
spinner = document.createElement('div');
spinner.id = 'infinite-scroll-spinner';
spinner.innerHTML = '';
jobsGrid.parentNode.appendChild(spinner);
}
spinner.style.display = 'flex';
}
function hideSpinner() {
const spinner = document.getElementById('infinite-scroll-spinner');
if (spinner) spinner.style.display = 'none';
}
async function fetchJobs(reset = false) {
if (loading || allLoaded) return;
loading = true;
showSpinner();
if (reset) {
skip = 0;
allLoaded = false;
jobs = [];
jobsGrid.innerHTML = '';
hideNoJobs();
}
const query = getFiltersQuery();
lastQuery = query;
try {
const res = await fetch(`/jobs?${query}`);
const data = await res.json();
if (reset) jobs = [];
if (Array.isArray(data.jobs)) {
jobs = jobs.concat(data.jobs);
skip += data.jobs.length;
if (data.jobs.length < limit) allLoaded = true;
} else {
allLoaded = true;
}
renderJobs();
if (jobs.length === 0) showNoJobs();
else hideNoJobs();
} catch (err) {
showNoJobs('Failed to load jobs. Please try again later.');
}
hideSpinner();
loading = false;
}
function renderJobs() {
jobsGrid.innerHTML = jobs.map(createJobCard).join('');
jobsGrid.querySelectorAll('.job-card').forEach((card, idx) => {
card.addEventListener('click', () => {
const job = jobs[idx];
showJobDetails(job);
openJobModal();
});
});
}
function showNoJobs(msg) {
noJobsFallback.style.display = 'block';
noJobsFallback.querySelector('.no-jobs-message').textContent = msg || 'No jobs match your criteria.';
allJobsGrid.innerHTML = '';
}
function hideNoJobs() {
noJobsFallback.style.display = 'none';
}
function onFilterChange() {
skip = 0;
allLoaded = false;
fetchJobs(true);
}
// --- Search and Filter Optimization ---
// Only trigger search/filter on Enter or search icon click
document.addEventListener('DOMContentLoaded', function() {
const searchBar = document.getElementById('search-bar');
const searchIconBtn = document.querySelector('.search-icon-btn');
if (searchBar) {
searchBar.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
console.log('Search triggered by Enter key');
onFilterChange();
}
});
// Add visual feedback for search
searchBar.addEventListener('input', function() {
// Optional: Add real-time search feedback
if (searchBar.value.length > 2) {
searchBar.style.borderColor = 'var(--accent-color)';
} else {
searchBar.style.borderColor = 'var(--border-color)';
}
});
}
if (searchIconBtn) {
searchIconBtn.addEventListener('click', function(e) {
e.preventDefault();
console.log('Search triggered by button click');
onFilterChange();
});
}
});
// Remove input event for searchBar (no more search on every keystroke)
// --- Filter options are now handled by HTML checkboxes for categories ---
// --- Experience Level Calculation ---
function getExperienceLevel(years) {
if (typeof years === 'string') years = parseFloat(years);
if (isNaN(years) || years <= 1) return 'Entry';
if (years > 1 && years <= 3) return 'Mid';
return 'Senior';
}
// Use getExperienceLevel when displaying/filtering jobs
function displayJobs(jobsToDisplay, gridId) {
const jobCardsContainer = document.getElementById(gridId);
jobCardsContainer.innerHTML = jobsToDisplay.map(job => {
// Calculate experience level for display
let expLevel = '';
if (job.experience) expLevel = getExperienceLevel(job.experience);
return createJobCard({ ...job, experience: expLevel });
}).join('');
// Add click event listeners to job cards
jobCardsContainer.querySelectorAll('.job-card').forEach((card, idx) => {
card.addEventListener('click', () => {
const job = jobsToDisplay[idx];
showJobDetails(job);
openJobModal();
});
});
}
// Infinite scroll
window.addEventListener('scroll', () => {
if (loading || allLoaded) return;
if ((window.innerHeight + window.scrollY) >= (document.body.offsetHeight - 300)) {
fetchJobs();
}
});
// Debounce helper
function debounce(fn, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => fn.apply(this, args), delay);
};
}
// Initial load
fetchJobs(true);
// Function to show job details in modal
function showJobDetails(job) {
window.currentJobId = job.id;
document.getElementById('modal-company-logo').src = job.companyLogo || '/images/default-company.svg';
document.getElementById('modal-job-title').textContent = job.position || 'Position Available';
document.getElementById('modal-company-name').textContent = job.companyName || '';
document.getElementById('modal-location').textContent = job.location || '';
document.getElementById('modal-salary').textContent = job.salary || '';
document.getElementById('modal-type').textContent = job.type || '';
document.getElementById('modal-category').textContent = job.category || '';
document.getElementById('modal-timing').textContent = job.timing || '';
document.getElementById('modal-education').textContent = job.education || '';
document.getElementById('modal-experience').textContent = job.experience || '';
document.getElementById('modal-contact').textContent = job.contactNumber || '';
document.getElementById('modal-description').textContent = job.description || '';
document.getElementById('modal-requirements').innerHTML = (job.requirements && job.requirements.length > 0)
? `${job.requirements.map(r => `- ${r}
`).join('')}
`
: 'No specific requirements listed';
// Handle job image display
const jobImageElement = document.getElementById('modal-job-image');
if (job.jobImage && job.jobImage.trim() !== '') {
jobImageElement.src = job.jobImage;
jobImageElement.style.display = 'block';
} else {
console.log('No job image, hiding element');
jobImageElement.style.display = 'none';
}
}
// Function to hide job details modal
function hideJobDetailsModal() {
document.getElementById('job-modal').style.display = 'none';
}
// Function to show the application modal
function showApplicationModal(jobId) {
document.getElementById('application-job-id').value = jobId;
document.getElementById('application-modal').style.display = 'flex'; // Use flex to center modal
}
// Close modal when clicking the close button or outside the modal
const closeJobModalBtn = document.getElementById('close-job-modal');
if (closeJobModalBtn) {
closeJobModalBtn.addEventListener('click', hideJobDetailsModal);
}
const closeApplicationModalBtn = document.getElementById('close-application-modal');
if (closeApplicationModalBtn) {
closeApplicationModalBtn.addEventListener('click', hideApplicationModal);
}
window.addEventListener('click', (e) => {
const jobModal = document.getElementById('job-modal');
const applicationModal = document.getElementById('application-modal');
// Check if the click is outside the modal content
if (e.target === jobModal) {
hideJobDetailsModal();
} else if (e.target === applicationModal) {
hideApplicationModal();
}
});
// Modal open/close logic
function closeJobModal() {
document.getElementById('job-modal').style.display = 'none';
}
function openJobModal() {
document.getElementById('job-modal').style.display = 'block';
}
function closeApplyModal() {
document.getElementById('apply-modal').style.display = 'none';
}
function openApplyForm() {
closeJobModal();
document.getElementById('apply-modal').style.display = 'block';
document.getElementById('apply-job-title').textContent = document.getElementById('modal-job-title').textContent;
}
// Handle application form submission
const applyForm = document.getElementById('applyJobForm');
const applyLoadingSpinner = document.getElementById('apply-loading-spinner');
if (applyForm) {
applyForm.addEventListener('submit', async function(e) {
e.preventDefault();
const form = e.target;
const formData = new FormData(form);
const jobId = window.currentJobId;
if (!jobId) {
alert('No job selected.');
return;
}
// Show spinner
if (applyLoadingSpinner) applyLoadingSpinner.style.display = 'flex';
try {
const response = await fetch(`/jobs/${jobId}/apply`, {
method: 'POST',
body: formData
});
const result = await response.json();
// Hide spinner
if (applyLoadingSpinner) applyLoadingSpinner.style.display = 'none';
if (response.ok) {
alert('Application submitted successfully!');
closeApplyModal();
form.reset();
} else {
alert(result.message || 'Failed to submit application.');
}
} catch (err) {
// Hide spinner
if (applyLoadingSpinner) applyLoadingSpinner.style.display = 'none';
alert('Error submitting application.');
}
});
}
// Add modal functions
function showModal(message) {
const modal = document.getElementById('authModal');
const modalMessage = document.getElementById('modalMessage');
modalMessage.textContent = message;
modal.style.display = 'flex';
}
function closeModal() {
const modal = document.getElementById('authModal');
modal.style.display = 'none';
}
// Handle restricted link click
document.getElementById('add-opportunity-link').addEventListener('click', async (e) => {
e.preventDefault();
try {
const response = await fetch('/auth/check-session');
const data = await response.json();
if (!data.isLoggedIn) {
showModal('Please log in to add job opportunities.');
} else if (data.userType === 'applicant') {
showModal('Applicants cannot add job opportunities. Please contact the admin for more information.');
} else {
window.location.href = '/jobs/add';
}
} catch (error) {
console.error('Error checking session:', error);
showModal('An error occurred. Please try again later.');
}
});
// Close modal when clicking outside
window.addEventListener('click', (e) => {
const modal = document.getElementById('authModal');
if (e.target === modal) {
closeModal();
}
});
// Add a function to check login status
async function isUserLoggedIn() {
try {
const response = await fetch('/auth/check-session');
const data = await response.json();
return data.isLoggedIn;
} catch (err) {
return false;
}
}
// Update openApplyForm to check login status
async function openApplyForm() {
const loggedIn = await isUserLoggedIn();
if (!loggedIn) {
alert('Please login to apply.');
return;
}
closeJobModal();
document.getElementById('apply-modal').style.display = 'block';
document.getElementById('apply-job-title').textContent = document.getElementById('modal-job-title').textContent;
}
// --- New Filter Modal Logic ---
document.addEventListener('DOMContentLoaded', function() {
const filterModal = document.getElementById('filter-modal');
const filterToggleBtn = document.getElementById('filter-toggle-btn');
const closeFilterModalBtn = document.getElementById('close-filter-modal');
const applyFiltersBtn = document.getElementById('apply-filters-btn');
const clearFiltersBtn = document.getElementById('clear-filters-btn');
// Open filter modal
if (filterToggleBtn) {
filterToggleBtn.addEventListener('click', (e) => {
e.preventDefault();
filterModal.classList.add('open');
loadSavedFilters(); // Load previously saved filters
});
}
// Close filter modal
if (closeFilterModalBtn) {
closeFilterModalBtn.addEventListener('click', () => {
filterModal.classList.remove('open');
});
}
// Close modal when clicking outside
if (filterModal) {
filterModal.addEventListener('click', (e) => {
if (e.target === filterModal) {
filterModal.classList.remove('open');
}
});
}
// Apply filters
if (applyFiltersBtn) {
applyFiltersBtn.addEventListener('click', () => {
applyFilters();
filterModal.classList.remove('open');
});
}
// Clear all filters
if (clearFiltersBtn) {
clearFiltersBtn.addEventListener('click', () => {
clearAllFilters();
});
}
});
// Function to apply filters and fetch jobs
function applyFilters() {
// Save current filter state
saveFilters();
// Reset pagination
skip = 0;
allLoaded = false;
// Fetch jobs with new filters
fetchJobs(true);
}
// Function to save filter state to localStorage
function saveFilters() {
const filters = {
types: Array.from(document.querySelectorAll('input[name="type"]:checked')).map(cb => cb.value),
education: Array.from(document.querySelectorAll('input[name="education"]:checked')).map(cb => cb.value),
experience: Array.from(document.querySelectorAll('input[name="experience"]:checked')).map(cb => cb.value),
categories: Array.from(document.querySelectorAll('input[name="category"]:checked')).map(cb => cb.value)
};
localStorage.setItem('jobFilters', JSON.stringify(filters));
}
// Function to load saved filter state
function loadSavedFilters() {
const savedFilters = localStorage.getItem('jobFilters');
if (savedFilters) {
const filters = JSON.parse(savedFilters);
// Clear all checkboxes first
document.querySelectorAll('.filter-checkbox').forEach(cb => cb.checked = false);
// Apply saved filters
if (filters.types) {
filters.types.forEach(type => {
const checkbox = document.querySelector(`input[name="type"][value="${type}"]`);
if (checkbox) checkbox.checked = true;
});
}
if (filters.education) {
filters.education.forEach(edu => {
const checkbox = document.querySelector(`input[name="education"][value="${edu}"]`);
if (checkbox) checkbox.checked = true;
});
}
if (filters.experience) {
filters.experience.forEach(exp => {
const checkbox = document.querySelector(`input[name="experience"][value="${exp}"]`);
if (checkbox) checkbox.checked = true;
});
}
if (filters.categories) {
filters.categories.forEach(category => {
const checkbox = document.querySelector(`input[name="category"][value="${category}"]`);
if (checkbox) checkbox.checked = true;
});
}
}
}
// Function to clear all filters
function clearAllFilters() {
document.querySelectorAll('.filter-checkbox').forEach(cb => cb.checked = false);
localStorage.removeItem('jobFilters');
}