hr-assistant/index.html

723 lines
30 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">
<title>HR Ассистент — контроль поручений</title>
<style>
:root {
--ink: #0F1218;
--cyan: #00E5FF;
--cyan-50: #E8FCFF;
--white: #fff;
--gray-500: #5B6573;
--gray-100: #F2F4F7;
--gray-200: #E4E7EC;
--green: #10B981;
--green-bg: #ECFDF5;
--amber: #F59E0B;
--amber-bg: #FFFBEB;
--red: #EF4444;
--red-bg: #FEF2F2;
--indigo: #4F46E5;
--indigo-bg: #EEF2FF;
--radius: 12px;
--shadow: 0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04);
--shadow-lg: 0 4px 24px rgba(0,0,0,.08);
}
* { box-sizing: border-box; margin: 0; padding: 0 }
body {
font: 16px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
color: var(--ink);
background: var(--gray-100);
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
}
/* NAV */
.nav {
position: sticky; top: 0; z-index: 100;
background: var(--white);
border-bottom: 1px solid var(--gray-200);
backdrop-filter: blur(12px);
}
.nav .container {
display: flex; align-items: center; justify-content: space-between;
height: 60px;
}
.nav-logo {
font-size: 18px; font-weight: 700;
display: flex; align-items: center; gap: 10px;
text-decoration: none; color: var(--ink);
}
.nav-logo .dot {
width: 32px; height: 32px;
background: var(--cyan);
border-radius: 8px;
display: flex; align-items: center; justify-content: center;
font-size: 16px;
}
.nav-links { display: flex; gap: 8px; flex-wrap: wrap }
.nav-links a {
text-decoration: none; color: var(--gray-500);
font-size: 14px; font-weight: 500;
padding: 6px 14px; border-radius: 8px;
transition: all .15s;
}
.nav-links a:hover, .nav-links a.active { background: var(--gray-100); color: var(--ink) }
/* HERO / STATS */
.hero { padding: 48px 0 32px }
.hero h1 { font-size: 36px; font-weight: 800; line-height: 1.2; margin-bottom: 8px }
.hero .sub { color: var(--gray-500); font-size: 17px; margin-bottom: 36px }
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px }
.stat-card {
background: var(--white); border-radius: var(--radius);
padding: 24px; box-shadow: var(--shadow);
display: flex; flex-direction: column; gap: 8px;
}
.stat-card .label { font-size: 13px; font-weight: 600; color: var(--gray-500); text-transform: uppercase; letter-spacing: .4px }
.stat-card .value { font-size: 36px; font-weight: 800 }
.stat-card .dot-indicator {
display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px;
}
/* SECTION HEADER */
.section-header {
display: flex; align-items: center; justify-content: space-between;
margin: 56px 0 24px; flex-wrap: wrap; gap: 12px;
}
.section-header h2 { font-size: 24px; font-weight: 700 }
.search-bar {
display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
}
.search-bar input {
padding: 10px 16px; border: 1px solid var(--gray-200);
border-radius: 8px; font-size: 14px; outline: none;
min-width: 220px; background: var(--white);
}
.search-bar input:focus { border-color: var(--cyan) }
.search-bar select {
padding: 10px 14px; border: 1px solid var(--gray-200);
border-radius: 8px; font-size: 14px; outline: none;
background: var(--white); cursor: pointer;
}
/* BUTTONS */
.btn {
display: inline-flex; align-items: center; gap: 8px;
padding: 10px 20px; border-radius: 8px;
font-size: 14px; font-weight: 600; text-decoration: none;
border: none; cursor: pointer;
transition: all .15s;
}
.btn-primary { background: var(--cyan); color: var(--ink) }
.btn-primary:hover { background: #1be5ff }
.btn-outline { background: var(--white); color: var(--ink); border: 1.5px solid var(--gray-200) }
.btn-outline:hover { border-color: var(--gray-500) }
.btn-sm { padding: 6px 14px; font-size: 13px }
.btn-danger { background: var(--red-bg); color: var(--red) }
.btn-danger:hover { background: #FEE2E2 }
/* COMPANIES GRID */
.companies-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
}
.company-card {
background: var(--white); border-radius: var(--radius);
padding: 24px; box-shadow: var(--shadow);
transition: box-shadow .2s; cursor: pointer;
border: 2px solid transparent;
position: relative;
}
.company-card:hover { box-shadow: var(--shadow-lg) }
.company-card.selected { border-color: var(--cyan) }
.company-card .company-name {
font-size: 17px; font-weight: 700; margin-bottom: 4px;
display: flex; align-items: center; gap: 8px;
}
.company-card .company-avatar {
width: 40px; height: 40px; border-radius: 10px;
display: flex; align-items: center; justify-content: center;
font-size: 18px; font-weight: 700; color: var(--white);
flex-shrink: 0;
}
.company-avatar.g1 { background: var(--indigo) }
.company-avatar.g2 { background: #0891B2 }
.company-avatar.g3 { background: #D946EF }
.company-avatar.g4 { background: #EA580C }
.company-avatar.g5 { background: #16A34A }
.company-card .task-counts {
display: flex; gap: 12px; margin-top: 12px; font-size: 13px;
}
.company-card .task-counts span { display: flex; align-items: center; gap: 4px }
.company-card .badge {
display: inline-block; padding: 3px 10px; border-radius: 20px;
font-size: 12px; font-weight: 600;
}
.company-card .actions { margin-top: 16px; display: flex; gap: 8px }
/* TASK TABLE */
.task-table-wrap { overflow-x: auto }
.task-table {
width: 100%; border-collapse: collapse;
background: var(--white); border-radius: var(--radius);
box-shadow: var(--shadow); overflow: hidden;
}
.task-table th {
text-align: left; padding: 14px 16px;
font-size: 12px; font-weight: 700; color: var(--gray-500);
text-transform: uppercase; letter-spacing: .5px;
border-bottom: 1px solid var(--gray-200);
background: var(--gray-100);
white-space: nowrap;
}
.task-table td {
padding: 14px 16px; font-size: 14px;
border-bottom: 1px solid var(--gray-100);
white-space: nowrap;
}
.task-table tr:hover td { background: var(--cyan-50) }
.task-table tr:last-child td { border-bottom: none }
.status-pill {
display: inline-block; padding: 4px 12px; border-radius: 20px;
font-size: 12px; font-weight: 600;
}
.status-pending { background: var(--amber-bg); color: var(--amber) }
.status-progress { background: var(--indigo-bg); color: var(--indigo) }
.status-done { background: var(--green-bg); color: var(--green) }
.status-overdue { background: var(--red-bg); color: var(--red) }
.priority-dot {
width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 6px;
}
.priority-high { background: var(--red) }
.priority-mid { background: var(--amber) }
.priority-low { background: var(--gray-200) }
/* FAQ */
.faq-item {
background: var(--white); border-radius: var(--radius);
box-shadow: var(--shadow); margin-bottom: 8px; overflow: hidden;
}
.faq-q {
padding: 18px 24px; cursor: pointer;
display: flex; align-items: center; justify-content: space-between;
font-weight: 600; font-size: 16px;
user-select: none;
}
.faq-q:hover { background: var(--gray-100) }
.faq-q .arrow { transition: transform .2s; font-size: 12px; color: var(--gray-500) }
.faq-item.open .faq-q .arrow { transform: rotate(180deg) }
.faq-a {
padding: 0 24px 18px; font-size: 15px; color: var(--gray-500);
line-height: 1.7; display: none;
}
.faq-item.open .faq-a { display: block }
/* MODAL */
.modal-overlay {
position: fixed; inset: 0; z-index: 200;
background: rgba(15,18,24,.5);
backdrop-filter: blur(4px);
display: flex; align-items: center; justify-content: center;
display: none;
}
.modal-overlay.open { display: flex }
.modal {
background: var(--white); border-radius: 16px;
padding: 32px; max-width: 500px; width: 90%;
box-shadow: var(--shadow-lg); max-height: 85vh; overflow-y: auto;
}
.modal h3 { font-size: 20px; margin-bottom: 20px }
.modal label {
display: block; font-size: 13px; font-weight: 600;
color: var(--gray-500); margin-bottom: 4px; margin-top: 16px;
}
.modal input, .modal select, .modal textarea {
width: 100%; padding: 10px 14px; border: 1px solid var(--gray-200);
border-radius: 8px; font-size: 14px; outline: none; font-family: inherit;
}
.modal input:focus, .modal select:focus, .modal textarea:focus { border-color: var(--cyan) }
.modal textarea { resize: vertical; min-height: 80px }
.modal .btn-row { display: flex; gap: 10px; margin-top: 24px; justify-content: flex-end }
/* EMPTY */
.empty { text-align: center; padding: 60px 20px; color: var(--gray-500) }
.empty .icon { font-size: 48px; margin-bottom: 12px }
/* FOOTER */
.footer {
text-align: center; padding: 32px 24px;
color: var(--gray-500); font-size: 13px;
border-top: 1px solid var(--gray-200); margin-top: 64px;
}
/* TABS */
.tabs { display: flex; gap: 4px; margin-bottom: 24px; background: var(--white); border-radius: 10px; padding: 4px; box-shadow: var(--shadow); width: fit-content }
.tab-btn {
padding: 10px 20px; border-radius: 8px; border: none;
background: transparent; font-size: 14px; font-weight: 600;
cursor: pointer; color: var(--gray-500); transition: all .15s;
}
.tab-btn.active { background: var(--ink); color: var(--white) }
.tab-content { display: none }
.tab-content.active { display: block }
@media (max-width: 768px) {
.stats { grid-template-columns: repeat(2, 1fr) }
.hero h1 { font-size: 28px }
.companies-grid { grid-template-columns: 1fr }
.nav-links { display: none }
}
@media (max-width: 480px) {
.stats { grid-template-columns: 1fr }
.section-header { flex-direction: column; align-items: flex-start }
}
</style>
</head>
<body>
<nav class="nav">
<div class="container">
<a href="#" class="nav-logo">
<div class="dot">📋</div>
HR Ассистент
</a>
<div class="nav-links">
<a href="#companies" class="active">Компании</a>
<a href="#tasks">Поручения</a>
<a href="#faq">HR FAQ</a>
</div>
</div>
</nav>
<section class="hero">
<div class="container">
<h1>Контроль поручений портфельным компаниям</h1>
<p class="sub">Отслеживайте задачи по всем портфельным компаниям АО «Самрук-Қазына» в одном окне</p>
<div class="stats" id="stats"></div>
</div>
</section>
<div class="container">
<!-- COMPANIES SECTION -->
<div id="companies">
<div class="section-header">
<h2>Портфельные компании</h2>
<div class="search-bar">
<input type="text" id="companySearch" placeholder="Поиск по названию..." oninput="renderCompanies()">
<button class="btn btn-primary" onclick="openModal('company')">+ Добавить компанию</button>
<button class="btn btn-outline btn-sm" onclick="if(confirm('Сбросить все данные к стандартным?')){localStorage.clear();location.reload()}" title="Сбросить данные">Сброс</button>
</div>
</div>
<div class="companies-grid" id="companiesGrid"></div>
</div>
<!-- TASKS SECTION -->
<div id="tasks" style="margin-top:56px">
<div class="section-header">
<h2>Поручения</h2>
<div class="search-bar">
<input type="text" id="taskSearch" placeholder="Поиск поручения..." oninput="renderTasks()">
<select id="taskFilterCompany" onchange="renderTasks()">
<option value="">Все компании</option>
</select>
<select id="taskFilterStatus" onchange="renderTasks()">
<option value="">Все статусы</option>
<option value="pending">Ожидает</option>
<option value="progress">В работе</option>
<option value="done">Готово</option>
<option value="overdue">Просрочено</option>
</select>
<button class="btn btn-primary" onclick="openModal('task')">+ Добавить поручение</button>
</div>
</div>
<div class="task-table-wrap" id="taskTableWrap"></div>
</div>
<!-- FAQ -->
<div id="faq" style="margin-top:56px">
<div class="section-header"><h2>HR FAQ</h2></div>
<div id="faqList"></div>
</div>
</div>
<footer class="footer">
HR Ассистент — инструмент для контроля поручений &copy; 2026
</footer>
<!-- MODALS -->
<div class="modal-overlay" id="modalOverlay" onclick="if(event.target===this)closeModal()">
<div class="modal" id="modalContent"></div>
</div>
<script>
// ====== DATA MODEL (localStorage) ======
const defaultCompanies = [
{ id: 'c1', name: 'АО «НК «КазМунайГаз»', color: 'g1' },
{ id: 'c2', name: 'АО «НК «ҚТЖ»', color: 'g2' },
{ id: 'c3', name: 'АО «НАК «Казатомпром»', color: 'g3' },
{ id: 'c4', name: 'АО «Самрук-Энерго»', color: 'g4' },
{ id: 'c5', name: 'АО «KEGOC»', color: 'g5' },
{ id: 'c6', name: 'АО «Казпочта»', color: 'g1' },
{ id: 'c7', name: 'АО «Эйр Астана»', color: 'g2' },
{ id: 'c8', name: 'АО «НК «QazaqGaz»', color: 'g3' },
{ id: 'c9', name: 'АО «Тау-Кен Самрук»', color: 'g4' },
{ id: 'c10', name: 'АО «Казахтелеком»', color: 'g5' },
{ id: 'c11', name: 'АО «НК «Kazakhstan Engineering»', color: 'g1' },
{ id: 'c12', name: 'ТОО «ОХК»', color: 'g2' },
{ id: 'c13', name: 'АО «КазАвтоЖол»', color: 'g3' },
{ id: 'c14', name: 'АО «Qazaq Air»', color: 'g4' },
{ id: 'c15', name: 'ТОО «Samruk-Kazyna Construction»', color: 'g5' },
{ id: 'c16', name: 'ТОО «Samruk-Kazyna Trust»', color: 'g1' },
{ id: 'c17', name: 'АО «Самрук-Қазына Контракт»', color: 'g2' },
{ id: 'c18', name: 'АО «KTZ Express»', color: 'g3' },
];
const defaultTasks = [
{ id: 't1', companyId: 'c1', title: 'Подготовить отчёт по охране труда за Q1', status: 'pending', deadline: '2026-06-20', priority: 'high' },
{ id: 't2', companyId: 'c2', title: 'Обновить штатное расписание', status: 'progress', deadline: '2026-06-25', priority: 'mid' },
{ id: 't3', companyId: 'c3', title: 'Согласовать KPI руководителей', status: 'done', deadline: '2026-06-01', priority: 'high' },
{ id: 't4', companyId: 'c1', title: 'Провести аудит условий труда', status: 'overdue', deadline: '2026-05-15', priority: 'high' },
{ id: 't5', companyId: 'c4', title: 'Организовать обучение по ТБ', status: 'pending', deadline: '2026-07-01', priority: 'mid' },
{ id: 't6', companyId: 'c5', title: 'Собрать данные по текучести кадров', status: 'progress', deadline: '2026-06-18', priority: 'low' },
{ id: 't7', companyId: 'c6', title: 'Проверить трудовые договоры', status: 'pending', deadline: '2026-06-30', priority: 'mid' },
{ id: 't8', companyId: 'c2', title: 'Запустить программу наставничества', status: 'progress', deadline: '2026-06-10', priority: 'high' },
{ id: 't9', companyId: 'c8', title: 'Внедрить систему оценки персонала', status: 'overdue', deadline: '2026-04-20', priority: 'high' },
{ id: 't10', companyId: 'c9', title: 'Актуализировать должностные инструкции', status: 'pending', deadline: '2026-07-10', priority: 'low' },
];
const DATA_VERSION = 'sk-v2';
function loadData() {
const ver = localStorage.getItem('hr-version');
if (ver !== DATA_VERSION) {
localStorage.clear();
localStorage.setItem('hr-version', DATA_VERSION);
return { companies: defaultCompanies, tasks: defaultTasks };
}
const c = localStorage.getItem('hr-companies');
const t = localStorage.getItem('hr-tasks');
return {
companies: c ? JSON.parse(c) : defaultCompanies,
tasks: t ? JSON.parse(t) : defaultTasks,
};
}
function saveData(companies, tasks) {
localStorage.setItem('hr-companies', JSON.stringify(companies));
localStorage.setItem('hr-tasks', JSON.stringify(tasks));
}
let { companies, tasks } = loadData();
// ====== HELPERS ======
function getCompany(id) { return companies.find(c => c.id === id) }
function taskCount(cid, status) { return tasks.filter(t => t.companyId === cid && t.status === status).length }
function totalFor(status) { return tasks.filter(t => t.status === status).length }
function openModal(type) {
const overlay = document.getElementById('modalOverlay');
const content = document.getElementById('modalContent');
if (type === 'company') {
content.innerHTML = `
<h3>Добавить компанию</h3>
<label>Название компании</label>
<input type="text" id="mCompanyName" placeholder="ТОО «...»">
<div class="btn-row">
<button class="btn btn-outline" onclick="closeModal()">Отмена</button>
<button class="btn btn-primary" onclick="addCompany()">Добавить</button>
</div>
`;
} else {
content.innerHTML = `
<h3>Добавить поручение</h3>
<label>Компания</label>
<select id="mTaskCompany">
${companies.map(c => `<option value="${c.id}">${esc(c.name)}</option>`).join('')}
</select>
<label>Описание поручения</label>
<input type="text" id="mTaskTitle" placeholder="Что нужно сделать?">
<label>Дедлайн</label>
<input type="date" id="mTaskDeadline">
<label>Приоритет</label>
<select id="mTaskPriority">
<option value="high">Высокий</option>
<option value="mid" selected>Средний</option>
<option value="low">Низкий</option>
</select>
<label>Статус</label>
<select id="mTaskStatus">
<option value="pending">Ожидает</option>
<option value="progress">В работе</option>
<option value="done">Готово</option>
<option value="overdue">Просрочено</option>
</select>
<div class="btn-row">
<button class="btn btn-outline" onclick="closeModal()">Отмена</button>
<button class="btn btn-primary" onclick="addTask()">Добавить</button>
</div>
`;
}
overlay.classList.add('open');
}
function closeModal() {
document.getElementById('modalOverlay').classList.remove('open');
}
function esc(s) {
const d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}
function uid() { return 'x' + Date.now() + Math.random().toString(36).slice(2, 8) }
function addCompany() {
const name = document.getElementById('mCompanyName').value.trim();
if (!name) return;
const colors = ['g1','g2','g3','g4','g5'];
companies.push({ id: uid(), name, color: colors[Math.floor(Math.random() * colors.length)] });
saveData(companies, tasks);
closeModal();
renderAll();
}
function addTask() {
const companyId = document.getElementById('mTaskCompany').value;
const title = document.getElementById('mTaskTitle').value.trim();
const deadline = document.getElementById('mTaskDeadline').value;
const priority = document.getElementById('mTaskPriority').value;
const status = document.getElementById('mTaskStatus').value;
if (!title) return;
tasks.push({ id: uid(), companyId, title, deadline, priority, status });
saveData(companies, tasks);
closeModal();
renderAll();
}
function deleteTask(id) {
tasks = tasks.filter(t => t.id !== id);
saveData(companies, tasks);
renderAll();
}
function deleteCompany(id) {
if (!confirm('Удалить компанию и все её поручения?')) return;
companies = companies.filter(c => c.id !== id);
tasks = tasks.filter(t => t.companyId !== id);
saveData(companies, tasks);
renderAll();
}
function changeTaskStatus(id, status) {
const t = tasks.find(t => t.id === id);
if (t) { t.status = status; saveData(companies, tasks); renderAll(); }
}
// ====== RENDER ======
const priorityLabels = { high: 'Высокий', mid: 'Средний', low: 'Низкий' };
const statusLabels = { pending: 'Ожидает', progress: 'В работе', done: 'Готово', overdue: 'Просрочено' };
function renderStats() {
const totalCompanies = companies.length;
const active = totalFor('pending') + totalFor('progress');
const done = totalFor('done');
const overdue = totalFor('overdue');
document.getElementById('stats').innerHTML = `
<div class="stat-card">
<div class="label">Компаний</div>
<div class="value" style="color:var(--indigo)">${totalCompanies}</div>
</div>
<div class="stat-card">
<div class="label">Активных поручений</div>
<div class="value" style="color:var(--amber)">${active}</div>
</div>
<div class="stat-card">
<div class="label">Выполнено</div>
<div class="value" style="color:var(--green)">${done}</div>
</div>
<div class="stat-card">
<div class="label">Просрочено</div>
<div class="value" style="color:var(--red)">${overdue}</div>
</div>
`;
}
function renderCompanies() {
const search = (document.getElementById('companySearch')?.value || '').toLowerCase();
const filtered = companies.filter(c => c.name.toLowerCase().includes(search));
const grid = document.getElementById('companiesGrid');
if (filtered.length === 0) {
grid.innerHTML = '<div class="empty"><div class="icon">🏢</div><p>Компании не найдены</p></div>';
return;
}
grid.innerHTML = filtered.map(c => {
const pending = taskCount(c.id, 'pending');
const progress = taskCount(c.id, 'progress');
const done = taskCount(c.id, 'done');
const overdue = taskCount(c.id, 'overdue');
return `
<div class="company-card" onclick="document.getElementById('taskFilterCompany').value='${c.id}';document.getElementById('tasks').scrollIntoView({behavior:'smooth'});renderTasks()">
<div style="display:flex;align-items:center;justify-content:space-between">
<div style="display:flex;align-items:center;gap:12px;min-width:0">
<div class="company-avatar ${c.color}">${c.name.charAt(0)}</div>
<div class="company-name" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(c.name)}</div>
</div>
<button class="btn btn-sm btn-danger" onclick="event.stopPropagation();deleteCompany('${c.id}')" title="Удалить">🗑</button>
</div>
<div class="task-counts">
<span title="Ожидает"><span class="dot-indicator" style="background:var(--amber)"></span>${pending} ожидает</span>
<span title="В работе"><span class="dot-indicator" style="background:var(--indigo)"></span>${progress} в работе</span>
<span title="Готово"><span class="dot-indicator" style="background:var(--green)"></span>${done} готово</span>
<span title="Просрочено"><span class="dot-indicator" style="background:var(--red)"></span>${overdue} просрочено</span>
</div>
${overdue > 0 ? `<div style="margin-top:12px"><span class="badge" style="background:var(--red-bg);color:var(--red)">⚠️ Есть просроченные</span></div>` : ''}
</div>
`;
}).join('');
}
function renderTasks() {
const searchText = (document.getElementById('taskSearch')?.value || '').toLowerCase();
const filterCompany = document.getElementById('taskFilterCompany')?.value || '';
const filterStatus = document.getElementById('taskFilterStatus')?.value || '';
let filtered = tasks;
if (searchText) filtered = filtered.filter(t => t.title.toLowerCase().includes(searchText));
if (filterCompany) filtered = filtered.filter(t => t.companyId === filterCompany);
if (filterStatus) filtered = filtered.filter(t => t.status === filterStatus);
// update company filter dropdown
const sel = document.getElementById('taskFilterCompany');
if (sel) {
const cur = sel.value;
sel.innerHTML = '<option value="">Все компании</option>' +
companies.map(c => `<option value="${c.id}" ${c.id === cur ? 'selected' : ''}>${esc(c.name)}</option>`).join('');
}
const wrap = document.getElementById('taskTableWrap');
if (filtered.length === 0) {
wrap.innerHTML = '<div class="empty"><div class="icon">📝</div><p>Поручений нет</p></div>';
return;
}
wrap.innerHTML = `
<table class="task-table">
<thead><tr>
<th>Компания</th>
<th>Поручение</th>
<th>Дедлайн</th>
<th>Приоритет</th>
<th>Статус</th>
<th></th>
</tr></thead>
<tbody>
${filtered.map(t => {
const c = getCompany(t.companyId);
const deadlineDisplay = t.deadline ? new Date(t.deadline).toLocaleDateString('ru-RU') : '—';
return `
<tr>
<td style="font-weight:600">${c ? esc(c.name) : '—'}</td>
<td style="max-width:300px;overflow:hidden;text-overflow:ellipsis">${esc(t.title)}</td>
<td>${deadlineDisplay}</td>
<td><span class="priority-dot priority-${t.priority}"></span>${priorityLabels[t.priority] || t.priority}</td>
<td>
<select class="status-pill status-${t.status}" style="border:none;cursor:pointer;font-family:inherit;font-weight:600;font-size:12px;padding:4px 12px;border-radius:20px;background:var(--${t.status === 'pending' ? 'amber' : t.status === 'progress' ? 'indigo' : t.status === 'done' ? 'green' : 'red'}-bg);color:var(--${t.status === 'pending' ? 'amber' : t.status === 'progress' ? 'indigo' : t.status === 'done' ? 'green' : 'red'})" onchange="changeTaskStatus('${t.id}', this.value)">
<option value="pending" ${t.status==='pending'?'selected':''}>Ожидает</option>
<option value="progress" ${t.status==='progress'?'selected':''}>В работе</option>
<option value="done" ${t.status==='done'?'selected':''}>Готово</option>
<option value="overdue" ${t.status==='overdue'?'selected':''}>Просрочено</option>
</select>
</td>
<td><button class="btn btn-sm btn-danger" onclick="deleteTask('${t.id}')">🗑</button></td>
</tr>
`;
}).join('')}
</tbody>
</table>
`;
}
// ====== FAQ ======
const faqData = [
{ q: 'Как оформить приказ о приёме на работу?', a: 'Приказ оформляется по форме Т-1 в течение 3 рабочих дней после подписания трудового договора. Обязательно ознакомить сотрудника под подпись.' },
{ q: 'Какие сроки уведомления о сокращении?', a: 'Работодатель обязан письменно уведомить сотрудника не менее чем за 1 месяц до расторжения трудового договора по сокращению штата (ст. 53 ТК РК).' },
{ q: 'Как рассчитать отпускные?', a: 'Средний дневной заработок = сумма зарплаты за 12 месяцев / количество рабочих дней. Умножаем на количество дней отпуска. Выплатить не позднее чем за 3 дня до начала.' },
{ q: 'Обязан ли работодатель проводить медосмотр?', a: 'Да, для сотрудников занятых на тяжёлых работах, с вредными/опасными условиями труда, а также для работников пищевой промышленности, медицины и образования — обязательно.' },
{ q: 'Как перевести сотрудника в другую дочернюю компанию?', a: 'Перевод оформляется через увольнение из одной компании и приём в другую, либо через трёхстороннее соглашение о переводе (ст. 42 ТК РК). В обоих случаях требуется письменное согласие сотрудника.' },
{ q: 'Что делать при несчастном случае на производстве?', a: 'Оказать первую помощь → вызвать скорую → сохранить место происшествия → создать комиссию по расследованию → уведомить госинспекцию труда в течение суток → оформить акт Н-1.' },
{ q: 'Можно ли установить испытательный срок для руководителя?', a: 'Да, до 3 месяцев. Для руководителей организаций и их заместителей, главных бухгалтеров — до 6 месяцев (ст. 36 ТК РК).' },
{ q: 'Как контролировать исполнение поручений дочерним компаниям?', a: 'Рекомендуется: 1) Письменная фиксация поручений с дедлайнами, 2) Еженедельный мониторинг статусов, 3) Единая система отчётности, 4) Регулярные статус-встречи с руководителями ДК.' },
];
function renderFAQ() {
document.getElementById('faqList').innerHTML = faqData.map((item, i) => `
<div class="faq-item">
<div class="faq-q" onclick="this.parentElement.classList.toggle('open')">
<span>${esc(item.q)}</span>
<span class="arrow">▼</span>
</div>
<div class="faq-a">${esc(item.a)}</div>
</div>
`).join('');
}
// ====== RENDER ALL ======
function renderAll() {
renderStats();
renderCompanies();
renderTasks();
}
renderAll();
renderFAQ();
// Nav smooth scroll
document.querySelectorAll('.nav-links a').forEach(a => {
a.addEventListener('click', e => {
e.preventDefault();
const target = document.querySelector(a.getAttribute('href'));
if (target) target.scrollIntoView({ behavior: 'smooth' });
document.querySelectorAll('.nav-links a').forEach(x => x.classList.remove('active'));
a.classList.add('active');
});
});
// Keyboard shortcut
document.addEventListener('keydown', e => {
if (e.key === 'Escape') closeModal();
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
document.getElementById('taskSearch').focus();
}
});
</script>
</body>
</html>