Files
yarmarka/templates/vacancy_detail.html
2026-03-20 19:49:22 +03:00

1278 lines
45 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">
<!-- 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="website">
<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.jpg">
<meta property="og:site_name" content="Rabota.Today">
<meta property="og:locale" content="ru_RU">
<!-- 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.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": "JobPosting",
"title": "",
"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;
}
.vacancy-detail {
background: white;
border-radius: 40px;
padding: 40px;
box-shadow: 0 20px 40px rgba(0,20,40,0.1);
}
.vacancy-header {
display: flex;
justify-content: space-between;
align-items: start;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 20px;
}
.vacancy-title {
font-size: 36px;
color: #0b1c34;
font-weight: 700;
flex: 1;
}
.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);
}
.vacancy-company-link {
display: inline-flex;
align-items: center;
gap: 12px;
color: #3b82f6;
font-size: 20px;
font-weight: 600;
text-decoration: none;
padding: 8px 16px;
border-radius: 40px;
background: #f0f7ff;
transition: all 0.3s ease;
margin: 15px 0;
border: 2px solid transparent;
}
.vacancy-company-link:hover {
background: #dbeafe;
border-color: #3b82f6;
transform: translateX(5px);
box-shadow: 0 4px 12px rgba(59,130,246,0.2);
}
.vacancy-company-link i {
font-size: 24px;
}
.vacancy-company-link .company-name {
border-bottom: 2px dashed #3b82f6;
}
.vacancy-company-link:hover .company-name {
border-bottom-style: solid;
}
.company-link-hint {
font-size: 14px;
color: #4f7092;
margin-left: 5px;
opacity: 0;
transition: 0.2s;
}
.vacancy-company-link:hover .company-link-hint {
opacity: 1;
}
.vacancy-salary {
font-size: 32px;
font-weight: 700;
color: #0f2b4f;
margin: 25px 0;
padding: 25px 0;
border-top: 2px solid #dee9f5;
border-bottom: 2px solid #dee9f5;
}
.vacancy-tags {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 25px 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: 30px 0 20px;
font-weight: 600;
}
.vacancy-description {
color: #1f3f60;
line-height: 1.8;
white-space: pre-line;
font-size: 16px;
}
.company-info {
background: #f9fcff;
border-radius: 30px;
padding: 30px;
margin: 30px 0;
border-left: 4px solid #3b82f6;
}
.company-info-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 15px;
}
.company-info-header h3 {
color: #0b1c34;
font-size: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.company-info-header h3 i {
color: #3b82f6;
}
.company-view-all {
color: #3b82f6;
text-decoration: none;
font-weight: 600;
padding: 8px 16px;
border-radius: 30px;
background: white;
transition: 0.2s;
display: inline-flex;
align-items: center;
gap: 8px;
}
.company-view-all:hover {
background: #dbeafe;
transform: translateX(5px);
}
.company-info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}
.company-info-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
background: white;
border-radius: 20px;
transition: 0.2s;
}
.company-info-item:hover {
background: #eef4fa;
transform: translateX(5px);
}
.company-info-item i {
color: #3b82f6;
width: 24px;
font-size: 18px;
}
.company-info-item a {
color: #1f3f60;
text-decoration: none;
font-weight: 500;
}
.company-info-item a:hover {
color: #3b82f6;
text-decoration: underline;
}
.contact-info {
background: #eef4fa;
border-radius: 30px;
padding: 25px;
margin: 30px 0;
}
.contact-item {
display: flex;
align-items: center;
gap: 15px;
padding: 12px 0;
color: #1f3f60;
border-bottom: 1px solid #d9e6f5;
}
.contact-item:last-child {
border-bottom: none;
}
.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:not(:disabled) {
background: #1b3f6b;
transform: translateY(-2px);
}
.btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-outline {
background: transparent;
border: 2px solid #3b82f6;
color: #0b1c34;
}
.btn-outline:hover {
background: #eef4fa;
}
.applied-badge {
background: #10b981;
color: white;
padding: 16px 32px;
border-radius: 40px;
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 10px;
}
.stats {
display: flex;
gap: 20px;
color: #4f7092;
font-size: 14px;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #dee9f5;
}
/* 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;
}
.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) {
.vacancy-title {
font-size: 28px;
}
.vacancy-salary {
font-size: 24px;
}
.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="/vacancies" class="back-link"><i class="fas fa-arrow-left"></i> Назад к вакансиям</a>
<div id="vacancyDetail" class="vacancy-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="qrVacancyTitle"></span></h2>
<div class="qr-subtitle" id="qrVacancyUrl">yarmarka.rabota.today/vacancy/</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="logo" style="display: none;">
<i id="qrLogoIcon" class="fas fa-briefcase"></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="qrSalaryInfo"></div>
<div class="qr-stat-label">зарплата</div>
</div>
<div class="qr-stat-item">
<div class="qr-stat-value" id="qrCompanyName"></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="copyVacancyLink()">
<i class="fas fa-link"></i> Копировать ссылку
</button>
<button class="qr-action-btn secondary" onclick="shareVacancy()">
<i class="fas fa-share-alt"></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 currentVacancy = null;
let qrViewCount = 0;
const pathParts = window.location.pathname.split('/');
const vacancyId = 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" class="active">Вакансии</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> ${escapeHtml(firstName)} ${adminBadge}
</a>
`;
} else {
nav.innerHTML = `
<a href="/">Главная</a>
<a href="/vacancies" class="active">Вакансии</a>
<a href="/resumes">Резюме</a>
<a href="/login">Войти</a>
<a href="/register">Регистрация</a>
`;
}
}
async function loadVacancy() {
const token = localStorage.getItem('accessToken');
try {
const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
const response = await fetch(`${API_BASE_URL}/vacancies/${vacancyId}`, { headers });
if (!response.ok) throw new Error('Вакансия не найдена');
currentVacancy = await response.json();
renderVacancy(currentVacancy);
} catch (error) {
console.error('Error loading vacancy:', error);
document.getElementById('vacancyDetail').innerHTML = `
<div class="error-message">
<i class="fas fa-exclamation-circle"></i>
Вакансия не найдена или была удалена
</div>
<div style="text-align: center; margin-top: 20px;">
<a href="/vacancies" class="btn btn-primary">Вернуться к списку</a>
</div>
`;
}
}
function renderVacancy(vacancy) {
const container = document.getElementById('vacancyDetail');
const token = localStorage.getItem('accessToken');
const isApplied = vacancy.has_applied;
const canApply = token && currentUser && currentUser.role === 'employee' && !isApplied;
const companyId = vacancy.company_id || '';
const decodedTitle = decodeHtmlEntities(vacancy.title);
const decodedCompanyName = decodeHtmlEntities(vacancy.company_name || '');
container.innerHTML = `
<div class="vacancy-header">
<h1 class="vacancy-title">${escapeHtml(decodedTitle)}</h1>
<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>
${vacancy.company_name ? `
<a href="/company/${companyId}" class="vacancy-company-link" ${!companyId ? 'onclick="event.preventDefault(); showNotification(\'Страница компании в разработке\')"' : ''}>
<i class="fas fa-building"></i>
<span class="company-name">${escapeHtml(decodedCompanyName)}</span>
<span class="company-link-hint">
<i class="fas fa-external-link-alt"></i> перейти
</span>
</a>
` : ''}
<div class="vacancy-salary">${escapeHtml(vacancy.salary || 'Зарплата не указана')}</div>
${vacancy.tags && vacancy.tags.length > 0 ? `
<div class="vacancy-tags">
${vacancy.tags.map(t => `<span class="tag">${escapeHtml(t.name)}</span>`).join('')}
</div>
` : ''}
<h2 class="section-title">Описание вакансии</h2>
<div class="vacancy-description">${escapeHtml(vacancy.description || 'Описание отсутствует').replace(/\n/g, '<br>')}</div>
${vacancy.company_name ? `
<div class="company-info">
<div class="company-info-header">
<h3><i class="fas fa-building"></i> О компании</h3>
${companyId ? `<a href="/company/${companyId}" class="company-view-all"><i class="fas fa-external-link-alt"></i> Все вакансии компании</a>` : ''}
</div>
<div class="company-info-grid">
<div class="company-info-item">
<i class="fas fa-building"></i>
<div>
<div style="font-weight: 600;">${escapeHtml(decodedCompanyName)}</div>
${companyId ? `<a href="/company/${companyId}" style="font-size: 13px; color: #3b82f6;">перейти в профиль компании →</a>` : ''}
</div>
</div>
${vacancy.company_website ? `<div class="company-info-item"><i class="fas fa-globe"></i><a href="${escapeHtml(vacancy.company_website)}" target="_blank">${escapeHtml(vacancy.company_website)}</a></div>` : ''}
${vacancy.company_address ? `<div class="company-info-item"><i class="fas fa-map-marker-alt"></i><span>${escapeHtml(vacancy.company_address)}</span></div>` : ''}
</div>
${vacancy.company_description ? `
<div style="margin-top: 20px; color: #1f3f60; line-height: 1.6; padding: 15px; background: white; border-radius: 15px;">
<i class="fas fa-quote-left" style="color: #3b82f6; opacity: 0.5;"></i>
${escapeHtml(vacancy.company_description.substring(0, 200))}...
${companyId ? `<a href="/company/${companyId}" style="color: #3b82f6; margin-left: 5px;">читать полностью</a>` : ''}
</div>
` : ''}
</div>
` : ''}
<h2 class="section-title">Контактная информация</h2>
<div class="contact-info">
<div class="contact-item"><i class="fab fa-telegram"></i><span>${escapeHtml(vacancy.contact || vacancy.user_telegram || 'Контакт не указан')}</span></div>
${vacancy.company_email ? `<div class="contact-item"><i class="fas fa-envelope"></i><a href="mailto:${escapeHtml(vacancy.company_email)}">${escapeHtml(vacancy.company_email)}</a></div>` : ''}
${vacancy.company_phone ? `<div class="contact-item"><i class="fas fa-phone"></i><a href="tel:${escapeHtml(vacancy.company_phone)}">${escapeHtml(vacancy.company_phone)}</a></div>` : ''}
</div>
<div class="action-buttons">
${isApplied ? `<div class="applied-badge"><i class="fas fa-check-circle"></i> Вы уже откликнулись</div>` :
canApply ? `<button class="btn btn-primary" onclick="applyForVacancy()"><i class="fas fa-paper-plane"></i> Откликнуться</button>` :
token && currentUser && currentUser.role === 'employer' ? `<button class="btn btn-primary" disabled><i class="fas fa-ban"></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="shareVacancy()"><i class="fas fa-share-alt"></i> Поделиться</button>
</div>
<div class="stats">
<span><i class="fas fa-eye"></i> ${vacancy.views || 0} просмотров</span>
<span><i class="fas fa-calendar"></i> ${new Date(vacancy.created_at).toLocaleDateString()}</span>
</div>
`;
}
// QR Code functions
function openQRModal() {
if (!currentVacancy) return;
const decodedTitle = decodeHtmlEntities(currentVacancy.title);
const decodedCompanyName = decodeHtmlEntities(currentVacancy.company_name || '');
document.getElementById('qrVacancyTitle').textContent = decodedTitle;
const vacancyUrl = window.location.origin + '/vacancy/' + vacancyId;
document.getElementById('qrVacancyUrl').textContent = vacancyUrl.replace('https://', '').replace('http://', '');
document.getElementById('qrViewCount').textContent = ++qrViewCount;
document.getElementById('qrViewBadge').textContent = qrViewCount;
document.getElementById('qrSalaryInfo').textContent = currentVacancy.salary || 'з/п не указана';
document.getElementById('qrCompanyName').textContent = decodedCompanyName || '—';
generateQRCodeWithLogo(vacancyUrl, currentVacancy.company_logo);
document.getElementById('qrModal').classList.add('active');
}
function closeQRModal() {
document.getElementById('qrModal').classList.remove('active');
}
function generateQRCodeWithLogo(text, logoUrl) {
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 logoImg = document.getElementById('qrLogoImg');
const logoIcon = document.getElementById('qrLogoIcon');
if (logoOverlay) logoOverlay.style.display = 'flex';
if (logoUrl && logoImg) {
logoImg.src = logoUrl;
logoImg.style.display = 'block';
if (logoIcon) logoIcon.style.display = 'none';
logoImg.onerror = function() {
if (logoImg) logoImg.style.display = 'none';
if (logoIcon) { logoIcon.style.display = 'block'; logoIcon.className = 'fas fa-briefcase'; }
};
} else if (logoIcon) {
logoImg.style.display = 'none';
logoIcon.style.display = 'block';
logoIcon.className = 'fas fa-briefcase';
}
}
});
}
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 decodedTitle = decodeHtmlEntities(currentVacancy.title);
const filename = `vacancy_${decodedTitle.toLowerCase().replace(/[^a-zа-я0-9]/g, '_')}.png`;
link.download = filename;
link.href = combinedCanvas.toDataURL('image/png');
link.click();
showNotification('QR-код скачан', 'success');
}
function copyVacancyLink() {
const url = window.location.origin + '/vacancy/' + vacancyId;
navigator.clipboard.writeText(url).then(() => showNotification('Ссылка скопирована', 'success')).catch(() => showNotification('Ошибка копирования', 'error'));
}
function shareVacancy() {
const url = window.location.href;
if (navigator.share) { navigator.share({ title: document.title, url: url }).catch(() => copyVacancyLink()); }
else { copyVacancyLink(); }
}
function printQR() {
const canvas = document.getElementById('qrCanvas');
const decodedTitle = decodeHtmlEntities(currentVacancy.title);
const decodedCompany = decodeHtmlEntities(currentVacancy.company_name || '');
const printWindow = window.open('', '_blank');
printWindow.document.write(`
<html><head><title>QR-код вакансии</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(decodedTitle)}</h2>
<h3>${escapeHtml(decodedCompany)}</h3>
<img src="${canvas.toDataURL()}" />
<div class="url">${window.location.origin}/vacancy/${vacancyId}</div>
</body></html>
`);
printWindow.document.close();
printWindow.focus();
printWindow.print();
}
function shareQR(platform) {
const url = window.location.origin + '/vacancy/' + vacancyId;
const decodedTitle = decodeHtmlEntities(currentVacancy.title);
const text = `Вакансия: ${decodedTitle} на 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('Вакансия ' + decodedTitle)}&body=${encodeURIComponent(text + '\n\n' + url)}`;
if (shareUrl) window.open(shareUrl, '_blank');
}
async function applyForVacancy() {
const token = localStorage.getItem('accessToken');
if (!token) { redirectToLogin(); return; }
try {
const response = await fetch(`${API_BASE_URL}/vacancies/${vacancyId}/apply`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` } });
if (response.ok) { showNotification('Отклик отправлен! Работодатель свяжется с вами.', 'success'); loadVacancy(); }
else { const error = await response.json(); throw new Error(error.detail || 'Ошибка отправки'); }
} catch (error) { showNotification(error.message, 'error'); }
}
function saveToFavorites() {
const token = localStorage.getItem('accessToken');
if (!token) { if (confirm('Для добавления в избранное нужно войти в систему. Перейти на страницу входа?')) { window.location.href = '/login'; } return; }
showNotification('Вакансия добавлена в избранное', 'success');
}
function redirectToLogin() {
if (confirm('Для отклика нужно войти в систему. Перейти на страницу входа?')) { window.location.href = '/login'; }
}
window.onclick = function(event) {
const modal = document.getElementById('qrModal');
if (event.target === modal) modal.classList.remove('active');
};
checkAuth().then(() => loadVacancy());
</script>
</body>
</html>