Split tasks list and analytics into separate views

Menu now has three items per role:
- Director: Главная · Все поручения · Аналитика (+Создать)
- Employee: Главная · Мои поручения · Аналитика

#/tasks holds the filterable table and archive.
#/analytics holds KPIs, workload, and charts. Employees see
their own KPIs plus their personal deadline/direction/timeline
charts; department-wide charts stay director-only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
sultan 2026-05-25 09:38:21 +05:00
parent 32378c342c
commit 9de998a80c
2 changed files with 63 additions and 29 deletions

28
app.js
View File

@ -303,11 +303,14 @@ function escapeHtml(s) {
function renderAll() {
const tasks = getTasks();
if (state.view === 'analytics') {
renderDashboard(tasks);
if (state.view === 'tasks') {
renderTable(tasks);
renderArchive(tasks);
renderWorkload(tasks);
}
if (state.view === 'analytics') {
const scoped = isDirector() ? tasks : tasks.filter((t) => t.assignee === state.currentUser);
renderDashboard(tasks);
renderWorkload(scoped);
}
}
@ -411,8 +414,14 @@ function renderWorkload(tasks) {
$('#kpiSoon').textContent = active.filter((t) => t.deadline && t.deadline >= today && t.deadline <= soonCutoff).length;
$('#kpiDone').textContent = tasks.filter((t) => t.status === 'done').length;
if (!isDirector()) {
destroyChart('status'); destroyChart('priority');
renderWorkloadCharts(tasks);
return;
}
const perAssignee = buildPerAssignee(tasks);
const employees = EMPLOYEES.filter((e) => e !== 'Директор');
const employees = EMPLOYEES.filter((e) => e !== 'Абделазиз');
destroyChart('status');
charts.status = new Chart($('#chartStatus'), {
@ -443,6 +452,11 @@ function renderWorkload(tasks) {
options: chartOpts({ stacked: true }),
});
renderWorkloadCharts(tasks);
}
function renderWorkloadCharts(tasks) {
if (!window.Chart) return;
const dl = buildDeadlineBuckets(tasks);
destroyChart('deadlines');
charts.deadlines = new Chart($('#chartDeadlines'), {
@ -593,7 +607,7 @@ function renderUpcoming() {
const iso = el.getAttribute('data-iso');
state.filters = state.filters || {};
state.filters.deadline = iso;
location.hash = '#/analytics';
location.hash = '#/tasks';
});
});
}
@ -660,7 +674,7 @@ function renderCalendar() {
if (!iso) return;
state.filters = state.filters || {};
state.filters.deadline = iso;
location.hash = '#/analytics';
location.hash = '#/tasks';
});
});
}
@ -884,6 +898,7 @@ function applyRoute() {
const hash = location.hash || '#/home';
let view = 'home';
if (hash === '#/create') view = 'create';
else if (hash === '#/tasks') view = 'tasks';
else if (hash === '#/analytics') view = 'analytics';
if (view === 'create' && !isDirector()) {
location.hash = '#/home';
@ -893,6 +908,7 @@ function applyRoute() {
document.body.dataset.view = view;
$('#view-home').hidden = view !== 'home';
$('#view-create').hidden = view !== 'create';
$('#view-tasks').hidden = view !== 'tasks';
$('#view-analytics').hidden = view !== 'analytics';
document.querySelectorAll('.menu-item').forEach((a) => {
a.classList.toggle('active', a.dataset.view === view);

View File

@ -11,7 +11,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.css">
<link rel="stylesheet" href="styles.css?v=36" />
<link rel="stylesheet" href="styles.css?v=37" />
</head>
<body>
<header class="topbar">
@ -35,11 +35,25 @@
<span class="menu-sub">Форма ввода нового поручения</span>
</span>
</a>
<a href="#/tasks" class="menu-item director-only" data-view="tasks" role="menuitem">
<span class="menu-icon">📋</span>
<span class="menu-text">
<span class="menu-title">Все поручения</span>
<span class="menu-sub">Таблица задач и архив</span>
</span>
</a>
<a href="#/tasks" class="menu-item employee-only" data-view="tasks" role="menuitem">
<span class="menu-icon">📋</span>
<span class="menu-text">
<span class="menu-title">Мои поручения</span>
<span class="menu-sub">Ваши задачи и архив</span>
</span>
</a>
<a href="#/analytics" class="menu-item" data-view="analytics" role="menuitem">
<span class="menu-icon">📊</span>
<span class="menu-text">
<span class="menu-title">Аналитика</span>
<span class="menu-sub">Сводка, фильтры, графики</span>
<span class="menu-sub">Сводка, графики</span>
</span>
</a>
</div>
@ -183,7 +197,7 @@
<section class="card home-tasks">
<div class="tasks-head">
<h2>Ближайшие задачи</h2>
<a href="#/analytics" class="tasks-all">Все →</a>
<a href="#/tasks" class="tasks-all">Все →</a>
</div>
<ul id="upcomingList" class="upcoming-list">
<li class="upcoming-empty">Загружаем…</li>
@ -224,24 +238,8 @@
</section>
</div>
<!-- Аналитика -->
<div id="view-analytics" class="view" hidden>
<section class="card director-only">
<h2>Сводка по департаменту</h2>
<div class="kpis">
<div class="kpi"><div class="kpi-num" id="kpiActive">0</div><div class="kpi-lbl">Активных задач</div></div>
<div class="kpi"><div class="kpi-num" id="kpiOverdue">0</div><div class="kpi-lbl">Просрочено</div></div>
<div class="kpi"><div class="kpi-num" id="kpiSoon">0</div><div class="kpi-lbl">Дедлайн ≤ 3 дн</div></div>
<div class="kpi"><div class="kpi-num" id="kpiDone">0</div><div class="kpi-lbl">Выполнено</div></div>
</div>
</section>
<section class="card director-only">
<h2>Загрузка по сотрудникам</h2>
<div class="load" id="loadByAssignee"></div>
<p class="hint">Нажмите на сотрудника, чтобы отфильтровать таблицу.</p>
</section>
<!-- Поручения -->
<div id="view-tasks" class="view" hidden>
<section class="card">
<h2 class="director-only">Все поручения</h2>
<h2 class="employee-only">Мои поручения</h2>
@ -302,6 +300,26 @@
<p id="archiveEmpty" class="hint" hidden>Архив пуст.</p>
</div>
</section>
</div>
<!-- Аналитика -->
<div id="view-analytics" class="view" hidden>
<section class="card">
<h2 class="director-only">Сводка по департаменту</h2>
<h2 class="employee-only">Моя сводка</h2>
<div class="kpis">
<div class="kpi"><div class="kpi-num" id="kpiActive">0</div><div class="kpi-lbl">Активных задач</div></div>
<div class="kpi"><div class="kpi-num" id="kpiOverdue">0</div><div class="kpi-lbl">Просрочено</div></div>
<div class="kpi"><div class="kpi-num" id="kpiSoon">0</div><div class="kpi-lbl">Дедлайн ≤ 3 дн</div></div>
<div class="kpi"><div class="kpi-num" id="kpiDone">0</div><div class="kpi-lbl">Выполнено</div></div>
</div>
</section>
<section class="card director-only">
<h2>Загрузка по сотрудникам</h2>
<div class="load" id="loadByAssignee"></div>
<p class="hint">Нажмите на сотрудника, чтобы отфильтровать таблицу.</p>
</section>
<section class="card director-only">
<h2>Задачи по сотрудникам (по статусу)</h2>
@ -313,7 +331,7 @@
<div class="chart-wrap"><canvas id="chartPriority"></canvas></div>
</section>
<div class="charts-row director-only">
<div class="charts-row">
<section class="card">
<h2>Распределение по дедлайнам</h2>
<div class="chart-wrap small"><canvas id="chartDeadlines"></canvas></div>
@ -337,6 +355,6 @@
<script src="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/l10n/ru.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<script src="app.js?v=33"></script>
<script src="app.js?v=34"></script>
</body>
</html>