Files
yarmarka/templates/profile.html
2026-03-16 18:57:22 +03:00

1885 lines
70 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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-grid {
display: grid;
grid-template-columns: 300px 1fr;
gap: 30px;
}
.profile-sidebar {
background: white;
border-radius: 40px;
padding: 30px;
text-align: center;
box-shadow: 0 20px 40px rgba(0,20,40,0.1);
height: fit-content;
}
.avatar {
width: 120px;
height: 120px;
background: #eef4fa;
border-radius: 60px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 20px;
font-size: 48px;
color: #3b82f6;
}
.profile-name {
font-size: 24px;
font-weight: 700;
color: #0b1c34;
margin-bottom: 5px;
}
.profile-role {
color: #3b82f6;
font-weight: 600;
margin-bottom: 20px;
}
.profile-stats {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin: 20px 0;
padding: 20px 0;
border-top: 1px solid #dee9f5;
border-bottom: 1px solid #dee9f5;
}
.stat-card {
background: #f9fcff;
border-radius: 20px;
padding: 15px;
text-align: center;
cursor: pointer;
transition: 0.2s;
text-decoration: none;
color: inherit;
display: block;
}
.stat-card:hover {
background: #eef4fa;
transform: translateY(-2px);
}
.stat-value {
font-size: 24px;
font-weight: 700;
color: #0b1c34;
}
.stat-label {
font-size: 12px;
color: #4f7092;
margin-top: 5px;
}
.contact-info {
text-align: left;
margin-top: 20px;
}
.contact-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 0;
color: #1f3f60;
}
.contact-item i {
color: #3b82f6;
width: 20px;
}
.profile-content {
background: white;
border-radius: 40px;
padding: 30px;
box-shadow: 0 20px 40px rgba(0,20,40,0.1);
}
.content-tabs {
display: flex;
gap: 10px;
margin-bottom: 30px;
border-bottom: 2px solid #dee9f5;
padding-bottom: 15px;
flex-wrap: wrap;
}
.content-tab {
padding: 10px 20px;
cursor: pointer;
font-weight: 600;
color: #4f7092;
border-radius: 30px;
transition: 0.2s;
}
.content-tab:hover {
background: #eef4fa;
}
.content-tab.active {
background: #eef4fa;
color: #0b1c34;
}
.content-pane {
display: none;
}
.content-pane.active {
display: block;
}
.btn {
padding: 12px 24px;
border-radius: 30px;
border: none;
font-weight: 600;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: 0.2s;
text-decoration: none;
}
.btn-primary {
background: #0b1c34;
color: white;
}
.btn-primary:hover {
background: #1b3f6b;
}
.btn-outline {
background: transparent;
border: 2px solid #3b82f6;
color: #0b1c34;
}
.btn-outline:hover {
background: #eef4fa;
}
.btn-success {
background: #10b981;
color: white;
}
.btn-success:hover {
background: #059669;
}
.btn-info {
background: #3b82f6;
color: white;
}
.btn-info:hover {
background: #2563eb;
}
.action-buttons {
display: flex;
gap: 15px;
margin: 20px 0;
flex-wrap: wrap;
}
/* Стили для вакансий */
.vacancies-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin-top: 20px;
}
.vacancy-card {
background: #f9fcff;
border: 1px solid #dee9f5;
border-radius: 30px;
padding: 25px;
transition: 0.2s;
position: relative;
}
.vacancy-card:hover {
background: white;
box-shadow: 0 10px 30px rgba(0,20,40,0.1);
}
.vacancy-card h3 {
color: #0b1c34;
margin-bottom: 10px;
font-size: 18px;
padding-right: 35px;
}
.vacancy-salary {
color: #3b82f6;
font-weight: 700;
font-size: 18px;
margin: 10px 0;
}
.vacancy-tags {
display: flex;
flex-wrap: wrap;
gap: 5px;
margin: 10px 0;
}
.tag {
background: #eef4fa;
padding: 4px 10px;
border-radius: 20px;
font-size: 12px;
color: #1f3f60;
}
.vacancy-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
font-size: 14px;
color: #4f7092;
}
/* Стили для меню с тремя точками */
.vacancy-menu {
position: absolute;
top: 15px;
right: 15px;
z-index: 100;
}
.menu-trigger {
background: white;
border: 1px solid #dee9f5;
width: 36px;
height: 36px;
border-radius: 18px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: 0.2s;
color: #4f7092;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.menu-trigger:hover {
background: #f0f7ff;
color: #0b1c34;
transform: scale(1.05);
}
.menu-dropdown {
position: absolute;
top: 40px;
right: 0;
background: white;
border-radius: 20px;
box-shadow: 0 10px 30px rgba(0,0,0,0.15);
min-width: 200px;
display: none;
overflow: hidden;
z-index: 200;
border: 1px solid #eef4fa;
}
.menu-dropdown.active {
display: block;
animation: fadeIn 0.2s;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.menu-item {
padding: 12px 16px;
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
transition: 0.2s;
color: #1f3f60;
font-size: 14px;
border-bottom: 1px solid #eef4fa;
}
.menu-item:last-child {
border-bottom: none;
}
.menu-item:hover {
background: #f0f7ff;
}
.menu-item i {
width: 18px;
font-size: 14px;
}
.menu-item.view i { color: #3b82f6; }
.menu-item.edit i { color: #f59e0b; }
.menu-item.activate i { color: #10b981; }
.menu-item.deactivate i { color: #f97316; }
.menu-item.delete i { color: #ef4444; }
.menu-divider {
height: 1px;
background: #eef4fa;
margin: 4px 0;
}
.menu-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: transparent;
z-index: 90;
}
.menu-overlay.active {
display: block;
}
/* Статус бейдж */
.status-badge {
display: inline-flex;
align-items: center;
padding: 4px 10px;
border-radius: 30px;
font-size: 11px;
font-weight: 600;
margin-left: 8px;
}
.status-badge.active {
background: #d1fae5;
color: #065f46;
}
.status-badge.inactive {
background: #fee2e2;
color: #b91c1c;
}
/* Фильтры для вакансий */
.filter-tabs {
display: flex;
gap: 10px;
background: #f0f7ff;
padding: 5px;
border-radius: 40px;
max-width: 400px;
margin-bottom: 15px;
}
.filter-tab {
flex: 1;
border: none;
background: transparent;
padding: 10px 16px;
border-radius: 40px;
font-weight: 600;
font-size: 14px;
color: #385073;
cursor: pointer;
transition: 0.2s;
white-space: nowrap;
}
.filter-tab.active {
background: white;
color: #0b1c34;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.filter-tab:hover:not(.active) {
background: rgba(255,255,255,0.5);
}
.vacancy-stats {
display: flex;
gap: 15px;
margin-top: 15px;
font-size: 14px;
color: #4f7092;
}
/* Стили для резюме */
.resume-section {
background: #f9fcff;
border-radius: 30px;
padding: 30px;
}
.resume-field {
margin-bottom: 20px;
}
.resume-field label {
display: block;
font-weight: 600;
color: #1f3f60;
margin-bottom: 5px;
}
.resume-field input,
.resume-field textarea {
width: 100%;
padding: 12px 16px;
border: 2px solid #dee9f5;
border-radius: 20px;
font-size: 16px;
}
.exp-item, .edu-item {
background: white;
border-radius: 20px;
padding: 20px;
margin-bottom: 15px;
border: 1px solid #dee9f5;
}
.item-actions {
display: flex;
justify-content: flex-end;
margin-top: 15px;
}
.resume-preview-link {
display: inline-flex;
align-items: center;
gap: 8px;
background: #eef4fa;
padding: 10px 20px;
border-radius: 30px;
color: #0b1c34;
text-decoration: none;
margin-bottom: 20px;
font-weight: 600;
}
.resume-preview-link:hover {
background: #dbeafe;
}
/* Стили для откликов */
.applications-summary {
background: #f9fcff;
border-radius: 20px;
padding: 20px;
margin-bottom: 20px;
}
.applications-stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin: 15px 0;
}
.stat-item {
text-align: center;
padding: 10px;
background: white;
border-radius: 15px;
}
.stat-item .number {
font-size: 20px;
font-weight: 700;
color: #0b1c34;
}
.stat-item .label {
font-size: 12px;
color: #4f7092;
}
.stat-item.pending .number { color: #f59e0b; }
.stat-item.viewed .number { color: #3b82f6; }
.stat-item.accepted .number { color: #10b981; }
.stat-item.rejected .number { color: #ef4444; }
/* Уведомления */
.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;
}
.notification.info {
background: #3b82f6;
color: white;
}
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.loading {
text-align: center;
padding: 40px;
color: #4f7092;
grid-column: 1/-1;
}
.error-message {
background: #fee2e2;
color: #b91c1c;
padding: 20px;
border-radius: 30px;
text-align: center;
margin: 20px 0;
}
.admin-badge {
background: #f59e0b;
color: white;
padding: 2px 8px;
border-radius: 20px;
font-size: 12px;
margin-left: 5px;
}
@media (max-width: 768px) {
.profile-grid {
grid-template-columns: 1fr;
}
.applications-stats {
grid-template-columns: repeat(2, 1fr);
}
.filter-tabs {
max-width: 100%;
}
}
</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 class="loading" style="color: white; padding: 0;">Загрузка...</div>
</div>
</div>
<!-- Профиль -->
<div class="profile-grid">
<!-- Боковая панель -->
<div class="profile-sidebar">
<div class="avatar">
<i class="fas fa-user-circle"></i>
</div>
<div class="profile-name" id="profileName">Загрузка...</div>
<div class="profile-role" id="profileRole"></div>
<div class="profile-stats">
<a href="/favorites" class="stat-card">
<div class="stat-value" id="totalFavorites">0</div>
<div class="stat-label">в избранном</div>
</a>
<a href="/applications" class="stat-card">
<div class="stat-value" id="totalApplications">0</div>
<div class="stat-label">откликов</div>
</a>
</div>
<div class="contact-info">
<div class="contact-item">
<i class="fas fa-envelope"></i>
<span id="profileEmail"></span>
</div>
<div class="contact-item">
<i class="fas fa-phone"></i>
<span id="profilePhone"></span>
</div>
<div class="contact-item">
<i class="fab fa-telegram"></i>
<span id="profileTelegram"></span>
</div>
</div>
</div>
<!-- Основной контент -->
<div class="profile-content">
<div class="content-tabs" id="contentTabs">
<!-- Табы будут заполнены динамически -->
</div>
<!-- Главная вкладка -->
<div class="content-pane active" id="mainPane">
<h2 style="margin-bottom: 20px;">Добро пожаловать!</h2>
<p style="color: #4f7092; margin-bottom: 30px;">Здесь будет ваша активность и статистика</p>
<div class="action-buttons">
<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>
<div id="applicationsPreview" style="margin-top: 30px;"></div>
</div>
<!-- Вкладка с вакансиями -->
<div class="content-pane" id="vacanciesPane">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 15px;">
<h2>Мои вакансии</h2>
<button class="btn btn-primary" onclick="openVacancyModal()">
<i class="fas fa-plus"></i> Создать вакансию
</button>
</div>
<div class="filter-tabs">
<button class="filter-tab active" data-status="all" onclick="filterVacancies('all')">
Все вакансии
</button>
<button class="filter-tab" data-status="active" onclick="filterVacancies('active')">
Активные
</button>
<button class="filter-tab" data-status="inactive" onclick="filterVacancies('inactive')">
Неактивные
</button>
</div>
<div class="vacancy-stats">
<span><i class="fas fa-check-circle" style="color: #10b981;"></i> Активных: <span id="activeCount">0</span></span>
<span><i class="fas fa-pause-circle" style="color: #f97316;"></i> Неактивных: <span id="inactiveCount">0</span></span>
<span><i class="fas fa-layer-group" style="color: #3b82f6;"></i> Всего: <span id="totalCount">0</span></span>
</div>
<div id="vacanciesList" class="vacancies-grid">
<div class="loading">Загрузка вакансий...</div>
</div>
</div>
<!-- Вкладка с резюме -->
<div class="content-pane" id="resumePane">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 15px;">
<h2>Мое резюме</h2>
<div style="display: flex; gap: 10px;">
<a href="#" id="previewResumeLink" class="btn btn-info" target="_blank" style="display: none;">
<i class="fas fa-eye"></i> Предпросмотр
</a>
<button class="btn btn-success" onclick="saveResume()">
<i class="fas fa-save"></i> Сохранить
</button>
</div>
</div>
<div class="resume-section">
<div id="resumePreviewLink" style="display: none; margin-bottom: 20px;"></div>
<div class="resume-field">
<label>Желаемая должность</label>
<input type="text" id="desiredPosition" placeholder="Например: Frontend-разработчик">
</div>
<div class="resume-field">
<label>О себе</label>
<textarea id="aboutMe" rows="4" placeholder="Расскажите о своем опыте"></textarea>
</div>
<div class="resume-field">
<label>Желаемая зарплата</label>
<input type="text" id="desiredSalary" placeholder="от 200 000 ₽">
</div>
<div class="resume-field">
<label>Навыки (теги через запятую)</label>
<input type="text" id="resumeTags" placeholder="Python, JavaScript, React, SQL">
</div>
<h3 style="margin: 30px 0 20px;">Опыт работы</h3>
<div id="experienceContainer"></div>
<button class="btn btn-outline" onclick="addExperience()" style="margin-bottom: 30px;">
<i class="fas fa-plus"></i> Добавить опыт
</button>
<h3 style="margin: 20px 0;">Образование</h3>
<div id="educationContainer"></div>
<button class="btn btn-outline" onclick="addEducation()">
<i class="fas fa-plus"></i> Добавить образование
</button>
</div>
</div>
<!-- Вкладка с компанией -->
<div class="content-pane" id="companyPane">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 15px;">
<h2>Информация о компании</h2>
<button class="btn btn-success" onclick="saveCompany()">
<i class="fas fa-save"></i> Сохранить
</button>
</div>
<div class="resume-section">
<div class="resume-field">
<label>Название компании <span style="color: #b91c1c;">*</span></label>
<input type="text" id="companyName" placeholder="ООО «Технологии»" required>
</div>
<div class="resume-field">
<label>Логотип (URL)</label>
<input type="text" id="companyLogo" placeholder="https://example.com/logo.png">
</div>
<div class="resume-field">
<label>Сайт компании</label>
<input type="url" id="companyWebsite" placeholder="https://example.com">
</div>
<div class="resume-field">
<label>Описание компании</label>
<textarea id="companyDescription" rows="5" placeholder="Расскажите о компании, миссии, ценностях..."></textarea>
</div>
<div class="resume-field">
<label>Адрес</label>
<input type="text" id="companyAddress" placeholder="г. Москва, ул. Примерная, д. 1">
</div>
<div class="resume-field">
<label>Контактный телефон</label>
<input type="tel" id="companyPhone" placeholder="+7 999 123-45-67">
</div>
<div class="resume-field">
<label>Контактный email</label>
<input type="email" id="companyEmail" placeholder="info@company.ru">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Модальное окно создания/редактирования вакансии -->
<div id="vacancyModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); align-items: center; justify-content: center; z-index: 1000;">
<div style="background: white; max-width: 500px; width: 90%; border-radius: 40px; padding: 40px; max-height: 90vh; overflow-y: auto;">
<h3 style="margin-bottom: 20px;" id="vacancyModalTitle">Новая вакансия</h3>
<input type="hidden" id="editVacancyId">
<div class="resume-field">
<label>Название <span style="color: #b91c1c;">*</span></label>
<input type="text" id="vacancyTitle" placeholder="Менеджер по продажам">
</div>
<div class="resume-field">
<label>Зарплата</label>
<input type="text" id="vacancySalary" placeholder="от 100 000 ₽">
</div>
<div class="resume-field">
<label>Теги (через запятую)</label>
<input type="text" id="vacancyTags" placeholder="Python, JavaScript, React">
</div>
<div class="resume-field">
<label>Описание</label>
<textarea id="vacancyDescription" rows="4" placeholder="Обязанности, требования..."></textarea>
</div>
<div class="resume-field">
<label>Контакт</label>
<input type="text" id="vacancyContact" placeholder="@telegram или email">
</div>
<div class="resume-field" id="activeStatusField" style="display: none;">
<label style="display: flex; align-items: center; gap: 10px;">
<input type="checkbox" id="vacancyActive" checked>
<span>Вакансия активна</span>
</label>
</div>
<div style="display: flex; gap: 10px; margin-top: 30px;">
<button class="btn btn-primary" onclick="saveVacancy()" id="saveVacancyBtn" style="flex: 1;">Создать</button>
<button class="btn btn-outline" onclick="closeVacancyModal()" style="flex: 1;">Отмена</button>
</div>
</div>
</div>
<!-- Уведомления -->
<div class="notification" id="notification"></div>
<script>
// ========== КОНФИГУРАЦИЯ ==========
const API_BASE_URL = window.location.protocol + '//' + window.location.host + '/api';
let token = localStorage.getItem('accessToken');
let currentUser = null;
let resumeId = null;
let currentEditVacancyId = null;
let currentOpenMenu = null;
let allVacancies = [];
let currentFilter = 'all';
// ========== ПРОВЕРКА АВТОРИЗАЦИИ ==========
if (!token) {
window.location.href = '/login';
}
// ========== СОЗДАНИЕ ОВЕРЛЕЯ ==========
function createMenuOverlay() {
if (!document.getElementById('menuOverlay')) {
const overlay = document.createElement('div');
overlay.id = 'menuOverlay';
overlay.className = 'menu-overlay';
overlay.onclick = closeAllMenus;
document.body.appendChild(overlay);
}
}
// ========== ФУНКЦИИ МЕНЮ ==========
function toggleMenu(vacancyId, event) {
if (!vacancyId) return;
if (event) {
event.stopPropagation();
event.preventDefault();
}
const menu = document.getElementById(`menu-${vacancyId}`);
if (!menu) return;
const overlay = document.getElementById('menuOverlay');
if (currentOpenMenu && currentOpenMenu !== menu) {
currentOpenMenu.classList.remove('active');
}
menu.classList.toggle('active');
if (menu.classList.contains('active')) {
currentOpenMenu = menu;
if (overlay) overlay.classList.add('active');
} else {
currentOpenMenu = null;
if (overlay) overlay.classList.remove('active');
}
}
function closeAllMenus() {
document.querySelectorAll('.menu-dropdown').forEach(menu => {
menu.classList.remove('active');
});
const overlay = document.getElementById('menuOverlay');
if (overlay) overlay.classList.remove('active');
currentOpenMenu = null;
}
function viewVacancy(vacancyId, event) {
if (event) {
event.stopPropagation();
event.preventDefault();
}
if (!vacancyId) return;
window.open(`/vacancy/${vacancyId}`, '_blank');
closeAllMenus();
}
async function toggleVacancyStatus(vacancyId, activate, event) {
if (event) {
event.stopPropagation();
event.preventDefault();
}
if (!vacancyId) return;
try {
const response = await fetch(`${API_BASE_URL}/vacancies/${vacancyId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ is_active: activate })
});
if (!response.ok) throw new Error('Ошибка обновления статуса');
closeAllMenus();
await loadAllVacancies();
showNotification(`Вакансия ${activate ? 'активирована' : 'деактивирована'}`, 'success');
} catch (error) {
showNotification(error.message, 'error');
}
}
async function editVacancy(vacancyId, event) {
if (event) {
event.stopPropagation();
event.preventDefault();
}
if (!vacancyId) return;
closeAllMenus();
try {
const response = await fetch(`${API_BASE_URL}/vacancies/${vacancyId}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (!response.ok) throw new Error('Ошибка загрузки');
const vacancy = await response.json();
currentEditVacancyId = vacancyId;
document.getElementById('editVacancyId').value = vacancyId;
document.getElementById('vacancyModalTitle').textContent = 'Редактирование вакансии';
document.getElementById('saveVacancyBtn').textContent = 'Сохранить';
document.getElementById('activeStatusField').style.display = 'block';
document.getElementById('vacancyTitle').value = vacancy.title || '';
document.getElementById('vacancySalary').value = vacancy.salary || '';
document.getElementById('vacancyDescription').value = vacancy.description || '';
document.getElementById('vacancyContact').value = vacancy.contact || '';
document.getElementById('vacancyActive').checked = vacancy.is_active !== false;
if (vacancy.tags && vacancy.tags.length > 0) {
document.getElementById('vacancyTags').value = vacancy.tags.map(t => t.name).join(', ');
} else {
document.getElementById('vacancyTags').value = '';
}
document.getElementById('vacancyModal').style.display = 'flex';
} catch (error) {
showNotification(error.message, 'error');
}
}
async function deleteVacancy(vacancyId, event) {
if (event) {
event.stopPropagation();
event.preventDefault();
}
if (!vacancyId) return;
closeAllMenus();
if (!confirm('Вы уверены, что хотите удалить вакансию? Это действие нельзя отменить.')) return;
try {
const response = await fetch(`${API_BASE_URL}/vacancies/${vacancyId}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
await loadAllVacancies();
showNotification('Вакансия удалена', 'success');
} else {
throw new Error('Ошибка при удалении');
}
} catch (error) {
showNotification(error.message, 'error');
}
}
// ========== ИНИЦИАЛИЗАЦИЯ МЕНЮ ==========
function initializeMenuHandlers() {
createMenuOverlay();
document.addEventListener('click', function(event) {
if (event.target.closest('.vacancy-menu')) return;
closeAllMenus();
});
}
// ========== УВЕДОМЛЕНИЯ ==========
function showNotification(message, type = 'info') {
let notification = document.getElementById('notification');
if (!notification) {
notification = document.createElement('div');
notification.id = 'notification';
document.body.appendChild(notification);
}
let backgroundColor = '#3b82f6';
if (type === 'success') backgroundColor = '#10b981';
if (type === 'error') backgroundColor = '#ef4444';
notification.style.backgroundColor = backgroundColor;
notification.style.color = 'white';
notification.textContent = message;
notification.style.display = 'block';
setTimeout(() => {
notification.style.display = 'none';
}, 3000);
}
// ========== ЗАГРУЗКА ПРОФИЛЯ ==========
async function loadProfile() {
try {
const response = await fetch(`${API_BASE_URL}/user`, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (!response.ok) {
if (response.status === 401) {
localStorage.removeItem('accessToken');
window.location.href = '/login';
return;
}
throw new Error('Ошибка загрузки');
}
currentUser = await response.json();
document.getElementById('profileName').textContent = currentUser.full_name || 'Не указано';
document.getElementById('profileEmail').textContent = currentUser.email || '—';
document.getElementById('profilePhone').textContent = currentUser.phone || '—';
document.getElementById('profileTelegram').textContent = currentUser.telegram || '—';
let roleText = '';
if (currentUser.role === 'employee') roleText = 'Соискатель';
else if (currentUser.role === 'employer') roleText = 'Работодатель';
else if (currentUser.role === 'admin') roleText = 'Администратор';
document.getElementById('profileRole').textContent = roleText;
await loadUserStats();
setupTabs();
updateNavigation();
} catch (error) {
console.error('Error loading profile:', error);
}
}
// ========== ЗАГРУЗКА СТАТИСТИКИ ==========
async function loadUserStats() {
try {
const favResponse = await fetch(`${API_BASE_URL}/favorites`, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (favResponse.ok) {
const favorites = await favResponse.json();
document.getElementById('totalFavorites').textContent = favorites.length || 0;
}
const appsResponse = await fetch(`${API_BASE_URL}/applications/stats`, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (appsResponse.ok) {
const appStats = await appsResponse.json();
document.getElementById('totalApplications').textContent = appStats.total || 0;
if (currentUser && currentUser.role === 'employee') {
showApplicationsPreview(appStats);
}
}
} catch (error) {
console.error('Error loading stats:', error);
}
}
function showApplicationsPreview(stats) {
const container = document.getElementById('applicationsPreview');
if (!container) return;
container.innerHTML = `
<h3 style="margin-bottom: 15px;">Мои отклики</h3>
<div class="applications-stats">
<div class="stat-item pending">
<div class="number">${stats.pending || 0}</div>
<div class="label">Ожидают</div>
</div>
<div class="stat-item viewed">
<div class="number">${stats.viewed || 0}</div>
<div class="label">Просмотрены</div>
</div>
<div class="stat-item accepted">
<div class="number">${stats.accepted || 0}</div>
<div class="label">Приняты</div>
</div>
<div class="stat-item rejected">
<div class="number">${stats.rejected || 0}</div>
<div class="label">Отклонены</div>
</div>
</div>
<a href="/applications" class="btn btn-outline" style="width: 100%; justify-content: center;">
<i class="fas fa-arrow-right"></i> Перейти к откликам
</a>
`;
}
// ========== НАСТРОЙКА ТАБОВ ==========
function setupTabs() {
const tabsContainer = document.getElementById('contentTabs');
if (!tabsContainer) return;
if (currentUser.role === 'employer') {
tabsContainer.innerHTML = `
<div class="content-tab active" onclick="switchTab('main')">Главная</div>
<div class="content-tab" onclick="switchTab('vacancies')">Вакансии</div>
<div class="content-tab" onclick="switchTab('company')">Компания</div>
`;
loadAllVacancies();
loadCompany();
} else if (currentUser.role === 'employee') {
tabsContainer.innerHTML = `
<div class="content-tab active" onclick="switchTab('main')">Главная</div>
<div class="content-tab" onclick="switchTab('resume')">Резюме</div>
`;
loadResume();
}
}
function switchTab(tab) {
document.querySelectorAll('.content-tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.content-pane').forEach(p => p.classList.remove('active'));
if (tab === 'main') {
document.querySelectorAll('.content-tab')[0]?.classList.add('active');
document.getElementById('mainPane')?.classList.add('active');
} else if (tab === 'vacancies') {
document.querySelectorAll('.content-tab')[1]?.classList.add('active');
document.getElementById('vacanciesPane')?.classList.add('active');
} else if (tab === 'resume') {
if (currentUser.role === 'employee') {
document.querySelectorAll('.content-tab')[1]?.classList.add('active');
} else {
document.querySelectorAll('.content-tab')[2]?.classList.add('active');
}
document.getElementById('resumePane')?.classList.add('active');
} else if (tab === 'company') {
document.querySelectorAll('.content-tab')[2]?.classList.add('active');
document.getElementById('companyPane')?.classList.add('active');
}
}
// ========== ОБНОВЛЕНИЕ НАВИГАЦИИ ==========
function updateNavigation() {
const nav = document.getElementById('nav');
if (!nav) return;
if (!currentUser) {
nav.innerHTML = `<a href="/login">Войти</a><a href="/register">Регистрация</a>`;
return;
}
let adminBadge = currentUser.is_admin ? '<span class="admin-badge">Admin</span>' : '';
let firstName = currentUser.full_name ? currentUser.full_name.split(' ')[0] : 'Профиль';
nav.innerHTML = `
<a href="/">Главная</a>
<a href="/vacancies">Вакансии</a>
<a href="/resumes">Резюме</a>
<a href="/favorites">Избранное</a>
<a href="/applications">Отклики</a>
<a href="/profile" class="profile-link">
<i class="fas fa-user-circle"></i>
<span class="user-name">${escapeHtml(firstName)}</span>
${adminBadge}
</a>
<a href="#" onclick="logout()" style="color: #ff6b6b;">Выйти</a>
`;
}
// ========== РАБОТА С ВАКАНСИЯМИ ==========
async function loadAllVacancies() {
try {
console.log('📥 Загрузка всех вакансий пользователя...');
const response = await fetch(`${API_BASE_URL}/vacancies/my-all`, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (!response.ok) {
throw new Error('Ошибка загрузки');
}
allVacancies = await response.json();
// Нормализуем булевы значения
allVacancies = allVacancies.map(v => ({
...v,
is_active: v.is_active === true || v.is_active === 1 || v.is_active === '1' || v.is_active === 'true'
}));
// Статистика загрузки
const activeCount = allVacancies.filter(v => v.is_active === true).length;
const inactiveCount = allVacancies.filter(v => v.is_active === false).length;
console.log(`✅ Загружено вакансий: ${allVacancies.length}`);
console.log(` 📊 Активные: ${activeCount}`);
console.log(` 📊 Неактивные: ${inactiveCount}`);
// Детальный лог всех вакансий
console.group('📋 Список всех вакансий:');
allVacancies.forEach((v, i) => {
console.log(`${i + 1}. [${v.is_active ? 'АКТИВНАЯ' : 'НЕАКТИВНАЯ'}] ${v.title} (ID: ${v.id})`);
});
console.groupEnd();
updateVacancyCounts();
filterVacancies(currentFilter);
} catch (error) {
console.error('❌ Ошибка загрузки вакансий:', error);
document.getElementById('vacanciesList').innerHTML = '<div class="loading">Ошибка загрузки вакансий</div>';
}
}
function updateVacancyCounts() {
const activeCount = allVacancies.filter(v => v.is_active).length;
const inactiveCount = allVacancies.filter(v => !v.is_active).length;
document.getElementById('activeCount').textContent = activeCount;
document.getElementById('inactiveCount').textContent = inactiveCount;
document.getElementById('totalCount').textContent = allVacancies.length;
}
function filterVacancies(filter) {
console.log(`🔍 Фильтрация: ${filter}`);
console.log('Всего вакансий:', allVacancies.length);
currentFilter = filter;
// Обновляем активный таб
document.querySelectorAll('.filter-tab').forEach(tab => {
tab.classList.remove('active');
if (tab.dataset.status === filter) {
tab.classList.add('active');
}
});
// Фильтруем вакансии
let filteredVacancies = [];
if (filter === 'all') {
filteredVacancies = allVacancies;
console.log('Показаны все вакансии:', filteredVacancies.length);
} else if (filter === 'active') {
filteredVacancies = allVacancies.filter(v => v.is_active === true);
console.log('Активные вакансии:', filteredVacancies.length);
} else if (filter === 'inactive') {
filteredVacancies = allVacancies.filter(v => v.is_active === false);
console.log('Неактивные вакансии:', filteredVacancies.length);
}
// Детальный лог для отладки
if (filter === 'active') {
console.log('Проверка активных:');
allVacancies.forEach((v, i) => {
console.log(` ${i}: ${v.title} - is_active: ${v.is_active} (${typeof v.is_active})`);
});
}
if (filter === 'inactive') {
console.log('Проверка неактивных:');
allVacancies.forEach((v, i) => {
console.log(` ${i}: ${v.title} - is_active: ${v.is_active} (${typeof v.is_active})`);
});
}
displayVacancies(filteredVacancies);
}
function displayVacancies(vacancies) {
const container = document.getElementById('vacanciesList');
if (!container) return;
console.log(`📋 Отображение ${vacancies.length} вакансий (фильтр: ${currentFilter})`);
if (vacancies.length === 0) {
let message = '📭 Нет вакансий';
if (currentFilter === 'active') message = '📭 Нет активных вакансий';
if (currentFilter === 'inactive') message = '📭 Нет неактивных вакансий';
container.innerHTML = `<div class="loading">${message}</div>`;
return;
}
container.innerHTML = vacancies.map(v => {
// Убедимся, что is_active - булево значение
const isActive = v.is_active === true;
return `
<div class="vacancy-card" id="vacancy-${v.id}">
<div class="vacancy-menu">
<div class="menu-trigger" onclick="toggleMenu(${v.id}, event)">
<i class="fas fa-ellipsis-v"></i>
</div>
<div class="menu-dropdown" id="menu-${v.id}">
<div class="menu-item view" onclick="viewVacancy(${v.id}, event)">
<i class="fas fa-eye"></i>
<span>Просмотреть</span>
</div>
<div class="menu-item edit" onclick="editVacancy(${v.id}, event)">
<i class="fas fa-edit"></i>
<span>Редактировать</span>
</div>
${isActive ?
`<div class="menu-item deactivate" onclick="toggleVacancyStatus(${v.id}, false, event)">
<i class="fas fa-pause-circle"></i>
<span>Деактивировать</span>
</div>` :
`<div class="menu-item activate" onclick="toggleVacancyStatus(${v.id}, true, event)">
<i class="fas fa-play-circle"></i>
<span>Активировать</span>
</div>`
}
<div class="menu-divider"></div>
<div class="menu-item delete" onclick="deleteVacancy(${v.id}, event)">
<i class="fas fa-trash-alt"></i>
<span>Удалить</span>
</div>
</div>
</div>
<h3>${escapeHtml(v.title)}</h3>
<div style="color: #3b82f6; font-size: 14px; margin-bottom: 5px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
<i class="fas fa-building"></i> ${escapeHtml(v.company_name || 'Компания')}
<span class="status-badge ${isActive ? 'active' : 'inactive'}">
${isActive ? '✅ Активна' : '⭕ Неактивна'}
</span>
</div>
<div class="vacancy-salary">${escapeHtml(v.salary || 'з/п не указана')}</div>
<div class="vacancy-tags">
${(v.tags || []).map(t => `<span class="tag">${escapeHtml(t.name)}</span>`).join('')}
</div>
<p style="color: #4f7092; margin: 10px 0;">${escapeHtml((v.description || '').substring(0, 100))}...</p>
<div class="vacancy-footer">
<span><i class="fab fa-telegram"></i> ${escapeHtml(v.contact || '—')}</span>
<span><i class="fas fa-eye"></i> ${v.views || 0}</span>
<span><i class="fas fa-calendar"></i> ${new Date(v.created_at).toLocaleDateString()}</span>
</div>
</div>
`}).join('');
}
// ========== РАБОТА С РЕЗЮМЕ ==========
function addExperience(position = '', company = '', period = '', description = '') {
const container = document.getElementById('experienceContainer');
if (!container) return;
const item = document.createElement('div');
item.className = 'exp-item';
item.innerHTML = `
<div class="resume-field">
<label>Должность</label>
<input type="text" class="exp-position" value="${escapeHtml(position)}" placeholder="Например: Senior Frontend Developer">
</div>
<div class="resume-field">
<label>Компания</label>
<input type="text" class="exp-company" value="${escapeHtml(company)}" placeholder="Название компании">
</div>
<div class="resume-field">
<label>Период работы</label>
<input type="text" class="exp-period" value="${escapeHtml(period)}" placeholder="2022-2024">
</div>
<div class="resume-field">
<label>Обязанности и достижения</label>
<textarea class="exp-description" rows="3" placeholder="Опишите ваши обязанности, проекты и достижения...">${escapeHtml(description)}</textarea>
</div>
<div class="item-actions">
<button class="btn btn-outline" onclick="this.closest('.exp-item').remove()">
<i class="fas fa-trash"></i> Удалить
</button>
</div>
`;
container.appendChild(item);
}
function addEducation(institution = '', specialty = '', year = '') {
const container = document.getElementById('educationContainer');
if (!container) return;
const item = document.createElement('div');
item.className = 'edu-item';
item.innerHTML = `
<div class="resume-field">
<label>Учебное заведение</label>
<input type="text" class="edu-institution" value="${escapeHtml(institution)}">
</div>
<div class="resume-field">
<label>Специальность</label>
<input type="text" class="edu-specialty" value="${escapeHtml(specialty)}">
</div>
<div class="resume-field">
<label>Год окончания</label>
<input type="text" class="edu-year" value="${escapeHtml(year)}">
</div>
<div class="item-actions">
<button class="btn btn-outline" onclick="this.closest('.edu-item').remove()">
<i class="fas fa-trash"></i> Удалить
</button>
</div>
`;
container.appendChild(item);
}
async function loadResume() {
try {
const response = await fetch(`${API_BASE_URL}/resume`, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.status === 404) {
addExperience();
addEducation();
return;
}
if (!response.ok) throw new Error('Ошибка загрузки');
const resume = await response.json();
resumeId = resume.id;
document.getElementById('desiredPosition').value = resume.desired_position || '';
document.getElementById('aboutMe').value = resume.about_me || '';
document.getElementById('desiredSalary').value = resume.desired_salary || '';
if (resume.tags && resume.tags.length > 0) {
document.getElementById('resumeTags').value = resume.tags.map(t => t.name).join(', ');
}
const previewLink = document.getElementById('previewResumeLink');
if (previewLink) {
previewLink.href = `/resume/${resumeId}`;
previewLink.style.display = 'inline-flex';
}
const resumePreviewLink = document.getElementById('resumePreviewLink');
if (resumePreviewLink) {
resumePreviewLink.innerHTML = `
<a href="/resume/${resumeId}" target="_blank" class="resume-preview-link">
<i class="fas fa-external-link-alt"></i> Посмотреть, как видят ваше резюме работодатели
</a>
`;
resumePreviewLink.style.display = 'block';
}
document.getElementById('experienceContainer').innerHTML = '';
resume.work_experience.forEach(exp => {
addExperience(
exp.position,
exp.company,
exp.period,
exp.description || '' // Добавляем описание
);
});
document.getElementById('educationContainer').innerHTML = '';
resume.education.forEach(edu => {
addEducation(edu.institution, edu.specialty, edu.graduation_year);
});
} catch (error) {
console.error('Error loading resume:', error);
}
}
async function saveResume() {
const workExperience = [];
document.querySelectorAll('#experienceContainer .exp-item').forEach(item => {
workExperience.push({
position: item.querySelector('.exp-position')?.value || '',
company: item.querySelector('.exp-company')?.value || '',
period: item.querySelector('.exp-period')?.value || null,
description: item.querySelector('.exp-description')?.value || null // Новое поле
});
});
const education = [];
document.querySelectorAll('#educationContainer .edu-item').forEach(item => {
education.push({
institution: item.querySelector('.edu-institution')?.value || '',
specialty: item.querySelector('.edu-specialty')?.value || null,
graduation_year: item.querySelector('.edu-year')?.value || null
});
});
const tagsInput = document.getElementById('resumeTags')?.value || '';
const tags = tagsInput
.split(',')
.map(t => t.trim())
.filter(t => t.length > 0);
const resumeData = {
desired_position: document.getElementById('desiredPosition').value || null,
about_me: document.getElementById('aboutMe').value || null,
desired_salary: document.getElementById('desiredSalary').value || null,
work_experience: workExperience,
education: education,
tags: tags
};
try {
const response = await fetch(`${API_BASE_URL}/resume`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(resumeData)
});
if (!response.ok) throw new Error('Ошибка сохранения');
const saved = await response.json();
resumeId = saved.id;
const previewLink = document.getElementById('previewResumeLink');
if (previewLink) {
previewLink.href = `/resume/${resumeId}`;
previewLink.style.display = 'inline-flex';
}
const resumePreviewLink = document.getElementById('resumePreviewLink');
if (resumePreviewLink) {
resumePreviewLink.innerHTML = `
<a href="/resume/${resumeId}" target="_blank" class="resume-preview-link">
<i class="fas fa-external-link-alt"></i> Посмотреть, как видят ваше резюме работодатели
</a>
`;
resumePreviewLink.style.display = 'block';
}
showNotification('Резюме сохранено!', 'success');
} catch (error) {
showNotification(error.message, 'error');
}
}
// ========== РАБОТА С КОМПАНИЕЙ ==========
async function loadCompany() {
try {
const response = await fetch(`${API_BASE_URL}/company`, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.status === 404) return;
if (!response.ok) throw new Error('Ошибка загрузки');
const company = await response.json();
document.getElementById('companyName').value = company.name || '';
document.getElementById('companyLogo').value = company.logo || '';
document.getElementById('companyWebsite').value = company.website || '';
document.getElementById('companyDescription').value = company.description || '';
document.getElementById('companyAddress').value = company.address || '';
document.getElementById('companyPhone').value = company.phone || '';
document.getElementById('companyEmail').value = company.email || '';
} catch (error) {
console.error('Error loading company:', error);
}
}
async function saveCompany() {
const companyData = {
name: document.getElementById('companyName').value,
logo: document.getElementById('companyLogo').value || null,
website: document.getElementById('companyWebsite').value || null,
description: document.getElementById('companyDescription').value || null,
address: document.getElementById('companyAddress').value || null,
phone: document.getElementById('companyPhone').value || null,
email: document.getElementById('companyEmail').value || null
};
if (!companyData.name) {
showNotification('Введите название компании', 'error');
return;
}
try {
const response = await fetch(`${API_BASE_URL}/company`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(companyData)
});
if (!response.ok) throw new Error('Ошибка сохранения');
showNotification('Информация о компании сохранена!', 'success');
} catch (error) {
showNotification(error.message, 'error');
}
}
// ========== МОДАЛЬНОЕ ОКНО ВАКАНСИИ ==========
function openVacancyModal() {
currentEditVacancyId = null;
document.getElementById('vacancyModalTitle').textContent = 'Новая вакансия';
document.getElementById('saveVacancyBtn').textContent = 'Создать';
document.getElementById('activeStatusField').style.display = 'none';
document.getElementById('editVacancyId').value = '';
document.getElementById('vacancyTitle').value = '';
document.getElementById('vacancySalary').value = '';
document.getElementById('vacancyTags').value = '';
document.getElementById('vacancyDescription').value = '';
document.getElementById('vacancyContact').value = '';
document.getElementById('vacancyActive').checked = true;
document.getElementById('vacancyModal').style.display = 'flex';
}
function closeVacancyModal() {
document.getElementById('vacancyModal').style.display = 'none';
currentEditVacancyId = null;
}
async function saveVacancy() {
const tagsInput = document.getElementById('vacancyTags')?.value || '';
const tags = tagsInput
.split(',')
.map(t => t.trim())
.filter(t => t.length > 0);
const vacancyData = {
title: document.getElementById('vacancyTitle').value,
salary: document.getElementById('vacancySalary').value || null,
description: document.getElementById('vacancyDescription').value || null,
contact: document.getElementById('vacancyContact').value || null,
tags: tags
};
if (!vacancyData.title) {
showNotification('Введите название вакансии', 'error');
return;
}
if (currentEditVacancyId) {
vacancyData.is_active = document.getElementById('vacancyActive').checked;
}
try {
let response;
let url = `${API_BASE_URL}/vacancies`;
if (currentEditVacancyId) {
response = await fetch(`${url}/${currentEditVacancyId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(vacancyData)
});
} else {
response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(vacancyData)
});
}
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Ошибка сохранения');
}
closeVacancyModal();
await loadAllVacancies();
showNotification(currentEditVacancyId ? 'Вакансия обновлена!' : 'Вакансия создана!', 'success');
} catch (error) {
showNotification(error.message, 'error');
}
}
// ========== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ==========
function escapeHtml(unsafe) {
if (!unsafe) return '';
return unsafe.toString()
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
function logout() {
localStorage.removeItem('accessToken');
window.location.href = '/';
}
function debugVacancies() {
console.group('🔍 Отладка вакансий');
console.log('Всего вакансий в памяти:', allVacancies.length);
allVacancies.forEach((v, index) => {
console.log(`${index + 1}. [${v.is_active ? '✅' : '⭕'}] ${v.title} (ID: ${v.id})`);
});
console.groupEnd();
// Показать неактивные отдельно
const inactive = allVacancies.filter(v => !v.is_active);
if (inactive.length > 0) {
console.log('🚫 Неактивные вакансии:');
inactive.forEach(v => console.log(` - ${v.title}`));
} else {
console.log('⚠️ Неактивных вакансий нет');
}
}
// ========== ИНИЦИАЛИЗАЦИЯ ==========
document.addEventListener('DOMContentLoaded', function() {
createMenuOverlay();
initializeMenuHandlers();
loadProfile();
});
window.onclick = function(event) {
const modal = document.getElementById('vacancyModal');
if (event.target === modal) {
modal.style.display = 'none';
}
};
</script>
</body>
</html>