Files
yarmarka/templates/resume_detail.html
2026-03-26 19:00:41 +03:00

1331 lines
46 KiB
HTML
Raw Permalink 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">
<!-- SEO теги будут заменены сервером -->
<title id="pageTitle">Резюме | Rabota.Today</title>
<meta name="description" id="metaDescription" content="Подробная информация о резюме кандидата на Rabota.Today. Опыт работы, образование, навыки, контактная информация.">
<meta name="keywords" id="metaKeywords" content="резюме, поиск сотрудников, кандидат, Rabota.Today, подбор персонала">
<meta name="author" content="Rabota.Today">
<meta name="robots" content="index, follow">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="profile">
<meta property="og:url" id="ogUrl" content="https://yarmarka.rabota.today/">
<meta property="og:title" id="ogTitle" content="Резюме | Rabota.Today">
<meta property="og:description" id="ogDescription" content="Подробная информация о резюме кандидата на Rabota.Today">
<meta property="og:image" id="ogImage" content="https://yarmarka.rabota.today/static/images/og-image-resume.jpg">
<meta property="og:site_name" content="Rabota.Today">
<meta property="og:locale" content="ru_RU">
<meta property="profile:first_name" id="profileFirstName" content="">
<meta property="profile:last_name" id="profileLastName" content="">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" id="twitterTitle" content="Резюме | Rabota.Today">
<meta name="twitter:description" id="twitterDescription" content="Подробная информация о резюме кандидата на Rabota.Today">
<meta name="twitter:image" id="twitterImage" content="https://yarmarka.rabota.today/static/images/og-image-resume.jpg">
<!-- Canonical URL -->
<link rel="canonical" id="canonicalUrl" href="https://yarmarka.rabota.today/">
<!-- Дополнительные SEO метатеги -->
<meta name="format-detection" content="telephone=no">
<meta name="theme-color" content="#0b1c34">
<!-- Структурированные данные (JSON-LD) будут заменены сервером -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Person",
"name": "",
"jobTitle": "",
"description": ""
}
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.1/build/qrcode.min.js"></script>
<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;
cursor: pointer;
}
.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;
}
.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;
transition: 0.2s;
}
.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;
flex-shrink: 0;
overflow: hidden;
}
.resume-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.resume-title {
flex: 1;
}
.resume-title-header {
display: flex;
align-items: center;
gap: 20px;
flex-wrap: wrap;
margin-bottom: 10px;
}
.resume-name {
font-size: 32px;
font-weight: 700;
color: #0b1c34;
}
.qr-button {
width: 50px;
height: 50px;
background: linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(59,130,246,0.3);
color: white;
font-size: 24px;
position: relative;
overflow: hidden;
border: none;
flex-shrink: 0;
}
.qr-button:hover {
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(59,130,246,0.4);
}
.qr-button:active {
transform: scale(0.95);
}
.qr-button i {
font-size: 24px;
}
.qr-view-count {
position: absolute;
top: -8px;
right: -8px;
background: #ef4444;
color: white;
font-size: 11px;
font-weight: 600;
padding: 2px 6px;
border-radius: 20px;
min-width: 20px;
text-align: center;
box-shadow: 0 2px 4px rgba(239,68,68,0.3);
}
.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;
transition: 0.2s;
}
.tag:hover {
background: #dbeafe;
}
.section-title {
font-size: 24px;
color: #0b1c34;
margin: 40px 0 20px;
font-weight: 600;
border-bottom: 2px solid #dee9f5;
padding-bottom: 10px;
}
.about-me {
background: #f9fcff;
border-radius: 20px;
padding: 25px;
line-height: 1.8;
color: #1f3f60;
font-size: 16px;
}
.experience-item {
background: #f9fcff;
border-radius: 20px;
padding: 25px;
margin-bottom: 20px;
border-left: 4px solid #3b82f6;
}
.experience-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
flex-wrap: wrap;
gap: 10px;
}
.experience-title {
font-size: 20px;
font-weight: 700;
color: #0b1c34;
}
.experience-company {
font-size: 18px;
color: #3b82f6;
font-weight: 600;
margin-bottom: 5px;
}
.experience-period {
color: #4f7092;
font-size: 14px;
display: flex;
align-items: center;
gap: 5px;
}
.experience-description {
margin-top: 15px;
padding: 15px;
background: white;
border-radius: 15px;
line-height: 1.6;
color: #1f3f60;
white-space: pre-line;
border-left: 3px solid #10b981;
}
.education-item {
background: #f9fcff;
border-radius: 20px;
padding: 20px;
margin-bottom: 15px;
}
.education-header {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
flex-wrap: wrap;
}
.education-institution {
font-size: 18px;
font-weight: 700;
color: #0b1c34;
}
.education-specialty {
color: #3b82f6;
font-size: 16px;
margin-bottom: 5px;
}
.education-year {
color: #4f7092;
font-size: 14px;
}
.contact-section {
background: #eef4fa;
border-radius: 30px;
padding: 30px;
margin: 40px 0 20px;
}
.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;
background: white;
border-radius: 15px;
transition: 0.2s;
}
.contact-item:hover {
background: #f9fcff;
transform: translateX(5px);
}
.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;
text-decoration: none;
}
.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;
}
/* QR Modal Styles */
.qr-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.7);
align-items: flex-start;
justify-content: center;
z-index: 1000;
backdrop-filter: blur(5px);
overflow-y: auto;
padding: 20px 0;
}
.qr-modal.active {
display: flex;
animation: fadeIn 0.3s;
}
.qr-modal-content {
background: white;
border-radius: 40px;
padding: 40px;
max-width: 500px;
width: 90%;
position: relative;
box-shadow: 0 30px 60px rgba(0,0,0,0.3);
animation: slideUp 0.3s;
margin: auto;
max-height: 90vh;
overflow-y: auto;
}
.qr-modal-close {
position: sticky;
top: 0;
right: 0;
margin-left: auto;
margin-bottom: 20px;
width: 40px;
height: 40px;
border-radius: 20px;
background: #eef4fa;
border: none;
font-size: 24px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: #4f7092;
transition: 0.2s;
z-index: 10;
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
}
.qr-modal-close:hover {
background: #dbeafe;
color: #0b1c34;
transform: scale(1.1);
}
.qr-modal h2 {
color: #0b1c34;
margin-bottom: 10px;
font-size: 24px;
display: flex;
align-items: center;
gap: 10px;
word-break: break-word;
padding-right: 40px;
position: sticky;
top: 0;
background: white;
padding-top: 0;
z-index: 5;
}
.qr-modal h2 i {
color: #3b82f6;
}
.qr-subtitle {
color: #4f7092;
margin-bottom: 25px;
font-size: 16px;
word-break: break-all;
}
.qr-container {
display: flex;
justify-content: center;
margin: 30px 0;
padding: 20px;
background: #f9fcff;
border-radius: 30px;
position: relative;
}
#qrCanvas {
width: 250px;
height: 250px;
image-rendering: crisp-edges;
display: block;
}
.qr-logo-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 60px;
height: 60px;
background: white;
border-radius: 15px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 10px rgba(0,0,0,0.2);
border: 3px solid white;
pointer-events: none;
}
.qr-logo-overlay img {
width: 100%;
height: 100%;
object-fit: contain;
border-radius: 12px;
}
.qr-logo-overlay i {
font-size: 30px;
color: #3b82f6;
}
.qr-stats {
display: flex;
justify-content: space-around;
margin: 20px 0;
padding: 15px;
background: #f0f7ff;
border-radius: 20px;
flex-wrap: wrap;
gap: 10px;
}
.qr-stat-item {
text-align: center;
min-width: 80px;
}
.qr-stat-value {
font-size: 20px;
font-weight: 700;
color: #0b1c34;
}
.qr-stat-label {
font-size: 12px;
color: #4f7092;
}
.qr-actions {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 12px;
margin: 20px 0;
}
.qr-action-btn {
padding: 14px;
border-radius: 30px;
border: none;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: 0.2s;
font-size: 14px;
}
.qr-action-btn.primary {
background: #0b1c34;
color: white;
}
.qr-action-btn.primary:hover {
background: #1b3f6b;
}
.qr-action-btn.secondary {
background: #eef4fa;
color: #1f3f60;
}
.qr-action-btn.secondary:hover {
background: #dbeafe;
}
.qr-action-btn i {
font-size: 16px;
}
.qr-share-buttons {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #dee9f5;
flex-wrap: wrap;
}
.qr-share-btn {
width: 44px;
height: 44px;
border-radius: 22px;
border: none;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
cursor: pointer;
transition: 0.2s;
}
.qr-share-btn.whatsapp {
background: #25D366;
color: white;
}
.qr-share-btn.telegram {
background: #0088cc;
color: white;
}
.qr-share-btn.email {
background: #ea4335;
color: white;
}
.qr-share-btn:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.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;
}
.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;
}
.fallback-link {
padding: 20px;
background: white;
border-radius: 15px;
margin: 10px;
word-break: break-all;
text-align: center;
}
.fallback-link a {
color: #3b82f6;
text-decoration: none;
font-size: 14px;
}
.fallback-link a:hover {
text-decoration: underline;
}
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0%); opacity: 1; }
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { transform: translateY(30px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
@media (max-width: 768px) {
.resume-header {
flex-direction: column;
text-align: center;
}
.resume-title-header {
justify-content: center;
}
.qr-button {
margin: 0 auto;
}
.qr-modal-content {
padding: 30px 20px;
width: 95%;
}
.qr-modal-close {
position: fixed;
top: 10px;
right: 10px;
background: white;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="logo" onclick="window.location.href='/'">
<i class="fas fa-briefcase"></i>
Rabota.Today
</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>
<!-- Модальное окно для QR-кода -->
<div class="qr-modal" id="qrModal">
<div class="qr-modal-content">
<button class="qr-modal-close" onclick="closeQRModal()">&times;</button>
<h2><i class="fas fa-qrcode"></i> <span id="qrResumeName"></span></h2>
<div class="qr-subtitle" id="qrResumeUrl">yarmarka.rabota.today/resume/</div>
<div class="qr-container" id="qrContainer">
<canvas id="qrCanvas" width="250" height="250"></canvas>
<div class="qr-logo-overlay" id="qrLogoOverlay" style="display: none;">
<img id="qrLogoImg" src="" alt="avatar" style="display: none;">
<i id="qrLogoIcon" class="fas fa-user"></i>
</div>
</div>
<div class="qr-stats">
<div class="qr-stat-item">
<div class="qr-stat-value" id="qrViewCount">0</div>
<div class="qr-stat-label">просмотров</div>
</div>
<div class="qr-stat-item">
<div class="qr-stat-value" id="qrExperienceCount">0</div>
<div class="qr-stat-label">мест работы</div>
</div>
<div class="qr-stat-item">
<div class="qr-stat-value" id="qrPosition"></div>
<div class="qr-stat-label">должность</div>
</div>
</div>
<div class="qr-actions">
<button class="qr-action-btn primary" onclick="downloadQR()">
<i class="fas fa-download"></i> Скачать PNG
</button>
<button class="qr-action-btn secondary" onclick="copyResumeLink()">
<i class="fas fa-link"></i> Копировать ссылку
</button>
<button class="qr-action-btn secondary" onclick="saveToContacts()">
<i class="fas fa-address-card"></i> В контакты
</button>
<button class="qr-action-btn secondary" onclick="printQR()">
<i class="fas fa-print"></i> Распечатать
</button>
</div>
<div class="qr-share-buttons">
<button class="qr-share-btn whatsapp" onclick="shareQR('whatsapp')">
<i class="fab fa-whatsapp"></i>
</button>
<button class="qr-share-btn telegram" onclick="shareQR('telegram')">
<i class="fab fa-telegram"></i>
</button>
<button class="qr-share-btn email" onclick="shareQR('email')">
<i class="fas fa-envelope"></i>
</button>
</div>
</div>
</div>
<!-- Уведомления -->
<div class="notification" id="notification"></div>
<script>
const API_BASE_URL = window.location.protocol + '//' + window.location.host + '/api';
let currentUser = null;
let currentResume = null;
let qrViewCount = 0;
const pathParts = window.location.pathname.split('/');
const resumeId = pathParts[pathParts.length - 1];
function decodeHtmlEntities(text) {
if (!text) return '';
const textarea = document.createElement('textarea');
textarea.innerHTML = text;
return textarea.value;
}
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 showNotification(message, type = 'info') {
const notification = document.getElementById('notification');
if (!notification) return;
notification.className = `notification ${type}`;
notification.innerHTML = message;
notification.style.display = 'block';
setTimeout(() => { notification.style.display = 'none'; }, 3000);
}
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) {
const firstName = currentUser.full_name.split(' ')[0];
const adminBadge = currentUser.is_admin ? '<span class="admin-badge">Admin</span>' : '';
nav.innerHTML = `
<a href="/">Главная</a>
<a href="/vacancies">Вакансии</a>
<a href="/resumes" class="active">Резюме</a>
<a href="/favorites">Избранное</a>
<a href="/applications">Отклики</a>
<a href="/profile" class="profile-link">
<i class="fas fa-user-circle"></i> ${escapeHtml(firstName)} ${adminBadge}
</a>
`;
} else {
nav.innerHTML = `
<a href="/">Главная</a>
<a href="/vacancies">Вакансии</a>
<a href="/resumes" class="active">Резюме</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('Резюме не найдено');
}
currentResume = await response.json();
// Отладочный вывод
console.log('📥 Загружено резюме:', currentResume);
console.log('📌 Теги (сырые):', currentResume.tags);
renderResume(currentResume);
} 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 decodedName = decodeHtmlEntities(resume.full_name || '');
const decodedPosition = decodeHtmlEntities(resume.desired_position || '');
// Проверяем и обрабатываем теги (поддержка разных форматов)
let tags = [];
if (resume.tags) {
if (Array.isArray(resume.tags)) {
// Если теги приходят как массив объектов или строк
tags = resume.tags.map(t => {
if (typeof t === 'string') return t;
if (t.name) return t.name;
return t;
});
} else if (typeof resume.tags === 'string') {
tags = resume.tags.split(',').map(t => t.trim()).filter(t => t);
}
}
console.log('📌 Теги для отображения:', tags);
const experienceHtml = resume.work_experience && resume.work_experience.length > 0
? resume.work_experience.map(exp => `
<div class="experience-item">
<div class="experience-header">
<span class="experience-title">${escapeHtml(exp.position)}</span>
<span class="experience-period">
<i class="far fa-calendar"></i> ${escapeHtml(exp.period || 'Период не указан')}
</span>
</div>
<div class="experience-company">
<i class="fas fa-building"></i> ${escapeHtml(exp.company)}
</div>
${exp.description ? `
<div class="experience-description">
<strong>📋 Обязанности и достижения:</strong>
<p style="margin-top: 10px;">${escapeHtml(exp.description).replace(/\n/g, '<br>')}</p>
</div>
` : ''}
</div>
`).join('')
: '<p style="color: #4f7092; text-align: center; padding: 20px;">Опыт работы не указан</p>';
const educationHtml = resume.education && resume.education.length > 0
? resume.education.map(edu => `
<div class="education-item">
<div class="education-header">
<span class="education-institution">${escapeHtml(edu.institution)}</span>
<span class="education-year">${escapeHtml(edu.graduation_year || 'Год не указан')}</span>
</div>
<div class="education-specialty">${escapeHtml(edu.specialty || 'Специальность не указана')}</div>
</div>
`).join('')
: '<p style="color: #4f7092; text-align: center; padding: 20px;">Образование не указано</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-title-header">
<div class="resume-name">${escapeHtml(decodedName)}</div>
<button class="qr-button" onclick="openQRModal()" title="QR-код резюме">
<i class="fas fa-qrcode"></i>
<span class="qr-view-count" id="qrViewBadge">0</span>
</button>
</div>
<div class="resume-position">${escapeHtml(decodedPosition)}</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>
<!-- Блок с тегами - исправленное отображение -->
${tags && tags.length > 0 ? `
<div class="resume-tags">
${tags.map(t => `<span class="tag">${escapeHtml(t)}</span>`).join('')}
</div>
` : ''}
<h2 class="section-title">О себе</h2>
<div class="about-me">${escapeHtml(resume.about_me || 'Информация не заполнена').replace(/\n/g, '<br>')}</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>
${!token ?
'<a href="/login" class="btn btn-primary">Войти как работодатель</a>' :
'<p class="btn btn-primary" style="opacity: 0.6;">Только для работодателей</p>'
}
</div>
`}
</div>
<div class="action-buttons">
${canContact ? `
<button class="btn btn-primary" onclick="contactCandidate()">
<i class="fas fa-paper-plane"></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>
`;
}
// QR Code functions
function openQRModal() {
if (!currentResume) return;
const decodedName = decodeHtmlEntities(currentResume.full_name || '');
const decodedPosition = decodeHtmlEntities(currentResume.desired_position || '');
document.getElementById('qrResumeName').textContent = decodedName;
const resumeUrl = window.location.origin + '/resume/' + resumeId;
document.getElementById('qrResumeUrl').textContent = resumeUrl.replace('https://', '').replace('http://', '');
document.getElementById('qrViewCount').textContent = ++qrViewCount;
document.getElementById('qrViewBadge').textContent = qrViewCount;
document.getElementById('qrExperienceCount').textContent = currentResume.work_experience?.length || 0;
document.getElementById('qrPosition').textContent = decodedPosition || '—';
generateQRCodeWithLogo(resumeUrl);
document.getElementById('qrModal').classList.add('active');
}
function closeQRModal() {
document.getElementById('qrModal').classList.remove('active');
}
function generateQRCodeWithLogo(text) {
const canvas = document.getElementById('qrCanvas');
if (!canvas) { showNotification('Ошибка: не найден элемент для QR-кода', 'error'); return; }
if (typeof QRCode === 'undefined') {
showNotification('Ошибка загрузки библиотеки QR-кода', 'error');
const container = document.getElementById('qrContainer');
if (container && !container.querySelector('.fallback-link')) {
const fallback = document.createElement('div');
fallback.className = 'fallback-link';
fallback.innerHTML = `<a href="${text}" target="_blank">${text}</a>`;
container.appendChild(fallback);
}
return;
}
const options = { width: 250, height: 250, color: { dark: '#0b1c34', light: '#ffffff' }, errorCorrectionLevel: 'H' };
QRCode.toCanvas(canvas, text, options, function(error) {
if (error) { showNotification('Ошибка генерации QR-кода', 'error'); }
else {
const logoOverlay = document.getElementById('qrLogoOverlay');
const logoIcon = document.getElementById('qrLogoIcon');
if (logoOverlay) logoOverlay.style.display = 'flex';
if (logoIcon) { logoIcon.style.display = 'block'; logoIcon.className = 'fas fa-user'; }
}
});
}
function downloadQR() {
const canvas = document.getElementById('qrCanvas');
if (!canvas) { showNotification('QR-код не найден', 'error'); return; }
const combinedCanvas = document.createElement('canvas');
combinedCanvas.width = canvas.width;
combinedCanvas.height = canvas.height;
const ctx = combinedCanvas.getContext('2d');
ctx.drawImage(canvas, 0, 0);
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(125, 125, 35, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = '#3b82f6';
ctx.font = '30px "Font Awesome 6 Free"';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('', 125, 125);
const link = document.createElement('a');
const decodedName = decodeHtmlEntities(currentResume.full_name || '');
const filename = `resume_${decodedName.toLowerCase().replace(/[^a-zа-я0-9]/g, '_')}.png`;
link.download = filename;
link.href = combinedCanvas.toDataURL('image/png');
link.click();
showNotification('QR-код скачан', 'success');
}
function copyResumeLink() {
const url = window.location.origin + '/resume/' + resumeId;
navigator.clipboard.writeText(url).then(() => showNotification('Ссылка скопирована', 'success')).catch(() => showNotification('Ошибка копирования', 'error'));
}
function saveToContacts() {
if (!currentResume) return;
const decodedName = decodeHtmlEntities(currentResume.full_name || '');
const decodedPosition = decodeHtmlEntities(currentResume.desired_position || '');
const vCard = `BEGIN:VCARD\nVERSION:3.0\nFN:${decodedName}\nTITLE:${decodedPosition}\n${currentResume.email ? `EMAIL:${currentResume.email}` : ''}\n${currentResume.phone ? `TEL:${currentResume.phone}` : ''}\n${currentResume.telegram ? `X-SOCIALPROFILE;TYPE=telegram:https://t.me/${currentResume.telegram.replace('@', '')}` : ''}\nNOTE:Кандидат на позицию ${decodedPosition}. Подробнее: ${window.location.origin}/resume/${resumeId}\nEND:VCARD`;
const blob = new Blob([vCard], { type: 'text/vcard' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `${decodedName}.vcf`;
link.click();
URL.revokeObjectURL(url);
showNotification('Контакт сохранен', 'success');
}
function printQR() {
const canvas = document.getElementById('qrCanvas');
const decodedName = decodeHtmlEntities(currentResume.full_name || '');
const decodedPosition = decodeHtmlEntities(currentResume.desired_position || '');
const printWindow = window.open('', '_blank');
printWindow.document.write(`
<html><head><title>QR-код резюме ${decodedName}</title>
<style>body{display:flex;justify-content:center;align-items:center;height:100vh;flex-direction:column;font-family:Arial;margin:0;padding:20px;}h2{color:#0b1c34;text-align:center;}h3{color:#3b82f6;text-align:center;}img{max-width:300px;box-shadow:0 10px 30px rgba(0,0,0,0.1);border-radius:20px;}.url{color:#4f7092;margin-top:20px;text-align:center;}</style>
</head><body>
<h2>${escapeHtml(decodedName)}</h2>
<h3>${escapeHtml(decodedPosition)}</h3>
<img src="${canvas.toDataURL()}" />
<div class="url">${window.location.origin}/resume/${resumeId}</div>
</body></html>
`);
printWindow.document.close();
printWindow.focus();
printWindow.print();
}
function shareQR(platform) {
const url = window.location.origin + '/resume/' + resumeId;
const decodedName = decodeHtmlEntities(currentResume.full_name || '');
const text = `Резюме ${decodedName} на Rabota.Today`;
let shareUrl = '';
if (platform === 'whatsapp') shareUrl = `https://wa.me/?text=${encodeURIComponent(text + ' ' + url)}`;
else if (platform === 'telegram') shareUrl = `https://t.me/share/url?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`;
else if (platform === 'email') shareUrl = `mailto:?subject=${encodeURIComponent('Резюме ' + decodedName)}&body=${encodeURIComponent(text + '\n\n' + url)}`;
if (shareUrl) window.open(shareUrl, '_blank');
}
function contactCandidate() { showNotification('Функция связи будет доступна в ближайшее время', 'info'); }
function saveToFavorites() {
const token = localStorage.getItem('accessToken');
if (!token) { if (confirm('Для добавления в избранное нужно войти в систему. Перейти на страницу входа?')) { window.location.href = '/login'; } return; }
showNotification('Резюме добавлено в избранное', 'success');
}
function shareResume() {
const url = window.location.href;
if (navigator.share) { navigator.share({ title: document.title, url: url }).catch(() => copyResumeLink()); }
else { copyResumeLink(); }
}
window.onclick = function(event) {
const modal = document.getElementById('qrModal');
if (event.target === modal) modal.classList.remove('active');
};
checkAuth().then(() => loadResume());
</script>
</body>
</html>