Files
yarmarka/templates/user_profile.html
2026-03-19 19:29:30 +03:00

1732 lines
60 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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">
<!-- Подключаем библиотеку для генерации QR-кодов -->
<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: 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;
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;
}
.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;
}
.avatar img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
}
.profile-name {
font-size: 24px;
font-weight: 700;
color: #0b1c34;
margin-bottom: 5px;
}
.profile-role-badge {
display: inline-block;
padding: 6px 16px;
background: #eef4fa;
border-radius: 30px;
font-size: 14px;
font-weight: 600;
color: #1f3f60;
margin-bottom: 20px;
}
.profile-role-badge.employee {
background: #e0f2e0;
color: #166534;
}
.profile-role-badge.employer {
background: #dbeafe;
color: #1e40af;
}
.profile-role-badge.admin {
background: #fef3c7;
color: #92400e;
}
.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-item {
text-align: center;
}
.stat-value {
font-size: 24px;
font-weight: 700;
color: #0b1c34;
}
.stat-label {
font-size: 12px;
color: #4f7092;
}
.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;
}
.contact-item a {
color: #3b82f6;
text-decoration: none;
}
.contact-item a:hover {
text-decoration: underline;
}
.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;
}
.info-section {
margin-bottom: 30px;
}
.info-section h3 {
color: #0b1c34;
font-size: 20px;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
}
.info-section h3 i {
color: #3b82f6;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.info-card {
background: #f9fcff;
padding: 15px;
border-radius: 20px;
}
.info-card .label {
font-size: 12px;
color: #4f7092;
margin-bottom: 5px;
}
.info-card .value {
font-weight: 600;
color: #0b1c34;
}
/* Для резюме */
.resume-preview {
background: #f9fcff;
border-radius: 30px;
padding: 25px;
margin-bottom: 20px;
}
.resume-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 15px;
}
.resume-header h3 {
color: #0b1c34;
font-size: 22px;
}
.resume-salary {
font-size: 20px;
font-weight: 700;
color: #0f2b4f;
padding: 10px 15px;
background: white;
border-radius: 30px;
}
.resume-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 20px 0;
}
.tag {
background: #eef4fa;
padding: 6px 14px;
border-radius: 30px;
font-size: 13px;
color: #1f3f60;
}
.about-me {
background: white;
padding: 20px;
border-radius: 20px;
line-height: 1.6;
margin: 20px 0;
}
.experience-item, .education-item {
background: white;
border-radius: 20px;
padding: 20px;
margin-bottom: 15px;
border-left: 4px solid #3b82f6;
}
.item-header {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
flex-wrap: wrap;
}
.item-title {
font-weight: 700;
color: #0b1c34;
}
.item-subtitle {
color: #3b82f6;
margin-bottom: 5px;
}
.item-period {
color: #4f7092;
font-size: 13px;
}
.item-description {
margin-top: 10px;
padding: 10px;
background: #f9fcff;
border-radius: 15px;
font-size: 14px;
line-height: 1.6;
}
/* Для компании */
.company-info {
background: #f9fcff;
border-radius: 30px;
padding: 25px;
}
.company-header {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.company-logo {
width: 80px;
height: 80px;
background: #eef4fa;
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
color: #3b82f6;
}
.company-logo img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 20px;
}
.company-name {
font-size: 28px;
font-weight: 700;
color: #0b1c34;
}
.vacancies-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin-top: 20px;
}
.vacancy-card {
background: white;
border-radius: 20px;
padding: 20px;
cursor: pointer;
transition: 0.2s;
border: 1px solid #dee9f5;
}
.vacancy-card:hover {
transform: translateY(-3px);
box-shadow: 0 10px 30px rgba(0,20,40,0.1);
}
.vacancy-card h4 {
color: #0b1c34;
margin-bottom: 8px;
}
.vacancy-salary {
color: #3b82f6;
font-weight: 600;
margin: 10px 0;
}
.vacancy-footer {
display: flex;
justify-content: space-between;
margin-top: 10px;
font-size: 13px;
color: #4f7092;
}
.btn {
padding: 14px 28px;
border-radius: 40px;
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;
}
/* Стили для QR-кнопки */
.qr-button {
width: 44px;
height: 44px;
background: linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%);
border-radius: 22px;
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: 20px;
position: relative;
overflow: hidden;
border: none;
margin-left: 10px;
}
.qr-button:hover {
transform: translateY(-3px) scale(1.05);
box-shadow: 0 8px 20px rgba(59,130,246,0.4);
}
.qr-button:active {
transform: scale(0.95);
}
.qr-button i {
font-size: 20px;
}
.qr-view-count {
position: absolute;
top: -5px;
right: -5px;
background: #ef4444;
color: white;
font-size: 10px;
font-weight: 600;
padding: 2px 5px;
border-radius: 12px;
min-width: 18px;
text-align: center;
box-shadow: 0 2px 4px rgba(239,68,68,0.3);
}
/* Модальное окно для QR */
.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;
}
@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) {
.profile-grid {
grid-template-columns: 1fr;
}
.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="javascript:history.back()" class="back-link"><i class="fas fa-arrow-left"></i> Назад</a>
<div id="profileContent">
<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="qrUserName"></span></h2>
<div class="qr-subtitle" id="qrUserUrl">yarmarka.rabota.today/user/</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="qrUserRole"></div>
<div class="qr-stat-label">роль</div>
</div>
<div class="qr-stat-item">
<div class="qr-stat-value" id="qrUserSince"></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="copyProfileLink()">
<i class="fas fa-link"></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 currentProfileUser = null;
let qrViewCount = 0;
// Получаем ID пользователя из URL
const pathParts = window.location.pathname.split('/');
const userId = pathParts[pathParts.length - 1];
// Функции для работы с 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;");
}
function showNotification(message, type = 'info') {
const notification = document.getElementById('notification');
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();
console.log('✅ Текущий пользователь:', currentUser);
} 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">Резюме</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">Резюме</a>
<a href="/login">Войти</a>
<a href="/register">Регистрация</a>
`;
}
}
// Загрузка профиля пользователя
async function loadUserProfile() {
try {
console.log(`📥 Загрузка профиля пользователя ID: ${userId}`);
const token = localStorage.getItem('accessToken');
const headers = {};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(`${API_BASE_URL}/users/${userId}`, {
headers: headers
});
if (!response.ok) {
throw new Error('Пользователь не найден');
}
const user = await response.json();
console.log('✅ Профиль загружен:', user);
// Сохраняем данные пользователя для QR
currentProfileUser = user;
// Загружаем резюме если это соискатель
let resume = null;
if (user.role === 'employee') {
try {
const resumeResponse = await fetch(`${API_BASE_URL}/users/${userId}/resume`);
if (resumeResponse.ok) {
resume = await resumeResponse.json();
console.log('✅ Резюме загружено');
} else if (resumeResponse.status === 404) {
console.log(' У пользователя нет резюме');
resume = null;
}
} catch (resumeError) {
console.error('❌ Ошибка при запросе резюме:', resumeError);
}
}
// Загружаем компанию если это работодатель
let company = null;
let vacancies = [];
if (user.role === 'employer') {
try {
const companyResponse = await fetch(`${API_BASE_URL}/users/${userId}/company`);
if (companyResponse.ok) {
company = await companyResponse.json();
console.log('✅ Компания загружена');
}
} catch (companyError) {
console.error('❌ Ошибка при запросе компании:', companyError);
}
try {
const vacanciesResponse = await fetch(`${API_BASE_URL}/users/${userId}/vacancies`);
if (vacanciesResponse.ok) {
vacancies = await vacanciesResponse.json();
console.log(`✅ Загружено ${vacancies.length} вакансий`);
}
} catch (vacanciesError) {
console.error('❌ Ошибка при запросе вакансий:', vacanciesError);
}
}
renderProfile(user, resume, company, vacancies);
} catch (error) {
console.error('❌ Ошибка загрузки профиля:', error);
document.getElementById('profileContent').innerHTML = `
<div class="error-message">
<i class="fas fa-exclamation-circle"></i>
Пользователь не найден
</div>
`;
}
}
// Отображение профиля
function renderProfile(user, resume, company, vacancies) {
const container = document.getElementById('profileContent');
const isOwnProfile = currentUser && currentUser.id === user.id;
const canSeeContacts = isOwnProfile || (currentUser && currentUser.is_admin) || !user.contacts_hidden;
// Определяем роль
let roleText = '';
let roleClass = '';
if (user.role === 'employee') {
roleText = 'Соискатель';
roleClass = 'employee';
} else if (user.role === 'employer') {
roleText = 'Работодатель';
roleClass = 'employer';
} else if (user.role === 'admin') {
roleText = 'Администратор';
roleClass = 'admin';
}
// Статистика
let stats = '';
if (user.role === 'employee') {
if (resume) {
stats = `
<div class="profile-stats">
<div class="stat-item">
<div class="stat-value">${resume.work_experience?.length || 0}</div>
<div class="stat-label">мест работы</div>
</div>
<div class="stat-item">
<div class="stat-value">${resume.views || 0}</div>
<div class="stat-label">просмотров</div>
</div>
</div>
`;
} else {
stats = `
<div class="profile-stats">
<div class="stat-item">
<div class="stat-value">0</div>
<div class="stat-label">мест работы</div>
</div>
<div class="stat-item">
<div class="stat-value">0</div>
<div class="stat-label">просмотров</div>
</div>
</div>
`;
}
} else if (user.role === 'employer') {
stats = `
<div class="profile-stats">
<div class="stat-item">
<div class="stat-value">${vacancies.length}</div>
<div class="stat-label">вакансий</div>
</div>
<div class="stat-item">
<div class="stat-value">${vacancies.filter(v => v.is_active).length}</div>
<div class="stat-label">активных</div>
</div>
</div>
`;
}
// Контакты
let contacts = '';
if (canSeeContacts) {
contacts = `
<div class="contact-item">
<i class="fas fa-envelope"></i>
<span>${escapeHtml(user.email) || '—'}</span>
</div>
<div class="contact-item">
<i class="fas fa-phone"></i>
<span>${escapeHtml(user.phone) || '—'}</span>
</div>
<div class="contact-item">
<i class="fab fa-telegram"></i>
<span>${escapeHtml(user.telegram) || '—'}</span>
</div>
`;
} else {
contacts = `
<div class="contact-item">
<i class="fas fa-lock"></i>
<span>Контакты скрыты</span>
</div>
<div class="contact-item" style="font-size: 12px; color: #4f7092;">
<i class="fas fa-info-circle"></i>
<span>Авторизуйтесь для просмотра контактов</span>
</div>
`;
}
// Основной контент в зависимости от роли
let contentTabs = '';
let mainContent = '';
if (user.role === 'employee') {
if (resume) {
// У соискателя есть резюме
contentTabs = `
<div class="content-tab active" onclick="switchTab('main')">Главная</div>
<div class="content-tab" onclick="switchTab('resume')">Резюме</div>
`;
mainContent = `
<div class="content-pane active" id="mainPane">
<div class="info-section">
<h3><i class="fas fa-info-circle"></i> О себе</h3>
<div class="about-me">${escapeHtml(resume.about_me || 'Информация не заполнена')}</div>
</div>
</div>
<div class="content-pane" id="resumePane">
<div class="resume-preview">
<div class="resume-header">
<h3>${escapeHtml(resume.desired_position || 'Желаемая должность не указана')}</h3>
<div class="resume-salary">${escapeHtml(resume.desired_salary || 'з/п не указана')}</div>
</div>
<div class="resume-tags">
${(resume.tags || []).map(t => `<span class="tag">${escapeHtml(t.name)}</span>`).join('')}
</div>
<h4 style="margin: 20px 0 10px;">Опыт работы</h4>
${(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>
${exp.description ? `<div class="item-description">${escapeHtml(exp.description)}</div>` : ''}
</div>
`).join('') :
'<p style="color: #4f7092; padding: 20px; text-align: center;">Опыт работы не указан</p>'
}
<h4 style="margin: 20px 0 10px;">Образование</h4>
${(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; padding: 20px; text-align: center;">Образование не указано</p>'
}
</div>
</div>
`;
} else {
// У соискателя нет резюме
contentTabs = `
<div class="content-tab active" onclick="switchTab('main')">Главная</div>
`;
mainContent = `
<div class="content-pane active" id="mainPane">
<div class="info-section" style="text-align: center; padding: 60px 20px;">
<i class="fas fa-file-alt" style="font-size: 64px; color: #3b82f6; margin-bottom: 20px;"></i>
<h3 style="color: #0b1c34; margin-bottom: 10px;">Резюме не заполнено</h3>
<p style="color: #4f7092;">Пользователь ещё не создал резюме</p>
</div>
</div>
`;
}
} else if (user.role === 'employer') {
if (company) {
// У работодателя есть компания
contentTabs = `
<div class="content-tab active" onclick="switchTab('main')">Главная</div>
<div class="content-tab" onclick="switchTab('company')">Компания</div>
<div class="content-tab" onclick="switchTab('vacancies')">Вакансии</div>
`;
mainContent = `
<div class="content-pane active" id="mainPane">
<div class="info-section">
<h3><i class="fas fa-building"></i> О компании</h3>
<div class="about-me">${escapeHtml(company?.description || 'Информация о компании не заполнена')}</div>
</div>
</div>
<div class="content-pane" id="companyPane">
<div class="company-info">
<div class="company-header">
<div class="company-logo">
${company?.logo ?
`<img src="${escapeHtml(company.logo)}" alt="${escapeHtml(company.name)}">` :
`<i class="fas fa-building"></i>`
}
</div>
<div>
<div class="company-name">${escapeHtml(company?.name || 'Название не указано')}</div>
${company?.website ?
`<a href="${escapeHtml(company.website)}" target="_blank">${escapeHtml(company.website)}</a>` :
''
}
</div>
</div>
<div class="info-grid" style="margin-top: 20px;">
${company?.address ? `
<div class="info-card">
<div class="label">Адрес</div>
<div class="value">${escapeHtml(company.address)}</div>
</div>
` : ''}
${company?.phone ? `
<div class="info-card">
<div class="label">Телефон</div>
<div class="value">${escapeHtml(company.phone)}</div>
</div>
` : ''}
${company?.email ? `
<div class="info-card">
<div class="label">Email</div>
<div class="value">${escapeHtml(company.email)}</div>
</div>
` : ''}
</div>
</div>
</div>
<div class="content-pane" id="vacanciesPane">
<h3 style="margin-bottom: 20px;">Вакансии компании</h3>
${vacancies.length > 0 ?
`<div class="vacancies-grid">
${vacancies.map(v => `
<div class="vacancy-card" onclick="window.location.href='/vacancy/${v.id}'">
<h4>${escapeHtml(v.title)}</h4>
<div class="vacancy-salary">${escapeHtml(v.salary || 'з/п не указана')}</div>
<div class="vacancy-footer">
<span><i class="fas fa-eye"></i> ${v.views || 0}</span>
<span>${v.is_active ? 'Активна' : 'Неактивна'}</span>
</div>
</div>
`).join('')}
</div>` :
'<p style="color: #4f7092; text-align: center; padding: 40px;">Нет активных вакансий</p>'
}
</div>
`;
} else {
// У работодателя нет компании
contentTabs = `
<div class="content-tab active" onclick="switchTab('main')">Главная</div>
`;
mainContent = `
<div class="content-pane active" id="mainPane">
<div class="info-section" style="text-align: center; padding: 60px 20px;">
<i class="fas fa-building" style="font-size: 64px; color: #3b82f6; margin-bottom: 20px;"></i>
<h3 style="color: #0b1c34; margin-bottom: 10px;">Компания не зарегистрирована</h3>
<p style="color: #4f7092;">Пользователь ещё не добавил информацию о компании</p>
</div>
</div>
`;
}
}
container.innerHTML = `
<div class="profile-grid">
<!-- Боковая панель -->
<div class="profile-sidebar">
<div class="avatar">
<i class="fas fa-user-circle"></i>
</div>
<div style="display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 10px; flex-wrap: wrap;">
<div class="profile-name">${escapeHtml(user.full_name)}</div>
<button class="qr-button" onclick="openQRModal()" title="QR-код профиля">
<i class="fas fa-qrcode"></i>
</button>
</div>
<div class="profile-role-badge ${roleClass}">${roleText}</div>
${stats}
<div class="contact-info">
${contacts}
</div>
${isOwnProfile ? `
<button class="btn btn-outline" style="width: 100%; margin-top: 20px;" onclick="window.location.href='/profile'">
<i class="fas fa-edit"></i> Редактировать профиль
</button>
` : ''}
</div>
<!-- Основной контент -->
<div class="profile-content">
<div class="content-tabs" id="contentTabs">
${contentTabs}
</div>
${mainContent}
</div>
</div>
`;
}
// Переключение табов
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 === 'resume') {
tabs[1]?.classList.add('active');
document.getElementById('resumePane').classList.add('active');
} else if (tab === 'company') {
tabs[1]?.classList.add('active');
document.getElementById('companyPane').classList.add('active');
} else if (tab === 'vacancies') {
tabs[2]?.classList.add('active');
document.getElementById('vacanciesPane').classList.add('active');
}
}
// ========== ФУНКЦИИ ДЛЯ QR-КОДА ==========
// Открыть модальное окно с QR
function openQRModal() {
console.log('📱 Попытка открыть QR-модальное окно');
if (!currentProfileUser) {
console.error('❌ Данные пользователя не загружены');
showNotification('Данные пользователя не загружены', 'error');
return;
}
// Проверяем наличие модального окна
const modal = document.getElementById('qrModal');
if (!modal) {
console.error('❌ Модальное окно с id "qrModal" не найдено в DOM');
showNotification('Ошибка: модальное окно не найдено', 'error');
return;
}
// Получаем элементы
const qrUserName = document.getElementById('qrUserName');
const qrUserUrl = document.getElementById('qrUserUrl');
const qrViewCountElement = document.getElementById('qrViewCount'); // Переименовал, чтобы не конфликтовать
const qrUserRole = document.getElementById('qrUserRole');
const qrUserSince = document.getElementById('qrUserSince');
const qrCanvas = document.getElementById('qrCanvas');
// Проверяем наличие критически важных элементов
if (!qrUserName || !qrUserUrl || !qrViewCountElement || !qrUserRole || !qrUserSince || !qrCanvas) {
console.error('❌ Критические элементы модального окна не найдены');
console.log('qrUserName:', qrUserName);
console.log('qrUserUrl:', qrUserUrl);
console.log('qrViewCountElement:', qrViewCountElement);
console.log('qrUserRole:', qrUserRole);
console.log('qrUserSince:', qrUserSince);
console.log('qrCanvas:', qrCanvas);
showNotification('Ошибка отображения QR-кода', 'error');
return;
}
// Устанавливаем имя пользователя
qrUserName.textContent = escapeHtml(currentProfileUser.full_name);
// Формируем URL профиля
const profileUrl = window.location.origin + '/user/' + currentProfileUser.id;
qrUserUrl.textContent = profileUrl.replace('https://', '').replace('http://', '');
// Обновляем счетчик просмотров - используем отдельную переменную
if (typeof window.qrViewCounter === 'undefined') {
window.qrViewCounter = 0;
}
window.qrViewCounter++;
qrViewCountElement.textContent = window.qrViewCounter;
// Роль пользователя
let roleText = '';
if (currentProfileUser.role === 'employee') roleText = '👤 Соискатель';
else if (currentProfileUser.role === 'employer') roleText = '🏢 Работодатель';
else if (currentProfileUser.role === 'admin') roleText = '👑 Админ';
else roleText = '👤 Пользователь';
qrUserRole.textContent = roleText;
// Дата регистрации
if (currentProfileUser.created_at) {
try {
const date = new Date(currentProfileUser.created_at);
const now = new Date();
const months = Math.floor((now - date) / (1000 * 60 * 60 * 24 * 30));
qrUserSince.textContent = (months > 0 ? months : '< 1') + ' мес';
} catch (e) {
console.error('Ошибка при вычислении даты:', e);
qrUserSince.textContent = '—';
}
} else {
qrUserSince.textContent = '—';
}
// Генерируем QR-код
try {
generateQRCodeWithLogo(profileUrl);
} catch (e) {
console.error('Ошибка генерации QR-кода:', e);
showNotification('Ошибка генерации QR-кода', 'error');
}
// Показываем модальное окно
modal.classList.add('active');
console.log('✅ QR-модальное окно открыто, счетчик:', window.qrViewCounter);
}
// Закрыть модальное окно
function closeQRModal() {
const modal = document.getElementById('qrModal');
if (modal) {
modal.classList.remove('active');
}
}
// Генерация QR-кода с иконкой пользователя
function generateQRCodeWithLogo(text) {
const canvas = document.getElementById('qrCanvas');
const logoOverlay = document.getElementById('qrLogoOverlay');
const logoIcon = document.getElementById('qrLogoIcon');
if (!canvas) {
console.error('❌ Canvas для QR-кода не найден');
return;
}
// Настройки QR-кода
const options = {
width: 250,
height: 250,
color: {
dark: '#0b1c34',
light: '#ffffff'
},
errorCorrectionLevel: 'H'
};
QRCode.toCanvas(canvas, text, options, function(error) {
if (error) {
console.error('Error generating QR code:', error);
showNotification('Ошибка генерации QR-кода', 'error');
} else {
console.log('✅ QR code generated successfully');
// Показываем оверлей с иконкой пользователя
if (logoOverlay) {
logoOverlay.style.display = 'flex';
}
if (logoIcon) {
logoIcon.style.display = 'block';
logoIcon.className = 'fas fa-user';
}
}
});
}
// Скачать QR-код
function downloadQR() {
const canvas = document.getElementById('qrCanvas');
// Создаем временный canvas для объединения QR и иконки
const combinedCanvas = document.createElement('canvas');
combinedCanvas.width = canvas.width;
combinedCanvas.height = canvas.height;
const ctx = combinedCanvas.getContext('2d');
// Рисуем QR-код
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); // Unicode для иконки user
// Скачиваем
const link = document.createElement('a');
const filename = `profile_${currentProfileUser.id}.png`;
link.download = filename;
link.href = combinedCanvas.toDataURL('image/png');
link.click();
showNotification('QR-код скачан', 'success');
}
// Копировать ссылку на профиль
function copyProfileLink() {
const url = window.location.origin + '/user/' + currentProfileUser.id;
navigator.clipboard.writeText(url).then(() => {
showNotification('Ссылка скопирована', 'success');
}).catch(() => {
showNotification('Ошибка копирования', 'error');
});
}
// Распечатать QR
function printQR() {
const canvas = document.getElementById('qrCanvas');
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; word-break: break-word; }
img { max-width: 300px; box-shadow: 0 10px 30px rgba(0,0,0,0.1); border-radius: 20px; }
.url { color: #4f7092; margin-top: 20px; word-break: break-all; text-align: center; }
.role { margin-bottom: 20px; color: #3b82f6; }
</style>
</head>
<body>
<h2>${escapeHtml(currentProfileUser.full_name)}</h2>
<div class="role">${currentProfileUser.role === 'employee' ? '👤 Соискатель' :
currentProfileUser.role === 'employer' ? '🏢 Работодатель' :
'👑 Администратор'}</div>
<img src="${canvas.toDataURL()}" />
<div class="url">${window.location.origin}/user/${currentProfileUser.id}</div>
</body>
</html>
`);
printWindow.document.close();
printWindow.focus();
printWindow.print();
}
// Поделиться QR в соцсетях
function shareQR(platform) {
const url = window.location.origin + '/user/' + currentProfileUser.id;
const text = `Профиль пользователя ${currentProfileUser.full_name} на Rabota.Today`;
let shareUrl = '';
switch(platform) {
case 'whatsapp':
shareUrl = `https://wa.me/?text=${encodeURIComponent(text + ' ' + url)}`;
break;
case 'telegram':
shareUrl = `https://t.me/share/url?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`;
break;
case 'email':
shareUrl = `mailto:?subject=${encodeURIComponent('Профиль ' + currentProfileUser.full_name)}&body=${encodeURIComponent(text + '\n\n' + url)}`;
break;
}
if (shareUrl) {
window.open(shareUrl, '_blank');
}
}
// Закрытие модального окна по клику вне
window.onclick = function(event) {
const modal = document.getElementById('qrModal');
if (event.target === modal) {
modal.classList.remove('active');
}
};
// Инициализация
checkAuth().then(() => {
loadUserProfile();
});
</script>
</body>
</html>