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

641 lines
21 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: 1000px;
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-link {
display: inline-block;
margin-bottom: 20px;
color: #4f7092;
text-decoration: none;
font-size: 16px;
}
.back-link i {
margin-right: 8px;
}
.back-link:hover {
color: #0b1c34;
}
.resume-detail {
background: white;
border-radius: 40px;
padding: 40px;
box-shadow: 0 20px 40px rgba(0,20,40,0.1);
}
.resume-header {
display: flex;
align-items: center;
gap: 30px;
margin-bottom: 30px;
flex-wrap: wrap;
}
.resume-avatar {
width: 120px;
height: 120px;
background: #eef4fa;
border-radius: 60px;
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
color: #3b82f6;
}
.resume-title {
flex: 1;
}
.resume-name {
font-size: 32px;
font-weight: 700;
color: #0b1c34;
margin-bottom: 5px;
}
.resume-position {
font-size: 20px;
color: #3b82f6;
margin-bottom: 10px;
}
.resume-stats {
display: flex;
gap: 20px;
color: #4f7092;
font-size: 14px;
}
.resume-salary {
font-size: 28px;
font-weight: 700;
color: #0f2b4f;
margin: 20px 0;
padding: 20px 0;
border-top: 2px solid #dee9f5;
border-bottom: 2px solid #dee9f5;
}
.resume-tags {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 20px 0;
}
.tag {
background: #eef4fa;
padding: 8px 16px;
border-radius: 30px;
font-size: 14px;
color: #1f3f60;
font-weight: 500;
}
.section-title {
font-size: 24px;
color: #0b1c34;
margin: 30px 0 20px;
font-weight: 600;
}
.about-me {
background: #f9fcff;
border-radius: 20px;
padding: 25px;
line-height: 1.8;
color: #1f3f60;
font-size: 16px;
}
.experience-item, .education-item {
background: #f9fcff;
border-radius: 20px;
padding: 25px;
margin-bottom: 20px;
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
flex-wrap: wrap;
gap: 10px;
}
.item-title {
font-size: 18px;
font-weight: 700;
color: #0b1c34;
}
.item-subtitle {
color: #3b82f6;
font-weight: 600;
margin-bottom: 10px;
}
.item-period {
color: #4f7092;
font-size: 14px;
}
.contact-section {
background: #eef4fa;
border-radius: 30px;
padding: 30px;
margin: 30px 0;
}
.contact-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-top: 20px;
}
.contact-item {
display: flex;
align-items: center;
gap: 15px;
padding: 12px 0;
color: #1f3f60;
}
.contact-item i {
color: #3b82f6;
width: 24px;
font-size: 18px;
}
.action-buttons {
display: flex;
gap: 15px;
margin-top: 30px;
flex-wrap: wrap;
}
.btn {
padding: 16px 32px;
border-radius: 40px;
border: none;
font-weight: 600;
cursor: pointer;
flex: 1;
min-width: 200px;
font-size: 16px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 10px;
transition: 0.2s;
}
.btn-primary {
background: #0b1c34;
color: white;
}
.btn-primary:hover {
background: #1b3f6b;
transform: translateY(-2px);
}
.btn-outline {
background: transparent;
border: 2px solid #3b82f6;
color: #0b1c34;
}
.btn-outline:hover {
background: #eef4fa;
}
.loading {
text-align: center;
padding: 60px;
color: #4f7092;
font-size: 18px;
}
.error-message {
background: #fee2e2;
color: #b91c1c;
padding: 20px;
border-radius: 30px;
text-align: center;
margin: 40px 0;
}
.view-counter {
display: inline-flex;
align-items: center;
gap: 5px;
background: #eef4fa;
padding: 5px 15px;
border-radius: 30px;
font-size: 14px;
color: #1f3f60;
}
</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>
</div>
<a href="/resumes" class="back-link"><i class="fas fa-arrow-left"></i> Назад к резюме</a>
<div id="resumeDetail" class="resume-detail">
<div class="loading">Загрузка резюме...</div>
</div>
</div>
<script>
const API_BASE_URL = 'http://localhost:8000/api';
let currentUser = null;
// Получаем ID резюме из URL
const pathParts = window.location.pathname.split('/');
const resumeId = pathParts[pathParts.length - 1];
// Проверка авторизации
async function checkAuth() {
const token = localStorage.getItem('accessToken');
if (token) {
try {
const response = await fetch(`${API_BASE_URL}/user`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
currentUser = await response.json();
} else {
localStorage.removeItem('accessToken');
}
} catch (error) {
console.error('Error checking auth:', error);
}
}
updateNavigation();
}
// Обновление навигации
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">Избранное</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>
`;
} else {
// Пользователь не авторизован
nav.innerHTML = `
<a href="/">Главная</a>
<a href="/vacancies">Вакансии</a>
<a href="/resumes">Резюме</a>
<a href="/login">Войти</a>
<a href="/register">Регистрация</a>
`;
}
}
// Загрузка резюме
async function loadResume() {
try {
const response = await fetch(`${API_BASE_URL}/resumes/${resumeId}`);
if (!response.ok) {
throw new Error('Резюме не найдено');
}
const resume = await response.json();
renderResume(resume);
} catch (error) {
console.error('Error loading resume:', error);
document.getElementById('resumeDetail').innerHTML = `
<div class="error-message">
<i class="fas fa-exclamation-circle"></i>
Резюме не найдено или было удалено
</div>
<div style="text-align: center; margin-top: 20px;">
<a href="/resumes" class="btn btn-primary">Вернуться к списку</a>
</div>
`;
}
}
// Отображение резюме
function renderResume(resume) {
const container = document.getElementById('resumeDetail');
const token = localStorage.getItem('accessToken');
const canContact = token && currentUser && currentUser.role === 'employer';
// Формируем блок с опытом работы
const experienceHtml = resume.work_experience && resume.work_experience.length > 0
? resume.work_experience.map(exp => `
<div class="experience-item">
<div class="item-header">
<span class="item-title">${escapeHtml(exp.position)}</span>
<span class="item-period">${escapeHtml(exp.period || 'Период не указан')}</span>
</div>
<div class="item-subtitle">${escapeHtml(exp.company)}</div>
</div>
`).join('')
: '<p style="color: #4f7092;">Опыт работы не указан</p>';
// Формируем блок с образованием
const educationHtml = resume.education && resume.education.length > 0
? resume.education.map(edu => `
<div class="education-item">
<div class="item-header">
<span class="item-title">${escapeHtml(edu.institution)}</span>
<span class="item-period">${escapeHtml(edu.graduation_year || 'Год не указан')}</span>
</div>
<div class="item-subtitle">${escapeHtml(edu.specialty || 'Специальность не указана')}</div>
</div>
`).join('')
: '<p style="color: #4f7092;">Образование не указано</p>';
container.innerHTML = `
<div class="resume-header">
<div class="resume-avatar">
<i class="fas fa-user"></i>
</div>
<div class="resume-title">
<div class="resume-name">${escapeHtml(resume.full_name || 'Имя не указано')}</div>
<div class="resume-position">${escapeHtml(resume.desired_position || 'Желаемая должность не указана')}</div>
<div class="resume-stats">
<span class="view-counter">
<i class="fas fa-eye"></i> ${resume.views || 0} просмотров
</span>
<span><i class="fas fa-calendar"></i> Обновлено: ${new Date(resume.updated_at).toLocaleDateString()}</span>
</div>
</div>
</div>
<div class="resume-salary">${escapeHtml(resume.desired_salary || 'Зарплатные ожидания не указаны')}</div>
${resume.tags && resume.tags.length > 0 ? `
<div class="resume-tags">
${resume.tags.map(t => `<span class="tag">${escapeHtml(t.name)}</span>`).join('')}
</div>
` : ''}
<h2 class="section-title">О себе</h2>
<div class="about-me">${escapeHtml(resume.about_me || 'Информация не заполнена')}</div>
<h2 class="section-title">Опыт работы</h2>
${experienceHtml}
<h2 class="section-title">Образование</h2>
${educationHtml}
<div class="contact-section">
<h3 style="color: #0b1c34; margin-bottom: 20px;">Контактная информация</h3>
${canContact ? `
<div class="contact-grid">
<div class="contact-item">
<i class="fas fa-envelope"></i>
<span>${escapeHtml(resume.email || 'Email не указан')}</span>
</div>
<div class="contact-item">
<i class="fas fa-phone"></i>
<span>${escapeHtml(resume.phone || 'Телефон не указан')}</span>
</div>
<div class="contact-item">
<i class="fab fa-telegram"></i>
<span>${escapeHtml(resume.telegram || 'Telegram не указан')}</span>
</div>
</div>
` : `
<div style="text-align: center; padding: 30px; background: white; border-radius: 20px;">
<i class="fas fa-lock" style="font-size: 48px; color: #4f7092; margin-bottom: 15px;"></i>
<p style="color: #1f3f60; margin-bottom: 20px;">
Контактные данные доступны только авторизованным работодателям
</p>
<a href="/login" class="btn btn-primary">Войти как работодатель</a>
</div>
`}
</div>
<div class="action-buttons">
${canContact ? `
<button class="btn btn-primary" onclick="contactCandidate()">
<i class="fas fa-paper-plane"></i> Связаться
</button>
` : !token ? `
<button class="btn btn-primary" onclick="redirectToLogin()">
<i class="fas fa-sign-in-alt"></i> Войдите чтобы увидеть контакты
</button>
` : ''}
<button class="btn btn-outline" onclick="saveToFavorites()">
<i class="fas fa-heart"></i> В избранное
</button>
<button class="btn btn-outline" onclick="shareResume()">
<i class="fas fa-share-alt"></i> Поделиться
</button>
</div>
`;
}
// Связаться с кандидатом
function contactCandidate() {
const token = localStorage.getItem('accessToken');
if (!token) {
redirectToLogin();
return;
}
// Здесь можно открыть модальное окно с формой сообщения
alert('Функция связи будет доступна в ближайшее время');
}
// Добавить в избранное
function saveToFavorites() {
const token = localStorage.getItem('accessToken');
if (!token) {
if (confirm('Для добавления в избранное нужно войти в систему. Перейти на страницу входа?')) {
window.location.href = '/login';
}
return;
}
alert('Резюме добавлено в избранное');
}
// Поделиться резюме
function shareResume() {
const url = window.location.href;
navigator.clipboard.writeText(url).then(() => {
alert('Ссылка скопирована в буфер обмена');
}).catch(() => {
alert('Ссылка: ' + url);
});
}
// Редирект на страницу входа
function redirectToLogin() {
if (confirm('Для просмотра контактов нужно войти в систему. Перейти на страницу входа?')) {
window.location.href = '/login';
}
}
// Экранирование HTML
function escapeHtml(unsafe) {
if (!unsafe) return '';
return unsafe.toString()
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
// Загрузка при старте
checkAuth().then(() => {
loadResume();
});
</script>
</body>
</html>