Files
yarmarka/templates/profile.html
2026-03-13 19:58:52 +03:00

1300 lines
48 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;
}
.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;
}
.delete-btn {
position: absolute;
top: 15px;
right: 15px;
background: #fee2e2;
color: #b91c1c;
border: none;
width: 30px;
height: 30px;
border-radius: 15px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.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; }
.loading {
text-align: center;
padding: 40px;
color: #4f7092;
}
.admin-badge {
background: #f59e0b;
color: white;
padding: 2px 8px;
border-radius: 20px;
font-size: 12px;
margin-left: 5px;
}
.error-message {
background: #fee2e2;
color: #b91c1c;
padding: 20px;
border-radius: 30px;
text-align: center;
margin: 20px 0;
}
@media (max-width: 768px) {
.profile-grid {
grid-template-columns: 1fr;
}
.applications-stats {
grid-template-columns: repeat(2, 1fr);
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="logo">
<i class="fas fa-briefcase"></i>
МП.Ярмарка
</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 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;">Новая вакансия</h3>
<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 style="display: flex; gap: 10px; margin-top: 30px;">
<button class="btn btn-primary" onclick="createVacancy()" style="flex: 1;">Создать</button>
<button class="btn btn-outline" onclick="closeVacancyModal()" style="flex: 1;">Отмена</button>
</div>
</div>
</div>
<script>
const API_BASE_URL = 'http://localhost:8000/api';
let token = localStorage.getItem('accessToken');
let currentUser = null;
let resumeId = null;
// Проверка авторизации
if (!token) {
window.location.href = '/login';
}
// Функция обновления навигации
function updateNavigation() {
const nav = document.getElementById('nav');
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 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();
// Обновляем навигацию
updateNavigation();
// Заполняем данные профиля
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();
} catch (error) {
console.error('Error loading profile:', error);
document.querySelector('.profile-sidebar').innerHTML = `
<div class="error-message">
<i class="fas fa-exclamation-circle"></i>
Ошибка загрузки профиля
</div>
`;
}
}
// Загрузка статистики пользователя
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.role === 'employee') {
showApplicationsPreview(appStats);
}
}
} catch (error) {
console.error('Error loading stats:', error);
}
}
// Показать превью откликов на главной
function showApplicationsPreview(stats) {
const container = document.getElementById('applicationsPreview');
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 (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>
`;
loadVacancies();
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'));
const tabs = document.querySelectorAll('.content-tab');
const panes = document.querySelectorAll('.content-pane');
if (tab === 'main') {
tabs[0]?.classList.add('active');
document.getElementById('mainPane').classList.add('active');
} else if (tab === 'vacancies') {
tabs[1]?.classList.add('active');
document.getElementById('vacanciesPane').classList.add('active');
} else if (tab === 'resume') {
if (currentUser.role === 'employee') {
tabs[1]?.classList.add('active');
} else {
tabs[2]?.classList.add('active');
}
document.getElementById('resumePane').classList.add('active');
} else if (tab === 'company') {
tabs[2]?.classList.add('active');
document.getElementById('companyPane').classList.add('active');
}
}
// ========== РАБОТА С РЕЗЮМЕ ==========
function addExperience(position = '', company = '', period = '') {
const container = document.getElementById('experienceContainer');
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)}">
</div>
<div class="resume-field">
<label>Компания</label>
<input type="text" class="exp-company" value="${escapeHtml(company)}">
</div>
<div class="resume-field">
<label>Период</label>
<input type="text" class="exp-period" value="${escapeHtml(period)}" placeholder="2022-2024">
</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');
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');
previewLink.href = `/resume/${resumeId}`;
previewLink.style.display = 'inline-flex';
// Показываем ссылку на предпросмотр
document.getElementById('resumePreviewLink').innerHTML = `
<a href="/resume/${resumeId}" target="_blank" class="resume-preview-link">
<i class="fas fa-external-link-alt"></i> Посмотреть, как видят ваше резюме работодатели
</a>
`;
document.getElementById('resumePreviewLink').style.display = 'block';
document.getElementById('experienceContainer').innerHTML = '';
resume.work_experience.forEach(exp => {
addExperience(exp.position, exp.company, exp.period);
});
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
});
});
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');
previewLink.href = `/resume/${resumeId}`;
previewLink.style.display = 'inline-flex';
document.getElementById('resumePreviewLink').innerHTML = `
<a href="/resume/${resumeId}" target="_blank" class="resume-preview-link">
<i class="fas fa-external-link-alt"></i> Посмотреть, как видят ваше резюме работодатели
</a>
`;
document.getElementById('resumePreviewLink').style.display = 'block';
alert('Резюме сохранено!');
} catch (error) {
alert(error.message);
}
}
// ========== РАБОТА С ВАКАНСИЯМИ ==========
async function loadVacancies() {
try {
const response = await fetch(`${API_BASE_URL}/vacancies`, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (!response.ok) throw new Error('Ошибка загрузки');
const vacancies = await response.json();
const container = document.getElementById('vacanciesList');
if (vacancies.length === 0) {
container.innerHTML = '<div class="loading">У вас пока нет вакансий</div>';
return;
}
container.innerHTML = vacancies.map(v => `
<div class="vacancy-card">
<button class="delete-btn" onclick="deleteVacancy(${v.id})">
<i class="fas fa-trash"></i>
</button>
<h3>${escapeHtml(v.title)}</h3>
<div style="color: #3b82f6; font-size: 14px; margin-bottom: 5px;">
<i class="fas fa-building"></i> ${escapeHtml(v.company_name || 'Название компании не указано')}
</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>
</div>
</div>
`).join('');
} catch (error) {
console.error('Error loading vacancies:', error);
document.getElementById('vacanciesList').innerHTML = '<div class="loading">Ошибка загрузки вакансий</div>';
}
}
async function deleteVacancy(vacancyId) {
if (!confirm('Удалить вакансию?')) return;
try {
const response = await fetch(`${API_BASE_URL}/vacancies/${vacancyId}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.ok) {
loadVacancies();
}
} catch (error) {
alert('Ошибка при удалении');
}
}
// ========== РАБОТА С КОМПАНИЕЙ ==========
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) {
alert('Введите название компании');
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('Ошибка сохранения');
alert('Информация о компании сохранена!');
} catch (error) {
alert(error.message);
}
}
// ========== РАБОТА С ВАКАНСИЯМИ (МОДАЛКА) ==========
function openVacancyModal() {
document.getElementById('vacancyModal').style.display = 'flex';
}
function closeVacancyModal() {
document.getElementById('vacancyModal').style.display = 'none';
}
async function createVacancy() {
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) {
alert('Введите название вакансии');
return;
}
try {
const response = await fetch(`${API_BASE_URL}/vacancies`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(vacancyData)
});
if (!response.ok) throw new Error('Ошибка создания');
closeVacancyModal();
loadVacancies();
// Очищаем форму
document.getElementById('vacancyTitle').value = '';
document.getElementById('vacancySalary').value = '';
document.getElementById('vacancyTags').value = '';
document.getElementById('vacancyDescription').value = '';
document.getElementById('vacancyContact').value = '';
alert('Вакансия создана!');
} catch (error) {
alert(error.message);
}
}
// ========== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ==========
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 = '/';
}
// Закрытие модалки по клику вне окна
window.onclick = function(event) {
const modal = document.getElementById('vacancyModal');
if (event.target === modal) {
modal.style.display = 'none';
}
};
// Загрузка при старте
loadProfile();
</script>
</body>
</html>