// Application state let appState = { jobs: [], currentUser: null, favorites: [], userJobs: [], notifications: [], messages: [], currentView: 'home' }; // DOM elements const elements = { // Views homeView: document.getElementById('home-view'), allJobsView: document.getElementById('all-jobs-view'), favoritesView: document.getElementById('favorites-view'), employersView: document.getElementById('employers-view'), postJobView: document.getElementById('post-job-view'), // Job containers jobCards: document.getElementById('job-cards'), allJobsCards: document.getElementById('all-jobs-cards'), favoritesCards: document.getElementById('favorites-cards'), employerJobsCards: document.getElementById('employer-jobs-cards'), // Empty states emptyFavorites: document.getElementById('empty-favorites'), emptyEmployerJobs: document.getElementById('empty-employer-jobs'), // Notifications and messages notificationsView: document.getElementById('notifications-view'), notificationsContainer: document.getElementById('notifications-container'), emptyNotifications: document.getElementById('empty-notifications'), messagesView: document.getElementById('messages-view'), messagesContainer: document.getElementById('messages-container'), emptyMessages: document.getElementById('empty-messages'), // Badges notificationBadge: document.getElementById('notification-badge'), messageBadge: document.getElementById('message-badge'), // Modals loginModal: document.getElementById('login-modal'), registerModal: document.getElementById('register-modal'), // Forms loginForm: document.getElementById('login-form'), registerForm: document.getElementById('register-form'), postJobForm: document.getElementById('post-job-form'), // Toast toast: document.getElementById('toast'), toastMessage: document.getElementById('toast-message'), // Counters jobsCount: document.getElementById('jobs-count'), favoritesCount: document.getElementById('favorites-count') }; // Load data from localStorage on initialization async function loadFromLocalStorage() { try { // Restore favorites const savedFavorites = localStorage.getItem('jobHubFavorites'); if (savedFavorites) { appState.favorites = JSON.parse(savedFavorites); } // Restore user jobs const savedUserJobs = localStorage.getItem('jobHubUserJobs'); if (savedUserJobs) { appState.userJobs = JSON.parse(savedUserJobs); } // Restore notifications const savedNotifications = localStorage.getItem('jobHubNotifications'); if (savedNotifications) { appState.notifications = JSON.parse(savedNotifications); } // Restore messages const savedMessages = localStorage.getItem('jobHubMessages'); if (savedMessages) { appState.messages = JSON.parse(savedMessages); } // Restore current user const savedUser = localStorage.getItem('jobHubCurrentUser'); if (savedUser) { appState.currentUser = JSON.parse(savedUser); // If user exists, reload their data from server if (appState.currentUser && appState.currentUser.id) { await loadUserFavorites(appState.currentUser.id); if (appState.currentUser.type === 'employer') { await loadUserJobs(appState.currentUser.id); } // Load notifications and messages from server await loadUserNotifications(appState.currentUser.id); await loadUserMessages(appState.currentUser.id); } } console.log('Data loaded from localStorage'); } catch (error) { console.error('Error loading from localStorage:', error); } } // Save data to localStorage function saveToLocalStorage() { try { localStorage.setItem('jobHubFavorites', JSON.stringify(appState.favorites)); localStorage.setItem('jobHubUserJobs', JSON.stringify(appState.userJobs)); localStorage.setItem('jobHubNotifications', JSON.stringify(appState.notifications)); localStorage.setItem('jobHubMessages', JSON.stringify(appState.messages)); if (appState.currentUser) { localStorage.setItem('jobHubCurrentUser', JSON.stringify(appState.currentUser)); } else { localStorage.removeItem('jobHubCurrentUser'); } console.log('Data saved to localStorage'); } catch (error) { console.error('Error saving to localStorage:', error); } } // Initialize the app async function initApp() { await loadJobsFromServer(); updateCounters(); updateNotificationBadge(); updateMessageBadge(); switchView('home'); updateAuthButtons(); // If user is logged in, make sure their data is properly loaded if (appState.currentUser) { if (appState.currentUser.id) { await loadUserFavorites(appState.currentUser.id); if (appState.currentUser.type === 'employer') { await loadUserJobs(appState.currentUser.id); } } } } // Load jobs from server async function loadJobsFromServer() { try { const response = await fetch('/api/jobs'); if (response.ok) { appState.jobs = await response.json(); } else { console.error('Failed to load jobs from server'); // Fallback to sample data if server is not available appState.jobs = getSampleJobs(); } } catch (error) { console.error('Error loading jobs:', error); appState.jobs = getSampleJobs(); } } // Get sample jobs for fallback function getSampleJobs() { return [ { id: 1, title: "Senior Frontend Разработчик", company: "TechNova", location: "Москва", salary: "180 000 - 250 000 ₽", description: "Требуется опытный frontend разработчик для работы с современным стеком: React, TypeScript, Next.js. Опыт работы от 5 лет, знание Redux, GraphQL будет плюсом.", tags: ["React", "TypeScript", "Next.js", "Redux", "GraphQL"], type: "full-time" }, { id: 2, title: "Backend Разработчик (Node.js)", company: "CloudSolutions", location: "Санкт-Петербург", salary: "200 000 - 280 000 ₽", description: "Ищем backend разработчика с опытом работы с Node.js, Express и микросервисной архитектурой. Знание Docker, Kubernetes и AWS обязательно.", tags: ["Node.js", "Express", "Docker", "Kubernetes", "AWS"], type: "full-time" }, { id: 3, title: "Full Stack Разработчик", company: "DigitalAgency Pro", location: "Екатеринбург", salary: "150 000 - 220 000 ₽", description: "Нужен full stack разработчик для работы над веб-приложениями. Опыт с React и Node.js обязателен. Знание TypeScript будет преимуществом.", tags: ["React", "Node.js", "TypeScript", "Full Stack"], type: "full-time" }, { id: 4, title: "Python Разработчик (ML)", company: "DataScience Inc", location: "Новосибирск", salary: "160 000 - 240 000 ₽", description: "Требуется Python разработчик для работы с данными и машинным обучением. Опыт с Django/Flask, TensorFlow/PyTorch обязателен.", tags: ["Python", "Django", "Machine Learning", "TensorFlow"], type: "full-time" }, { id: 5, title: "DevOps Инженер", company: "CloudTech", location: "Казань", salary: "220 000 - 300 000 ₽", description: "Ищем DevOps инженера с опытом работы с AWS, Kubernetes и CI/CD. Знание Terraform и Ansible будет плюсом.", tags: ["DevOps", "AWS", "Kubernetes", "CI/CD"], type: "full-time" }, { id: 6, title: "UI/UX Дизайнер", company: "CreativeMinds", location: "Москва", salary: "120 000 - 180 000 ₽", description: "Требуется UI/UX дизайнер для создания пользовательских интерфейсов. Опыт с Figma, Adobe XD обязателен.", tags: ["UI/UX", "Figma", "Adobe XD", "Design"], type: "full-time" } ]; } // Display jobs function displayJobs(jobs, container) { container.innerHTML = ''; if (jobs.length === 0) { container.innerHTML = `
🔍

Вакансий не найдено

Попробуйте изменить параметры поиска

`; return; } jobs.forEach(job => { const jobCard = document.createElement('div'); jobCard.className = 'job-card'; // Check if job is favorited const isFavorited = appState.favorites.some(fav => fav.id === job.id); jobCard.innerHTML = `

${job.title}

${job.company}
${job.location}
${job.salary}

${job.description}

${job.tags.map(tag => `${tag}`).join('')}
`; container.appendChild(jobCard); }); } // Switch between views function switchView(view) { // Hide all views document.querySelectorAll('.view').forEach(v => v.classList.add('hidden')); document.querySelectorAll('nav a').forEach(a => a.classList.remove('active')); // Show selected view const viewElement = document.getElementById(`${view}-view`); if (viewElement) { viewElement.classList.remove('hidden'); appState.currentView = view; // Update active nav link const navLink = document.querySelector(`nav a[onclick="switchView('${view}')"]`); if (navLink) { navLink.classList.add('active'); } // Handle specific view logic switch (view) { case 'home': displayJobs(appState.jobs.slice(0, 6), elements.jobCards); break; case 'all-jobs': displayJobs(appState.jobs, elements.allJobsCards); elements.jobsCount.textContent = `${appState.jobs.length} вакансий`; break; case 'favorites': displayFavorites(); break; case 'notifications': displayNotifications(); break; case 'messages': displayMessages(); break; case 'employers': displayEmployerJobs(); break; case 'post-job': // Clear form elements.postJobForm.reset(); break; } } } // Filter jobs based on search criteria function filterJobs() { const jobSearchTerm = document.getElementById('job-search').value.trim().toLowerCase(); const locationSearchTerm = document.getElementById('location-search').value.trim().toLowerCase(); let filteredJobs = appState.jobs; // Filter by job title, description, or tags if search term is provided if (jobSearchTerm) { filteredJobs = filteredJobs.filter(job => { return job.title.toLowerCase().includes(jobSearchTerm) || job.description.toLowerCase().includes(jobSearchTerm) || job.tags.some(tag => tag.toLowerCase().includes(jobSearchTerm)); }); } // Filter by location if location term is provided if (locationSearchTerm) { filteredJobs = filteredJobs.filter(job => { return job.location.toLowerCase().includes(locationSearchTerm); }); } // Sort results by relevance (prioritize matches in title) filteredJobs.sort((a, b) => { const aTitleMatch = a.title.toLowerCase().includes(jobSearchTerm); const bTitleMatch = b.title.toLowerCase().includes(jobSearchTerm); if (aTitleMatch && !bTitleMatch) return -1; if (!aTitleMatch && bTitleMatch) return 1; return 0; }); if (appState.currentView === 'home') { displayJobs(filteredJobs.slice(0, 6), elements.jobCards); } else if (appState.currentView === 'all-jobs') { displayJobs(filteredJobs, elements.allJobsCards); elements.jobsCount.textContent = `${filteredJobs.length} вакансий`; } } // Advanced search functionality function advancedFilterJobs(filters) { let filteredJobs = appState.jobs; // Apply keyword filter if (filters.keyword) { const keyword = filters.keyword.toLowerCase(); filteredJobs = filteredJobs.filter(job => { return job.title.toLowerCase().includes(keyword) || job.description.toLowerCase().includes(keyword) || job.tags.some(tag => tag.toLowerCase().includes(keyword)); }); } // Apply location filter if (filters.location) { const location = filters.location.toLowerCase(); filteredJobs = filteredJobs.filter(job => { return job.location.toLowerCase().includes(location); }); } // Apply salary range filter if (filters.minSalary || filters.maxSalary) { filteredJobs = filteredJobs.filter(job => { // Extract numeric salary values for comparison const salaryText = job.salary.replace(/[^\d\s]/g, '').trim(); if (!salaryText) return true; // If no salary info, include by default const salaryNumbers = salaryText.match(/\d+/g); if (!salaryNumbers) return true; const avgSalary = salaryNumbers.reduce((sum, num) => sum + parseInt(num), 0) / salaryNumbers.length; if (filters.minSalary && avgSalary < filters.minSalary) return false; if (filters.maxSalary && avgSalary > filters.maxSalary) return false; return true; }); } // Apply job type filter if (filters.jobType && filters.jobType !== 'all') { filteredJobs = filteredJobs.filter(job => job.type === filters.jobType); } // Apply tags filter if (filters.tags && filters.tags.length > 0) { filteredJobs = filteredJobs.filter(job => { return filters.tags.every(tag => job.tags.some(jobTag => jobTag.toLowerCase().includes(tag.toLowerCase()) ) ); }); } // Sort results by relevance if (filters.keyword) { filteredJobs.sort((a, b) => { const keyword = filters.keyword.toLowerCase(); const aTitleMatch = a.title.toLowerCase().includes(keyword); const bTitleMatch = b.title.toLowerCase().includes(keyword); if (aTitleMatch && !bTitleMatch) return -1; if (!aTitleMatch && bTitleMatch) return 1; return 0; }); } return filteredJobs; } // Debounced search function to improve performance let searchTimeout; function debouncedSearch() { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { filterJobs(); }, 300); // Wait 300ms after user stops typing } // Display favorites function displayFavorites() { if (appState.favorites.length === 0) { elements.favoritesCards.innerHTML = ''; elements.emptyFavorites.classList.remove('hidden'); } else { elements.emptyFavorites.classList.add('hidden'); displayJobs(appState.favorites, elements.favoritesCards); } updateCounters(); } // Display notifications async function displayNotifications() { // Load notifications from server if user is logged in if (appState.currentUser && appState.currentUser.id) { try { const response = await fetch(`/api/users/${appState.currentUser.id}/notifications`); if (response.ok) { const serverNotifications = await response.json(); // Update local state with server data appState.notifications = serverNotifications.map(n => ({ id: n.id, title: n.title, text: n.text, icon: n.icon, timestamp: n.created_at, read: n.read_status })); } } catch (error) { console.error('Error loading notifications from server:', error); } } elements.notificationsContainer.innerHTML = ''; if (appState.notifications.length === 0) { elements.emptyNotifications.classList.remove('hidden'); return; } elements.emptyNotifications.classList.add('hidden'); appState.notifications.forEach(notification => { const notificationItem = document.createElement('div'); notificationItem.className = `notification-item ${!notification.read ? 'unread' : ''}`; notificationItem.innerHTML = `
${notification.icon || '🔔'}
${notification.title}
${notification.text}
${formatTimeAgo(notification.timestamp)}
`; // Mark as read when displayed if (!notification.read) { notification.read = true; updateNotificationBadge(); // Update server as well if (appState.currentUser && appState.currentUser.id) { fetch(`/api/users/${appState.currentUser.id}/notifications/${notification.id}/read`, { method: 'PUT' }).catch(console.error); } } elements.notificationsContainer.appendChild(notificationItem); }); } // Display messages async function displayMessages() { // Load messages from server if user is logged in if (appState.currentUser && appState.currentUser.id) { try { const response = await fetch(`/api/users/${appState.currentUser.id}/messages`); if (response.ok) { const serverMessages = await response.json(); // Update local state with server data appState.messages = serverMessages.map(m => ({ id: m.id, sender: { name: m.sender_name || 'Unknown', avatar: '👤' }, content: m.content, timestamp: m.created_at, read: m.read_status })); } } catch (error) { console.error('Error loading messages from server:', error); } } elements.messagesContainer.innerHTML = ''; if (appState.messages.length === 0) { elements.emptyMessages.classList.remove('hidden'); return; } elements.emptyMessages.classList.add('hidden'); appState.messages.forEach(message => { const messageItem = document.createElement('div'); messageItem.className = `message-item ${!message.read ? 'unread' : ''}`; messageItem.innerHTML = `
${message.sender.avatar || '👤'}
${message.sender.name}
${message.content}
${formatTimeAgo(message.timestamp)}
`; // Mark as read when displayed if (!message.read) { message.read = true; updateMessageBadge(); // Update server as well if (appState.currentUser && appState.currentUser.id) { fetch(`/api/users/${appState.currentUser.id}/messages/${message.id}/read`, { method: 'PUT' }).catch(console.error); } } elements.messagesContainer.appendChild(messageItem); }); } // Add notification async function addNotification(title, text, icon = '🔔') { const notification = { id: Date.now(), title: title, text: text, icon: icon, timestamp: new Date().toISOString(), read: false }; // Add to local state appState.notifications.unshift(notification); updateNotificationBadge(); saveToLocalStorage(); // Try to add to server if user is logged in if (appState.currentUser && appState.currentUser.id) { try { await fetch(`/api/users/${appState.currentUser.id}/notifications`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title, text, icon }) }); } catch (error) { console.error('Error adding notification to server:', error); } } // Show toast notification as well showToast(text); } // Add message async function addMessage(sender, content) { const message = { id: Date.now(), sender: sender, content: content, timestamp: new Date().toISOString(), read: false }; appState.messages.unshift(message); updateMessageBadge(); saveToLocalStorage(); // Show notification about new message await addNotification('Новое сообщение', `Сообщение от ${sender.name}`, '💬'); } // Format time ago function formatTimeAgo(timestamp) { const date = new Date(timestamp); const now = new Date(); const seconds = Math.floor((now - date) / 1000); if (seconds < 60) return 'только что'; if (seconds < 3600) return `${Math.floor(seconds / 60)} мин назад`; if (seconds < 86400) return `${Math.floor(seconds / 3600)} ч назад`; return `${Math.floor(seconds / 86400)} дн назад`; } // Update notification badge function updateNotificationBadge() { const unreadCount = appState.notifications.filter(n => !n.read).length; if (unreadCount > 0) { elements.notificationBadge.textContent = unreadCount; elements.notificationBadge.classList.remove('hidden'); } else { elements.notificationBadge.classList.add('hidden'); } } // Update message badge function updateMessageBadge() { const unreadCount = appState.messages.filter(m => !m.read).length; if (unreadCount > 0) { elements.messageBadge.textContent = unreadCount; elements.messageBadge.classList.remove('hidden'); } else { elements.messageBadge.classList.add('hidden'); } } // Clear all notifications async function clearNotifications() { appState.notifications = []; updateNotificationBadge(); saveToLocalStorage(); // Clear notifications on server if user is logged in if (appState.currentUser && appState.currentUser.id) { try { await fetch(`/api/users/${appState.currentUser.id}/notifications`, { method: 'DELETE' }); } catch (error) { console.error('Error clearing notifications from server:', error); } } displayNotifications(); } // Reply to message function replyToMessage(messageId) { if (!appState.currentUser) { showToast('Для ответа на сообщение нужно авторизоваться'); showLoginModal(); return; } // For a real implementation, we would need to know who sent the original message // For now, we'll just allow sending a message to another user by ID const recipientId = prompt('Введите ID получателя (для тестирования используйте ID другого пользователя):'); if (recipientId) { const reply = prompt('Введите ваш ответ:'); if (reply) { sendMessageToUser(parseInt(recipientId), reply); } } } // Send message to user async function sendMessageToUser(receiverId, content) { if (!appState.currentUser || !appState.currentUser.id) { showToast('Для отправки сообщения нужно авторизоваться'); showLoginModal(); return; } try { const response = await fetch('/api/messages', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sender_id: appState.currentUser.id, receiver_id: receiverId, content: content }) }); if (response.ok) { showToast('Сообщение отправлено'); } else { const errorData = await response.json(); showToast(errorData.error || 'Ошибка отправки сообщения'); } } catch (error) { console.error('Error sending message:', error); showToast('Ошибка отправки сообщения'); } } // Delete message async function deleteMessage(messageId) { appState.messages = appState.messages.filter(m => m.id !== messageId); updateMessageBadge(); saveToLocalStorage(); // Delete message on server if user is logged in if (appState.currentUser && appState.currentUser.id) { try { await fetch(`/api/users/${appState.currentUser.id}/messages/${messageId}`, { method: 'DELETE' }); } catch (error) { console.error('Error deleting message from server:', error); } } displayMessages(); } // Display employer's jobs function displayEmployerJobs() { if (appState.userJobs.length === 0) { elements.employerJobsCards.innerHTML = ''; elements.emptyEmployerJobs.classList.remove('hidden'); } else { elements.emptyEmployerJobs.classList.add('hidden'); displayJobs(appState.userJobs, elements.employerJobsCards); } } // Update counters function updateCounters() { elements.favoritesCount.textContent = `${appState.favorites.length} избранных`; } // Show login modal function showLoginModal() { elements.loginModal.classList.add('active'); } // Hide login modal function hideLoginModal() { elements.loginModal.classList.remove('active'); elements.loginForm.reset(); } // Show register modal function showRegisterModal() { elements.registerModal.classList.add('active'); } // Hide register modal function hideRegisterModal() { elements.registerModal.classList.remove('active'); elements.registerForm.reset(); } // Handle login async function handleLogin(e) { e.preventDefault(); const email = document.getElementById('login-email').value; const password = document.getElementById('login-password').value; const userType = document.getElementById('login-type').value; // Simple validation if (!email || !password) { showToast('Пожалуйста, заполните все поля'); return; } try { // Try to login via server API const response = await fetch('/api/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); if (response.ok) { const userData = await response.json(); appState.currentUser = { id: userData.id, name: userData.name, email: userData.email, type: userData.type }; // Load user's favorites, jobs, notifications and messages await loadUserFavorites(userData.id); if (userData.type === 'employer') { await loadUserJobs(userData.id); } await loadUserNotifications(userData.id); await loadUserMessages(userData.id); hideLoginModal(); showToast(`Добро пожаловать, ${appState.currentUser.name}!`); updateAuthButtons(); saveToLocalStorage(); } else { // Fallback to client-side login simulation appState.currentUser = { name: 'Пользователь', email: email, type: userType }; hideLoginModal(); showToast(`Добро пожаловать, ${appState.currentUser.name}!`); updateAuthButtons(); saveToLocalStorage(); } } catch (error) { console.error('Login error:', error); // Fallback to client-side login simulation appState.currentUser = { name: 'Пользователь', email: email, type: userType }; hideLoginModal(); showToast(`Добро пожаловать, ${appState.currentUser.name}!`); updateAuthButtons(); saveToLocalStorage(); } } // Load user favorites from server async function loadUserFavorites(userId) { try { const response = await fetch(`/api/users/${userId}/favorites`); if (response.ok) { appState.favorites = await response.json(); } } catch (error) { console.error('Error loading favorites:', error); } } // Load user jobs from server async function loadUserJobs(userId) { try { const response = await fetch(`/api/users/${userId}/jobs`); if (response.ok) { appState.userJobs = await response.json(); } } catch (error) { console.error('Error loading user jobs:', error); } } // Load user notifications from server async function loadUserNotifications(userId) { try { const response = await fetch(`/api/users/${userId}/notifications`); if (response.ok) { const serverNotifications = await response.json(); appState.notifications = serverNotifications.map(n => ({ id: n.id, title: n.title, text: n.text, icon: n.icon, timestamp: n.created_at, read: n.read_status })); updateNotificationBadge(); } } catch (error) { console.error('Error loading user notifications:', error); } } // Load user messages from server async function loadUserMessages(userId) { try { const response = await fetch(`/api/users/${userId}/messages`); if (response.ok) { const serverMessages = await response.json(); appState.messages = serverMessages.map(m => ({ id: m.id, sender: { name: m.sender_name || 'Unknown', avatar: '👤' }, content: m.content, timestamp: m.created_at, read: m.read_status })); updateMessageBadge(); } } catch (error) { console.error('Error loading user messages:', error); } } // Handle registration async function handleRegister(e) { e.preventDefault(); const name = document.getElementById('register-name').value; const email = document.getElementById('register-email').value; const password = document.getElementById('register-password').value; const userType = document.getElementById('register-type').value; // Simple validation if (!name || !email || !password) { showToast('Пожалуйста, заполните все поля'); return; } try { // Try to register via server API const response = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, email, password, type: userType }) }); if (response.ok) { const userData = await response.json(); appState.currentUser = { id: userData.id, name: userData.name, email: userData.email, type: userData.type }; // Initialize empty notifications and messages arrays for new user appState.notifications = []; appState.messages = []; hideRegisterModal(); showToast(`Регистрация успешна! Добро пожаловать, ${name}!`); updateAuthButtons(); saveToLocalStorage(); } else { const errorData = await response.json(); showToast(errorData.error || 'Ошибка регистрации'); } } catch (error) { console.error('Registration error:', error); // Fallback to client-side registration simulation appState.currentUser = { name: name, email: email, type: userType }; // Initialize empty notifications and messages arrays for new user appState.notifications = []; appState.messages = []; hideRegisterModal(); showToast(`Регистрация успешна! Добро пожаловать, ${name}!`); updateAuthButtons(); saveToLocalStorage(); } } // Handle post job async function handlePostJob(e) { e.preventDefault(); if (!appState.currentUser || appState.currentUser.type !== 'employer') { showToast('Только работодатели могут размещать вакансии'); return; } const job = { title: document.getElementById('job-title').value, company: document.getElementById('job-company').value, location: document.getElementById('job-location').value, salary: document.getElementById('job-salary').value || 'По договоренности', description: document.getElementById('job-description').value, tags: document.getElementById('job-tags').value.split(',').map(tag => tag.trim()).filter(tag => tag), type: document.getElementById('job-type').value, user_id: appState.currentUser.id }; try { // Try to post job via server API const response = await fetch('/api/jobs', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(job) }); if (response.ok) { const newJob = await response.json(); // Add to jobs and user jobs appState.jobs.unshift(newJob); appState.userJobs.unshift(newJob); showToast('Вакансия успешно размещена!'); // Add notification about successful job posting addNotification('Вакансия опубликована', `Ваша вакансия "${job.title}" успешно опубликована`, '✅'); elements.postJobForm.reset(); switchView('employers'); saveToLocalStorage(); } else { const errorData = await response.json(); showToast(errorData.error || 'Ошибка размещения вакансии'); } } catch (error) { console.error('Error posting job:', error); // Fallback to client-side addition job.id = Date.now(); job.created_at = new Date().toISOString(); // Add to jobs and user jobs appState.jobs.unshift(job); appState.userJobs.unshift(job); showToast('Вакансия успешно размещена!'); // Add notification about successful job posting addNotification('Вакансия опубликована', `Ваша вакансия "${job.title}" успешно опубликована`, '✅'); elements.postJobForm.reset(); switchView('employers'); saveToLocalStorage(); } } // Toggle favorite async function toggleFavorite(jobId) { if (!appState.currentUser) { showToast('Для добавления в избранное нужно авторизоваться'); showLoginModal(); return; } const job = appState.jobs.find(j => j.id === jobId); if (!job) return; const isFavorite = appState.favorites.some(fav => fav.id === jobId); try { if (isFavorite) { // Remove from favorites via server API const response = await fetch(`/api/users/${appState.currentUser.id}/favorites/${jobId}`, { method: 'DELETE' }); if (response.ok || response.status === 404) { appState.favorites = appState.favorites.filter(fav => fav.id !== jobId); showToast('Вакансия удалена из избранного'); // Add notification addNotification('Избранное обновлено', `Вакансия "${job.title}" удалена из избранного`, '🗑️'); } else { throw new Error('Failed to remove from favorites'); } } else { // Add to favorites via server API const response = await fetch(`/api/users/${appState.currentUser.id}/favorites`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jobId }) }); if (response.ok) { appState.favorites.push(job); showToast('Вакансия добавлена в избранное'); // Add notification addNotification('Избранное обновлено', `Вакансия "${job.title}" добавлена в избранное`, '❤️'); } else { throw new Error('Failed to add to favorites'); } } } catch (error) { console.error('Favorite toggle error:', error); // Fallback to client-side toggle if (isFavorite) { appState.favorites = appState.favorites.filter(fav => fav.id !== jobId); showToast('Вакансия удалена из избранного'); // Add notification addNotification('Избранное обновлено', `Вакансия "${job.title}" удалена из избранного`, '🗑️'); } else { appState.favorites.push(job); showToast('Вакансия добавлена в избранное'); // Add notification addNotification('Избранное обновлено', `Вакансия "${job.title}" добавлена в избранное`, '❤️'); } } // Update UI if (appState.currentView === 'home') { displayJobs(appState.jobs.slice(0, 6), elements.jobCards); } else if (appState.currentView === 'all-jobs') { displayJobs(appState.jobs, elements.allJobsCards); } else if (appState.currentView === 'favorites') { displayFavorites(); } updateCounters(); saveToLocalStorage(); } // Apply for job function applyForJob(jobId) { if (!appState.currentUser) { showToast('Для отклика на вакансию нужно авторизоваться'); showLoginModal(); return; } const job = appState.jobs.find(j => j.id === jobId); if (job) { showToast('Ваш отклик отправлен работодателю!'); // Add notification about job application addNotification('Отклик отправлен', `Ваш отклик на вакансию "${job.title}" отправлен работодателю`, '📤'); } else { showToast('Ваш отклик отправлен работодателю!'); } // Here would be the logic to actually submit an application // For now, we'll just show a success message and add a notification } // Update auth buttons function updateAuthButtons() { const authButtons = document.getElementById('auth-buttons'); if (appState.currentUser) { authButtons.innerHTML = `
Привет, ${appState.currentUser.name}! (${appState.currentUser.type === 'employer' ? 'Работодатель' : 'Соискатель'})
`; } else { authButtons.innerHTML = ` `; } } // Handle logout function handleLogout() { appState.currentUser = null; appState.favorites = []; appState.userJobs = []; appState.notifications = []; appState.messages = []; updateAuthButtons(); updateNotificationBadge(); updateMessageBadge(); showToast('Вы успешно вышли из аккаунта'); switchView('home'); saveToLocalStorage(); // Also remove from localStorage localStorage.removeItem('jobHubCurrentUser'); localStorage.removeItem('jobHubFavorites'); localStorage.removeItem('jobHubUserJobs'); localStorage.removeItem('jobHubNotifications'); localStorage.removeItem('jobHubMessages'); } // Show toast notification function showToast(message) { elements.toastMessage.textContent = message; elements.toast.classList.add('active'); setTimeout(() => { elements.toast.classList.remove('active'); }, 3000); } // Initialize the app when DOM is loaded document.addEventListener('DOMContentLoaded', async function() { await loadFromLocalStorage(); await initApp(); // Add event listeners for search inputs document.getElementById('job-search').addEventListener('input', debouncedSearch); document.getElementById('location-search').addEventListener('input', debouncedSearch); // Add event listeners for Enter key submission document.getElementById('job-search').addEventListener('keypress', function(e) { if (e.key === 'Enter') { filterJobs(); } }); document.getElementById('location-search').addEventListener('keypress', function(e) { if (e.key === 'Enter') { filterJobs(); } }); });