864 lines
28 KiB
HTML
864 lines
28 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Избранное | Rabota.Today</title>
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
}
|
||
|
||
body {
|
||
background: linear-gradient(145deg, #eef5fa 0%, #e0eaf5 100%);
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
}
|
||
|
||
.header {
|
||
background: #0b1c34;
|
||
color: white;
|
||
padding: 20px 40px;
|
||
border-radius: 40px;
|
||
margin-bottom: 40px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 20px;
|
||
}
|
||
|
||
.logo {
|
||
font-size: 28px;
|
||
font-weight: 700;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15px;
|
||
}
|
||
|
||
.logo i {
|
||
color: #3b82f6;
|
||
background: rgba(255,255,255,0.1);
|
||
padding: 12px;
|
||
border-radius: 20px;
|
||
}
|
||
|
||
.nav {
|
||
display: flex;
|
||
gap: 15px;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.nav a {
|
||
color: white;
|
||
text-decoration: none;
|
||
padding: 10px 20px;
|
||
border-radius: 30px;
|
||
transition: 0.2s;
|
||
}
|
||
|
||
.nav a:hover {
|
||
background: rgba(255,255,255,0.1);
|
||
}
|
||
|
||
.nav .active {
|
||
background: #3b82f6;
|
||
}
|
||
|
||
.profile-link {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
background: #3b82f6;
|
||
padding: 8px 20px !important;
|
||
}
|
||
|
||
.profile-link i {
|
||
font-size: 18px;
|
||
}
|
||
|
||
.user-avatar {
|
||
width: 35px;
|
||
height: 35px;
|
||
background: #3b82f6;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.user-name {
|
||
font-weight: 500;
|
||
}
|
||
|
||
.admin-badge {
|
||
background: #f59e0b;
|
||
color: white;
|
||
padding: 2px 8px;
|
||
border-radius: 20px;
|
||
font-size: 12px;
|
||
margin-left: 5px;
|
||
}
|
||
|
||
.back-to-profile {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
color: #4f7092;
|
||
text-decoration: none;
|
||
margin-bottom: 20px;
|
||
padding: 8px 16px;
|
||
border-radius: 30px;
|
||
background: white;
|
||
transition: 0.2s;
|
||
}
|
||
|
||
.back-to-profile:hover {
|
||
background: #eef4fa;
|
||
color: #0b1c34;
|
||
}
|
||
|
||
.favorites-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 30px;
|
||
flex-wrap: wrap;
|
||
gap: 20px;
|
||
}
|
||
|
||
.favorites-header h1 {
|
||
font-size: 32px;
|
||
color: #0b1c34;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.favorites-header h1 i {
|
||
color: #b91c1c;
|
||
font-size: 28px;
|
||
}
|
||
|
||
.filter-tabs {
|
||
display: flex;
|
||
gap: 10px;
|
||
background: white;
|
||
padding: 8px;
|
||
border-radius: 50px;
|
||
box-shadow: 0 5px 15px rgba(0,0,0,0.05);
|
||
}
|
||
|
||
.filter-tab {
|
||
padding: 10px 24px;
|
||
border-radius: 40px;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
transition: 0.2s;
|
||
color: #4f7092;
|
||
}
|
||
|
||
.filter-tab:hover {
|
||
background: #eef4fa;
|
||
}
|
||
|
||
.filter-tab.active {
|
||
background: #0b1c34;
|
||
color: white;
|
||
}
|
||
|
||
.stats-summary {
|
||
background: white;
|
||
border-radius: 30px;
|
||
padding: 20px 30px;
|
||
margin-bottom: 30px;
|
||
display: flex;
|
||
gap: 40px;
|
||
flex-wrap: wrap;
|
||
box-shadow: 0 10px 30px rgba(0,20,40,0.05);
|
||
}
|
||
|
||
.stat-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.stat-item .value {
|
||
font-size: 28px;
|
||
font-weight: 700;
|
||
color: #0b1c34;
|
||
}
|
||
|
||
.stat-item .label {
|
||
color: #4f7092;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.favorites-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||
gap: 30px;
|
||
}
|
||
|
||
.favorite-card {
|
||
background: white;
|
||
border-radius: 40px;
|
||
padding: 30px;
|
||
transition: 0.3s;
|
||
cursor: pointer;
|
||
position: relative;
|
||
box-shadow: 0 10px 30px rgba(0,0,0,0.05);
|
||
}
|
||
|
||
.favorite-card:hover {
|
||
transform: translateY(-5px);
|
||
box-shadow: 0 20px 40px rgba(0,20,40,0.15);
|
||
}
|
||
|
||
.favorite-card.vacancy {
|
||
border-left: 5px solid #3b82f6;
|
||
}
|
||
|
||
.favorite-card.resume {
|
||
border-left: 5px solid #10b981;
|
||
}
|
||
|
||
.favorite-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: start;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.favorite-type {
|
||
background: #eef4fa;
|
||
padding: 6px 14px;
|
||
border-radius: 30px;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: #1f3f60;
|
||
}
|
||
|
||
.favorite-type.vacancy {
|
||
background: #dbeafe;
|
||
color: #1e40af;
|
||
}
|
||
|
||
.favorite-type.resume {
|
||
background: #d1fae5;
|
||
color: #065f46;
|
||
}
|
||
|
||
.remove-btn {
|
||
background: #fee2e2;
|
||
border: none;
|
||
color: #b91c1c;
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 18px;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: 0.2s;
|
||
}
|
||
|
||
.remove-btn:hover {
|
||
background: #fecaca;
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.favorite-title {
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
color: #0b1c34;
|
||
margin-bottom: 8px;
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.favorite-subtitle {
|
||
color: #3b82f6;
|
||
font-weight: 600;
|
||
margin-bottom: 10px;
|
||
font-size: 15px;
|
||
}
|
||
|
||
.favorite-salary {
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
color: #0f2b4f;
|
||
margin: 15px 0;
|
||
padding: 10px 0;
|
||
border-top: 1px solid #dee9f5;
|
||
border-bottom: 1px solid #dee9f5;
|
||
}
|
||
|
||
.favorite-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin: 15px 0;
|
||
}
|
||
|
||
.tag {
|
||
background: #eef4fa;
|
||
padding: 6px 14px;
|
||
border-radius: 30px;
|
||
font-size: 13px;
|
||
color: #1f3f60;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.favorite-footer {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
border-top: 1px solid #dee9f5;
|
||
padding-top: 20px;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.favorite-date {
|
||
color: #4f7092;
|
||
font-size: 14px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
}
|
||
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 80px 40px;
|
||
background: white;
|
||
border-radius: 40px;
|
||
grid-column: 1/-1;
|
||
box-shadow: 0 10px 30px rgba(0,0,0,0.05);
|
||
}
|
||
|
||
.empty-state i {
|
||
font-size: 80px;
|
||
color: #cbd5e1;
|
||
margin-bottom: 25px;
|
||
}
|
||
|
||
.empty-state h2 {
|
||
font-size: 28px;
|
||
color: #0b1c34;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.empty-state p {
|
||
color: #4f7092;
|
||
margin-bottom: 30px;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.empty-state-actions {
|
||
display: flex;
|
||
gap: 15px;
|
||
justify-content: center;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.btn {
|
||
padding: 14px 32px;
|
||
border-radius: 40px;
|
||
border: none;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
text-decoration: none;
|
||
font-size: 16px;
|
||
transition: 0.2s;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: #0b1c34;
|
||
color: white;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background: #1b3f6b;
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.btn-outline {
|
||
background: transparent;
|
||
border: 2px solid #3b82f6;
|
||
color: #0b1c34;
|
||
}
|
||
|
||
.btn-outline:hover {
|
||
background: #eef4fa;
|
||
}
|
||
|
||
.loading {
|
||
text-align: center;
|
||
padding: 80px;
|
||
color: #4f7092;
|
||
font-size: 18px;
|
||
grid-column: 1/-1;
|
||
background: white;
|
||
border-radius: 40px;
|
||
}
|
||
|
||
.loading i {
|
||
font-size: 40px;
|
||
margin-bottom: 15px;
|
||
color: #3b82f6;
|
||
}
|
||
|
||
#notification {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
padding: 16px 24px;
|
||
border-radius: 30px;
|
||
background: white;
|
||
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||
z-index: 9999;
|
||
animation: slideIn 0.3s;
|
||
max-width: 350px;
|
||
display: none;
|
||
}
|
||
|
||
#notification.success {
|
||
background: #10b981;
|
||
color: white;
|
||
}
|
||
|
||
#notification.error {
|
||
background: #ef4444;
|
||
color: white;
|
||
}
|
||
|
||
@keyframes slideIn {
|
||
from { transform: translateX(100%); opacity: 0; }
|
||
to { transform: translateX(0); opacity: 1; }
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.favorites-header {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.filter-tabs {
|
||
width: 100%;
|
||
justify-content: stretch;
|
||
}
|
||
|
||
.filter-tab {
|
||
flex: 1;
|
||
text-align: center;
|
||
padding: 10px 16px;
|
||
}
|
||
|
||
.stats-summary {
|
||
justify-content: space-around;
|
||
}
|
||
|
||
.favorites-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<div class="logo">
|
||
<i class="fas fa-briefcase"></i>
|
||
Rabota.Today
|
||
</div>
|
||
<div class="nav" id="nav">
|
||
<!-- Навигация будет заполнена динамически -->
|
||
</div>
|
||
</div>
|
||
|
||
<a href="/profile" class="back-to-profile">
|
||
<i class="fas fa-arrow-left"></i> Вернуться в профиль
|
||
</a>
|
||
|
||
<div class="favorites-header">
|
||
<h1>
|
||
<i class="fas fa-heart"></i>
|
||
Избранное
|
||
</h1>
|
||
<div class="filter-tabs">
|
||
<div class="filter-tab" data-filter="all" onclick="filterFavorites('all')">Все</div>
|
||
<div class="filter-tab" data-filter="vacancy" onclick="filterFavorites('vacancy')">Вакансии</div>
|
||
<div class="filter-tab" data-filter="resume" onclick="filterFavorites('resume')">Резюме</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Статистика избранного -->
|
||
<div class="stats-summary" id="statsSummary">
|
||
<div class="stat-item">
|
||
<span class="value" id="totalCount">0</span>
|
||
<span class="label">всего</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="value" id="vacancyCount">0</span>
|
||
<span class="label">вакансий</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="value" id="resumeCount">0</span>
|
||
<span class="label">резюме</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="favoritesContainer" class="favorites-grid">
|
||
<div class="loading">
|
||
<i class="fas fa-spinner fa-spin"></i>
|
||
<div>Загрузка избранного...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="notification"></div>
|
||
|
||
<script>
|
||
const API_BASE_URL = 'http://localhost:8000/api';
|
||
let currentUser = null;
|
||
let currentFilter = 'all';
|
||
let favorites = [];
|
||
|
||
// Получаем параметр filter из URL
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const urlFilter = urlParams.get('type');
|
||
if (urlFilter && ['all', 'vacancy', 'resume'].includes(urlFilter)) {
|
||
currentFilter = urlFilter;
|
||
}
|
||
|
||
// Проверка авторизации
|
||
const token = localStorage.getItem('accessToken');
|
||
if (!token) {
|
||
window.location.href = '/login';
|
||
}
|
||
|
||
// Проверка авторизации и получение данных пользователя
|
||
async function checkAuth() {
|
||
try {
|
||
const response = await fetch(`${API_BASE_URL}/user`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${token}`
|
||
}
|
||
});
|
||
|
||
if (response.ok) {
|
||
currentUser = await response.json();
|
||
} else {
|
||
localStorage.removeItem('accessToken');
|
||
window.location.href = '/login';
|
||
}
|
||
} catch (error) {
|
||
console.error('Error checking auth:', error);
|
||
}
|
||
|
||
updateNavigation();
|
||
activateTabFromUrl();
|
||
}
|
||
|
||
// Активация таба из URL
|
||
function activateTabFromUrl() {
|
||
document.querySelectorAll('.filter-tab').forEach(tab => {
|
||
tab.classList.remove('active');
|
||
if (tab.dataset.filter === currentFilter) {
|
||
tab.classList.add('active');
|
||
}
|
||
});
|
||
}
|
||
|
||
// Обновление навигации
|
||
function updateNavigation() {
|
||
const nav = document.getElementById('nav');
|
||
|
||
if (currentUser) {
|
||
nav.innerHTML = `
|
||
<a href="/">Главная</a>
|
||
<a href="/vacancies">Вакансии</a>
|
||
<a href="/resumes">Резюме</a>
|
||
<a href="/favorites" class="active">Избранное</a>
|
||
<a href="/applications">Отклики</a>
|
||
<a href="/profile" class="profile-link">
|
||
<i class="fas fa-user-circle"></i>
|
||
<span class="user-name">${escapeHtml(currentUser.full_name.split(' ')[0])}</span>
|
||
${currentUser.is_admin ? '<span class="admin-badge">Admin</span>' : ''}
|
||
</a>
|
||
`;
|
||
}
|
||
}
|
||
|
||
// Загрузка избранного
|
||
async function loadFavorites() {
|
||
try {
|
||
const response = await fetch(`${API_BASE_URL}/favorites`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${token}`
|
||
}
|
||
});
|
||
|
||
if (!response.ok) throw new Error('Ошибка загрузки');
|
||
|
||
favorites = await response.json();
|
||
|
||
// Обновляем статистику
|
||
updateStats();
|
||
|
||
// Отображаем избранное с текущим фильтром
|
||
renderFavorites();
|
||
|
||
} catch (error) {
|
||
console.error('Error loading favorites:', error);
|
||
document.getElementById('favoritesContainer').innerHTML =
|
||
'<div class="loading">Ошибка загрузки избранного</div>';
|
||
}
|
||
}
|
||
|
||
// Обновление статистики
|
||
function updateStats() {
|
||
const vacancyCount = favorites.filter(f => f.item_type === 'vacancy').length;
|
||
const resumeCount = favorites.filter(f => f.item_type === 'resume').length;
|
||
|
||
document.getElementById('totalCount').textContent = favorites.length;
|
||
document.getElementById('vacancyCount').textContent = vacancyCount;
|
||
document.getElementById('resumeCount').textContent = resumeCount;
|
||
}
|
||
|
||
// Отображение избранного с фильтром
|
||
function renderFavorites() {
|
||
const container = document.getElementById('favoritesContainer');
|
||
|
||
const filtered = currentFilter === 'all'
|
||
? favorites
|
||
: favorites.filter(f => f.item_type === currentFilter);
|
||
|
||
if (filtered.length === 0) {
|
||
let title = '';
|
||
let message = '';
|
||
let actionButtons = '';
|
||
|
||
if (currentFilter === 'vacancy') {
|
||
title = 'Нет избранных вакансий';
|
||
message = 'Добавляйте вакансии в избранное, чтобы не потерять интересные предложения';
|
||
actionButtons = `
|
||
<div class="empty-state-actions">
|
||
<a href="/vacancies" class="btn btn-primary">
|
||
<i class="fas fa-search"></i> Найти вакансии
|
||
</a>
|
||
<a href="/resumes" class="btn btn-outline">
|
||
<i class="fas fa-users"></i> Смотреть резюме
|
||
</a>
|
||
</div>
|
||
`;
|
||
} else if (currentFilter === 'resume') {
|
||
title = 'Нет избранных резюме';
|
||
message = 'Сохраняйте резюме кандидатов, чтобы вернуться к ним позже';
|
||
actionButtons = `
|
||
<div class="empty-state-actions">
|
||
<a href="/resumes" class="btn btn-primary">
|
||
<i class="fas fa-users"></i> Найти резюме
|
||
</a>
|
||
<a href="/vacancies" class="btn btn-outline">
|
||
<i class="fas fa-briefcase"></i> Смотреть вакансии
|
||
</a>
|
||
</div>
|
||
`;
|
||
} else {
|
||
title = 'Список избранного пуст';
|
||
message = 'Добавляйте вакансии и резюме в избранное, чтобы не потерять их';
|
||
actionButtons = `
|
||
<div class="empty-state-actions">
|
||
<a href="/vacancies" class="btn btn-primary">
|
||
<i class="fas fa-search"></i> Найти вакансии
|
||
</a>
|
||
<a href="/resumes" class="btn btn-primary">
|
||
<i class="fas fa-users"></i> Найти резюме
|
||
</a>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
container.innerHTML = `
|
||
<div class="empty-state">
|
||
<i class="far fa-heart"></i>
|
||
<h2>${title}</h2>
|
||
<p>${message}</p>
|
||
${actionButtons}
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = filtered.map(f => {
|
||
const data = f.item_data;
|
||
const date = new Date(f.created_at).toLocaleDateString('ru-RU', {
|
||
day: 'numeric',
|
||
month: 'long',
|
||
year: 'numeric'
|
||
});
|
||
|
||
if (f.item_type === 'vacancy') {
|
||
return `
|
||
<div class="favorite-card vacancy" onclick="window.location.href='${data.url}'">
|
||
<div class="favorite-header">
|
||
<span class="favorite-type vacancy">
|
||
<i class="fas fa-briefcase"></i> Вакансия
|
||
</span>
|
||
<button class="remove-btn" onclick="event.stopPropagation(); removeFromFavorites('vacancy', ${f.item_id})">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
<div class="favorite-title">${escapeHtml(data.title)}</div>
|
||
<div class="favorite-subtitle">
|
||
<i class="fas fa-building"></i> ${escapeHtml(data.company || 'Компания')}
|
||
</div>
|
||
<div class="favorite-salary">${escapeHtml(data.salary || 'Зарплата не указана')}</div>
|
||
<div class="favorite-tags">
|
||
${(data.tags || []).map(t => `<span class="tag">${escapeHtml(t)}</span>`).join('')}
|
||
</div>
|
||
<div class="favorite-footer">
|
||
<span class="favorite-date">
|
||
<i class="far fa-calendar"></i> ${date}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
} else {
|
||
return `
|
||
<div class="favorite-card resume" onclick="window.location.href='${data.url}'">
|
||
<div class="favorite-header">
|
||
<span class="favorite-type resume">
|
||
<i class="fas fa-user"></i> Резюме
|
||
</span>
|
||
<button class="remove-btn" onclick="event.stopPropagation(); removeFromFavorites('resume', ${f.item_id})">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
<div class="favorite-title">${escapeHtml(data.name)}</div>
|
||
<div class="favorite-subtitle">
|
||
<i class="fas fa-briefcase"></i> ${escapeHtml(data.title || 'Должность не указана')}
|
||
</div>
|
||
<div class="favorite-salary">${escapeHtml(data.salary || 'Зарплата не указана')}</div>
|
||
<div class="favorite-tags">
|
||
${(data.tags || []).map(t => `<span class="tag">${escapeHtml(t)}</span>`).join('')}
|
||
</div>
|
||
<div class="favorite-footer">
|
||
<span class="favorite-date">
|
||
<i class="far fa-calendar"></i> ${date}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
}).join('');
|
||
}
|
||
|
||
// Фильтрация избранного
|
||
function filterFavorites(filter) {
|
||
currentFilter = filter;
|
||
|
||
// Обновляем URL без перезагрузки страницы
|
||
const url = new URL(window.location);
|
||
url.searchParams.set('type', filter);
|
||
window.history.pushState({}, '', url);
|
||
|
||
// Обновляем активный таб
|
||
document.querySelectorAll('.filter-tab').forEach(tab => {
|
||
tab.classList.remove('active');
|
||
if (tab.dataset.filter === filter) {
|
||
tab.classList.add('active');
|
||
}
|
||
});
|
||
|
||
renderFavorites();
|
||
}
|
||
|
||
// Удаление из избранного
|
||
async function removeFromFavorites(itemType, itemId) {
|
||
if (!confirm('Удалить из избранного?')) return;
|
||
|
||
try {
|
||
const response = await fetch(`${API_BASE_URL}/favorites`, {
|
||
method: 'DELETE',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${token}`
|
||
},
|
||
body: JSON.stringify({
|
||
item_type: itemType,
|
||
item_id: itemId
|
||
})
|
||
});
|
||
|
||
if (response.ok) {
|
||
// Удаляем из локального массива
|
||
favorites = favorites.filter(f => !(f.item_type === itemType && f.item_id === itemId));
|
||
updateStats();
|
||
renderFavorites();
|
||
|
||
showNotification('Удалено из избранного', 'success');
|
||
} else {
|
||
const error = await response.json();
|
||
throw new Error(error.detail);
|
||
}
|
||
} catch (error) {
|
||
showNotification(error.message, 'error');
|
||
}
|
||
}
|
||
|
||
// Функция для показа уведомлений
|
||
function showNotification(message, type = 'success') {
|
||
const notification = document.getElementById('notification');
|
||
notification.className = type;
|
||
notification.innerHTML = message;
|
||
notification.style.display = 'block';
|
||
|
||
setTimeout(() => {
|
||
notification.style.display = 'none';
|
||
}, 3000);
|
||
}
|
||
|
||
// Экранирование HTML
|
||
function escapeHtml(unsafe) {
|
||
if (!unsafe) return '';
|
||
return unsafe.toString()
|
||
.replace(/&/g, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">")
|
||
.replace(/"/g, """)
|
||
.replace(/'/g, "'");
|
||
}
|
||
|
||
// Обработка клика по кнопке "Назад"
|
||
window.onpopstate = function() {
|
||
const params = new URLSearchParams(window.location.search);
|
||
const filter = params.get('type') || 'all';
|
||
if (['all', 'vacancy', 'resume'].includes(filter)) {
|
||
currentFilter = filter;
|
||
activateTabFromUrl();
|
||
renderFavorites();
|
||
}
|
||
};
|
||
|
||
// Загрузка при старте
|
||
checkAuth().then(() => {
|
||
loadFavorites();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |