meraproject/services/user-reader/static/summary.html
keboss-m 5c21d25d45 Initial commit: Merakomis portal, Docker stack and user-reader API.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 11:04:05 +03:00

174 lines
7.1 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>Сводка (read-only)</title>
<style>
body { font-family: system-ui, sans-serif; margin: 1rem; background: #111; color: #e6e6e6; }
h1 { font-size: 1.2rem; }
.meta { color: #888; font-size: 0.85rem; margin-bottom: 1rem; }
table { border-collapse: collapse; width: 100%; font-size: 0.85rem; }
th, td { border: 1px solid #333; padding: 0.35rem 0.45rem; text-align: left; }
th { background: #222; position: sticky; top: 0; }
tr:nth-child(even) { background: #1a1a1a; }
.err { color: #f66; }
a { color: #8cf; }
.panel { margin-bottom: 1rem; padding: 0.75rem; background: #1a1a1a; border: 1px solid #333; }
.panel label { margin-right: 0.75rem; font-size: 0.85rem; }
.panel input, .panel button {
padding: 0.35rem; margin: 0.2rem 0.35rem 0.2rem 0;
background: #222; border: 1px solid #444; color: #e6e6e6;
}
.panel button { cursor: pointer; }
#wrap { overflow-x: auto; max-height: 70vh; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
</style>
</head>
<body>
<nav class="meta" style="margin-bottom:0.75rem;">
<a href="/">Сотрудники</a> · <a href="/labor">Трудозатраты</a> · <a href="/time-entry">Запись часов</a> · <strong>Сводка</strong> · <a href="/project-report">Проекты</a>
</nav>
<h1>Сводка: сотрудник × проект</h1>
<p class="meta">ФИО, орготдел (АР, ОВ, ВК…), раздел в проекте (ОВ1, ОВ2, ТХ…) + проект и часы за период.</p>
<div id="keybox" class="panel" style="display:none;">
<div>API-ключ (<code>USER_READER_API_KEY</code>):</div>
<input type="password" id="apiKeyInput" placeholder="ключ" autocomplete="off" />
<button type="button" id="saveKey">Сохранить</button>
</div>
<div class="panel">
<label>С <input type="date" id="dateFrom" /></label>
<label>По <input type="date" id="dateTo" /></label>
<label>emp_id <input type="number" id="empId" min="1" placeholder="—" style="width:5rem;" /></label>
<label>project_id <input type="number" id="projectId" min="1" placeholder="—" style="width:5rem;" /></label>
<button type="button" id="loadBtn">Загрузить</button>
</div>
<p class="meta">API: <a href="/api/work-report">/api/work-report</a></p>
<p id="status" class="meta">Выберите период и нажмите «Загрузить».</p>
<div id="wrap"></div>
<script>
const STORE_KEY = 'meraproject_user_reader_api_key';
const COLUMNS = [
{ key: 'id', label: 'id' },
{ key: 'employee', label: 'Сотрудник' },
{ key: 'staffing_title', label: 'Должность' },
{ key: 'department', label: 'department' },
{ key: 'section', label: 'section' },
{ key: 'project_code', label: 'project_code' },
{ key: 'step_name', label: 'Стадия' },
{ key: 'status_name', label: 'Статус' },
{ key: 'archive', label: 'archive', num: true },
{ key: 'project_name', label: 'project_name' },
{ key: 'hours', label: 'hours', num: true },
{ key: 'over', label: 'over', num: true },
{ key: 'over1', label: 'over1', num: true },
{ key: 'over2', label: 'over2', num: true },
{ key: 'total', label: 'total', num: true },
];
function headers() {
const k = sessionStorage.getItem(STORE_KEY);
const h = {};
if (k) h['X-Api-Key'] = k;
return h;
}
function escapeHtml(s) {
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
function defaultDates() {
const to = new Date();
const from = new Date();
from.setMonth(from.getMonth() - 1);
const fmt = d => d.toISOString().slice(0, 10);
document.getElementById('dateFrom').value = fmt(from);
document.getElementById('dateTo').value = fmt(to);
}
function showKeyBox(show) {
document.getElementById('keybox').style.display = show ? 'block' : 'none';
}
function renderTable(items) {
const wrap = document.getElementById('wrap');
if (!items || !items.length) {
wrap.innerHTML = '<p>Нет строк за выбранный период.</p>';
return;
}
const th = COLUMNS.map(c => '<th>' + escapeHtml(c.label) + '</th>').join('');
const tr = items.map(row => '<tr>' + COLUMNS.map(c => {
const v = row[c.key];
const cls = c.num ? ' class="num"' : '';
return '<td' + cls + '>' + escapeHtml(v == null ? '' : v) + '</td>';
}).join('') + '</tr>').join('');
wrap.innerHTML = '<table><thead><tr>' + th + '</tr></thead><tbody>' + tr + '</tbody></table>';
}
function buildUrl() {
const df = document.getElementById('dateFrom').value;
const dt = document.getElementById('dateTo').value;
if (!df || !dt) throw new Error('Нужны даты «С» и «По»');
const params = new URLSearchParams();
params.set('date_from', df);
params.set('date_to', dt);
params.set('limit', '2000');
const emp = document.getElementById('empId').value.trim();
const proj = document.getElementById('projectId').value.trim();
if (emp) params.set('emp_id', emp);
if (proj) params.set('project_id', proj);
return '/api/work-report?' + params.toString();
}
async function load() {
const status = document.getElementById('status');
try {
const h = await fetch('/api/health').then(r => r.json());
if (!h.ok) {
status.textContent = 'БД недоступна: ' + (h.error || '');
status.className = 'meta err';
return;
}
const url = buildUrl();
const r = await fetch(url, { headers: headers() });
if (r.status === 401) {
status.textContent = 'Нужен API-ключ.';
status.className = 'meta err';
showKeyBox(true);
return;
}
if (!r.ok) {
const err = await r.json().catch(() => ({}));
throw new Error(err.detail || (r.status + ' ' + r.statusText));
}
const data = await r.json();
showKeyBox(false);
const items = data.items || [];
status.textContent = 'период: ' + data.date_from + ' … ' + data.date_to
+ ' · всего строк: ' + (data.total != null ? data.total : items.length);
status.className = 'meta';
renderTable(items);
} catch (e) {
status.textContent = 'Ошибка: ' + e.message;
status.className = 'meta err';
}
}
document.getElementById('loadBtn').addEventListener('click', load);
document.getElementById('saveKey').addEventListener('click', function() {
const v = document.getElementById('apiKeyInput').value.trim();
if (v) sessionStorage.setItem(STORE_KEY, v);
load();
});
defaultDates();
if (!sessionStorage.getItem(STORE_KEY)) showKeyBox(true);
</script>
</body>
</html>