663 lines
32 KiB
HTML
663 lines
32 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru" data-theme="light">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Журнал учёта заявок</title>
|
||
<script data-kt-ai-base-resolver>
|
||
(function () {
|
||
var корень = document.documentElement;
|
||
function норм(значение) {
|
||
значение = (значение || "").trim();
|
||
if (!значение || значение === "/") return "";
|
||
if (значение.charAt(0) !== "/") значение = "/" + значение;
|
||
return значение.replace(/\/+$/, "");
|
||
}
|
||
var база = норм(корень.getAttribute("data-kt-ai-base"));
|
||
var папка = норм(корень.getAttribute("data-kt-ai-static")) || "/design-system";
|
||
window.ktAiBase = база;
|
||
window.ktAiAsset = function (имя) {
|
||
return /^([a-z][a-z0-9+.-]*:|\/\/)/i.test(имя) ? имя : база + папка + "/" + имя;
|
||
};
|
||
})();
|
||
</script>
|
||
<script data-kt-ai-boot="css">
|
||
["kt-ai-tokens.css", "kt-ai-components.css"].forEach(function (файл) {
|
||
var ссылка = document.createElement("link");
|
||
ссылка.rel = "stylesheet";
|
||
ссылка.href = window.ktAiAsset(файл);
|
||
document.head.appendChild(ссылка);
|
||
});
|
||
</script>
|
||
<style>
|
||
@layer product {
|
||
*, *::before, *::after { box-sizing: border-box; }
|
||
body { margin: 0; }
|
||
.shell-topbar-burger { display: none; }
|
||
@media (max-width: 831.98px) { .shell-topbar-burger { display: inline-flex; } }
|
||
.sb-top { display: flex; align-items: center; gap: var(--kt-ai-space-3); padding: 2px 4px 12px; }
|
||
.sb-top img { width: 28px; height: 28px; border-radius: var(--kt-ai-radius-xl); flex-shrink: 0; }
|
||
.sb-top .nm { font-weight: var(--kt-ai-weight-semibold); font-size: var(--kt-ai-text-sm); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
|
||
.sb-bottom { margin-top: auto; display: flex; flex-direction: column; gap: 2px; padding-top: var(--kt-ai-space-4); }
|
||
.sb-bottom button { background: none; border: none; font: inherit; color: inherit; text-align: left; cursor: pointer; width: 100%; }
|
||
.sb-bottom .badge { margin-left: auto; font-family: var(--kt-ai-font-mono); font-size: var(--kt-ai-text-2xs); background: var(--kt-ai-primary-subtle); color: var(--kt-ai-primary); border-radius: var(--kt-ai-radius-full); padding: 0 6px; }
|
||
.sb-new { margin: 0 4px var(--kt-ai-space-5); }
|
||
.sb-new .kt-ai-btn { width: 100%; justify-content: center; }
|
||
.kt-ai-shell[data-rail="true"] .sb-new .kt-ai-btn { padding: 0; }
|
||
#view { width: 100%; max-width: var(--kt-ai-content-table); margin-inline: auto; padding: var(--kt-ai-stack-region) var(--kt-ai-stack-region) var(--kt-ai-space-11); }
|
||
@media (max-width: 639.98px) { #view { padding: var(--kt-ai-stack-group) var(--kt-ai-space-6) var(--kt-ai-space-10); } }
|
||
.page-hero { display: flex; align-items: flex-start; gap: var(--kt-ai-space-6); margin-bottom: var(--kt-ai-stack-group); }
|
||
.page-hero .ph-text { flex: 1; min-width: 0; }
|
||
.page-hero h1 { margin: 0; font-size: var(--kt-ai-text-2xl); font-weight: var(--kt-ai-weight-semibold); letter-spacing: -.01em; line-height: 1.15; }
|
||
.page-hero .sub { margin-top: var(--kt-ai-space-3); font-size: var(--kt-ai-text-md); color: var(--kt-ai-fg-muted); }
|
||
.page-hero .ph-action { flex-shrink: 0; }
|
||
@media (max-width: 639.98px) { .page-hero { flex-direction: column; gap: var(--kt-ai-space-5); } .page-hero h1 { font-size: var(--kt-ai-text-2xl); } }
|
||
#view > .kt-ai-kpi-strip { margin-bottom: var(--kt-ai-stack-block); }
|
||
.toolbar-row { display: flex; align-items: center; gap: var(--kt-ai-space-4); flex-wrap: wrap; padding: 0 0 var(--kt-ai-stack-block); }
|
||
.toolbar-row .spacer { flex: 1; }
|
||
.drawer-head { display: flex; align-items: flex-start; gap: var(--kt-ai-space-4); margin-bottom: var(--kt-ai-space-6); }
|
||
.drawer-head .t { flex: 1; }
|
||
.drawer-flags { display: flex; flex-direction: column; gap: var(--kt-ai-space-3); margin: var(--kt-ai-space-6) 0; }
|
||
.drawer-actions { display: flex; flex-direction: column; gap: var(--kt-ai-space-4); margin-top: var(--kt-ai-space-8); }
|
||
.kt-ai-card .drawer-actions { flex-direction: row-reverse; justify-content: flex-start; }
|
||
.kt-ai-card .drawer-actions .kt-ai-btn { width: auto; }
|
||
@media (max-width: 639.98px) { .kt-ai-card .drawer-actions { flex-direction: column; } }
|
||
.drawer-sec { padding: var(--kt-ai-space-6) 0; border-top: 1px solid var(--kt-ai-divider); }
|
||
.drawer-sec:first-of-type { border-top: none; padding-top: 0; }
|
||
.log-entry { display: flex; gap: var(--kt-ai-space-3); padding: var(--kt-ai-space-3) 0; font-size: var(--kt-ai-text-sm); border-bottom: 1px solid var(--kt-ai-divider); }
|
||
.log-entry:last-child { border-bottom: none; }
|
||
.log-time { font-family: var(--kt-ai-font-mono); font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-faint); min-width: 80px; }
|
||
.log-what { flex: 1; }
|
||
.log-who { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); }
|
||
.empty-state { text-align: center; padding: var(--kt-ai-space-12) var(--kt-ai-space-6); color: var(--kt-ai-fg-muted); }
|
||
.empty-state .kt-icon { width: 48px; height: 48px; margin-bottom: var(--kt-ai-space-4); color: var(--kt-ai-fg-faint); }
|
||
.empty-state h3 { margin: 0 0 var(--kt-ai-space-2); font-size: var(--kt-ai-text-lg); color: var(--kt-ai-fg); }
|
||
.empty-state p { margin: 0; font-size: var(--kt-ai-text-sm); }
|
||
.conflict-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: none; align-items: center; justify-content: center; z-index: 1000; }
|
||
.conflict-overlay.on { display: flex; }
|
||
.conflict-card { background: var(--kt-ai-bg-elevated); border-radius: var(--kt-ai-radius-3xl); padding: var(--kt-ai-space-8); max-width: 480px; width: 90%; }
|
||
.conflict-card h3 { margin: 0 0 var(--kt-ai-space-4); font-size: var(--kt-ai-text-xl); }
|
||
.conflict-card p { margin: 0 0 var(--kt-ai-space-6); color: var(--kt-ai-fg-muted); font-size: var(--kt-ai-text-sm); }
|
||
.conflict-actions { display: flex; gap: var(--kt-ai-space-3); justify-content: flex-end; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body class="kt-ai-app">
|
||
|
||
<script id="kt-app-config" type="application/json">
|
||
{
|
||
"archetype": "queue",
|
||
"product": { "name": "Журнал учёта заявок", "purpose": "Учёт внутренних заявок сотрудников", "entity": "заявка", "view": "operational" },
|
||
"user": { "name": "Сотрудник подразделения", "role": "Ответственный за обработку заявок" },
|
||
"kpis": [
|
||
{ "value": "0", "label": "Всего заявок", "hint": "активных записей" },
|
||
{ "value": "0", "label": "В работе", "hint": "текущие задачи" },
|
||
{ "value": "0", "label": "Выполнено", "hint": "закрытые заявки" }
|
||
],
|
||
"statuses": {
|
||
"new": { "label": "Новая", "tone": "info" },
|
||
"in_progress": { "label": "В работе", "tone": "warn" },
|
||
"done": { "label": "Выполнена", "tone": "ok" }
|
||
},
|
||
"scenario": {
|
||
"headline": "Управление заявкой",
|
||
"control": "Сохранить",
|
||
"reject": "Удалить",
|
||
"confirmText": "Заявка будет сохранена в журнале."
|
||
},
|
||
"table": {
|
||
"columns": [
|
||
{ "key": "person", "label": "ФИО", "type": "text" },
|
||
{ "key": "title", "label": "Описание заявки", "type": "text" },
|
||
{ "key": "deadline", "label": "Срок", "type": "date" },
|
||
{ "key": "status", "label": "Статус", "type": "status" }
|
||
],
|
||
"rows": [
|
||
{ "person": "Алиев А.", "title": "Настройка рабочего места", "deadline": "15.09.2026", "status": "new" },
|
||
{ "person": "Смирнова Е.", "title": "Закуп канцтоваров", "deadline": "12.09.2026", "status": "in_progress" },
|
||
{ "person": "Иванов П.", "title": "Ремонт принтера", "deadline": "10.09.2026", "status": "done" },
|
||
{ "person": "Ким Д.", "title": "Доступ к CRM", "deadline": "18.09.2026", "status": "new" },
|
||
{ "person": "Петрова О.", "title": "Командировка в Астану", "deadline": "20.09.2026", "status": "in_progress" }
|
||
]
|
||
},
|
||
"drawer": {
|
||
"titleKey": "title",
|
||
"fields": [
|
||
{ "key": "person", "label": "ФИО заявителя" },
|
||
{ "key": "deadline", "label": "Срок исполнения" },
|
||
{ "key": "status", "label": "Текущий статус" }
|
||
],
|
||
"flagsLabel": "Детали заявки"
|
||
},
|
||
"filterKeys": ["status"],
|
||
"search": { "placeholder": "Поиск по заявкам…", "shortcut": "Ctrl+K" }
|
||
}
|
||
</script>
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="position:absolute;width:0;height:0;overflow:hidden">
|
||
<symbol id="kt-ai-logo" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="4"/></symbol>
|
||
<symbol id="home" viewBox="0 0 24 24"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></symbol>
|
||
<symbol id="table" viewBox="0 0 24 24"><path d="M9 3H5a2 2 0 0 0-2 2v4m6-6h10a2 2 0 0 1 2 2v4M9 3v18m0 0h10a2 2 0 0 0 2-2V9M9 21H5a2 2 0 0 1-2-2V9m0 0h18"/></symbol>
|
||
<symbol id="file-text" viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></symbol>
|
||
<symbol id="settings" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></symbol>
|
||
<symbol id="download" viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></symbol>
|
||
<symbol id="plus" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></symbol>
|
||
<symbol id="trash" viewBox="0 0 24 24"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></symbol>
|
||
<symbol id="edit" viewBox="0 0 24 24"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></symbol>
|
||
<symbol id="x" viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></symbol>
|
||
<symbol id="check" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></symbol>
|
||
<symbol id="alert-circle" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></symbol>
|
||
<symbol id="calendar" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></symbol>
|
||
<symbol id="user" viewBox="0 0 24 24"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></symbol>
|
||
<symbol id="sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></symbol>
|
||
<symbol id="moon" viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></symbol>
|
||
</svg>
|
||
|
||
<div class="kt-ai-shell" data-rail="false">
|
||
<aside class="kt-ai-shell-sidebar">
|
||
<div class="sb-top">
|
||
<svg class="kt-icon"><use href="#kt-ai-logo"/></svg>
|
||
<span class="nm">Журнал заявок</span>
|
||
<button class="kt-ai-btn" data-variant="ghost" data-size="sm" id="themeToggle" title="Сменить тему">
|
||
<svg class="kt-icon"><use href="#sun"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="sb-new">
|
||
<button class="kt-ai-btn" data-variant="primary" id="createBtn">
|
||
<svg class="kt-icon"><use href="#plus"/></svg>
|
||
<span>Добавить заявку</span>
|
||
</button>
|
||
</div>
|
||
<nav class="kt-ai-nav">
|
||
<button class="kt-ai-nav-item on">
|
||
<svg class="kt-icon"><use href="#table"/></svg>
|
||
<span class="nav-label">Все заявки</span>
|
||
</button>
|
||
<button class="kt-ai-nav-item" id="logBtn">
|
||
<svg class="kt-icon"><use href="#file-text"/></svg>
|
||
<span class="nav-label">Журнал изменений</span>
|
||
</button>
|
||
</nav>
|
||
<div class="sb-bottom">
|
||
<div class="kt-ai-nav-item">
|
||
<svg class="kt-icon"><use href="#user"/></svg>
|
||
<span class="nav-label">Сотрудник</span>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<main class="kt-ai-shell-main">
|
||
<div class="kt-ai-topbar">
|
||
<div class="topbar-breadcrumbs">
|
||
<div class="topbar-crumb">
|
||
<svg class="kt-icon"><use href="#table"/></svg>
|
||
<span>Журнал учёта заявок</span>
|
||
</div>
|
||
</div>
|
||
<div class="kt-ai-topbar-right">
|
||
<button class="kt-ai-btn" data-variant="ghost" id="exportBtn" title="Скачать в Excel">
|
||
<svg class="kt-icon"><use href="#download"/></svg>
|
||
<span>Экспорт</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="view">
|
||
<div class="page-hero">
|
||
<div class="ph-text">
|
||
<h1>Журнал учёта заявок</h1>
|
||
<p class="sub">Управление внутренними заявками сотрудников</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="kt-ai-kpi-strip" id="kpiStrip">
|
||
<div class="kt-ai-kpi-card"><div class="v" id="kpiTotal">0</div><div class="l">Всего</div></div>
|
||
<div class="kt-ai-kpi-card"><div class="v" id="kpiNew">0</div><div class="l">Новые</div></div>
|
||
<div class="kt-ai-kpi-card"><div class="v" id="kpiProgress">0</div><div class="l">В работе</div></div>
|
||
<div class="kt-ai-kpi-card"><div class="v" id="kpiDone">0</div><div class="l">Выполнено</div></div>
|
||
</div>
|
||
|
||
<div class="toolbar-row" id="toolbar">
|
||
<div class="kt-ai-chip-group" id="statusFilter"></div>
|
||
<div class="spacer"></div>
|
||
</div>
|
||
|
||
<div id="tableContainer"></div>
|
||
<div id="logContainer" style="display:none"></div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
|
||
<div class="kt-ai-drawer" id="drawer" data-open="false">
|
||
<div class="kt-ai-drawer-overlay"></div>
|
||
<aside class="kt-ai-drawer-panel">
|
||
<div class="drawer-head">
|
||
<div class="t">
|
||
<h2 id="drawerTitle" style="margin:0;font-size:var(--kt-ai-text-xl)"></h2>
|
||
</div>
|
||
<button class="kt-ai-btn" data-variant="ghost" id="closeDrawer">
|
||
<svg class="kt-icon"><use href="#x"/></svg>
|
||
</button>
|
||
</div>
|
||
<div id="drawerContent"></div>
|
||
<div class="drawer-actions" id="drawerActions"></div>
|
||
</aside>
|
||
</div>
|
||
|
||
<div class="kt-ai-modal" id="modal" data-open="false">
|
||
<div class="kt-ai-modal-overlay"></div>
|
||
<div class="kt-ai-modal-panel" style="max-width:480px">
|
||
<div class="drawer-head">
|
||
<h2 id="modalTitle" style="margin:0;font-size:var(--kt-ai-text-xl)">Заявка</h2>
|
||
<button class="kt-ai-btn" data-variant="ghost" id="closeModal">
|
||
<svg class="kt-icon"><use href="#x"/></svg>
|
||
</button>
|
||
</div>
|
||
<div style="display:flex;flex-direction:column;gap:var(--kt-ai-space-4)">
|
||
<div class="kt-ai-field">
|
||
<label>ФИО</label>
|
||
<input class="kt-ai-input" id="formPerson" required>
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label>Описание заявки</label>
|
||
<input class="kt-ai-input" id="formTitle" required>
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label>Срок</label>
|
||
<input class="kt-ai-input" type="date" id="formDeadline" required>
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label>Статус</label>
|
||
<select class="kt-ai-select" id="formStatus">
|
||
<option value="new">Новая</option>
|
||
<option value="in_progress">В работе</option>
|
||
<option value="done">Выполнена</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="drawer-actions" style="margin-top:var(--kt-ai-space-6)">
|
||
<button class="kt-ai-btn" data-variant="ghost" id="cancelModal">Отмена</button>
|
||
<button class="kt-ai-btn" data-variant="primary" id="saveBtn">Сохранить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="conflict-overlay" id="confirmOverlay">
|
||
<div class="conflict-card">
|
||
<h3>Подтверждение</h3>
|
||
<p id="confirmText">Вы уверены?</p>
|
||
<div class="conflict-actions">
|
||
<button class="kt-ai-btn" data-variant="ghost" id="confirmCancel">Отмена</button>
|
||
<button class="kt-ai-btn" data-variant="danger" id="confirmOk">Подтвердить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(function() {
|
||
var CFG = JSON.parse(document.getElementById('kt-app-config').textContent);
|
||
var LS_KEY = 'journal_requests_v1';
|
||
var LOG_KEY = 'journal_log_v1';
|
||
|
||
var data = JSON.parse(localStorage.getItem(LS_KEY) || 'null');
|
||
var log = JSON.parse(localStorage.getItem(LOG_KEY) || 'null');
|
||
|
||
if (!data) {
|
||
data = CFG.table.rows.map(function(r, i) {
|
||
return { id: i + 1, person: r.person, title: r.title, deadline: r.deadline, status: r.status };
|
||
});
|
||
localStorage.setItem(LS_KEY, JSON.stringify(data));
|
||
}
|
||
if (!log) {
|
||
log = [];
|
||
localStorage.setItem(LOG_KEY, JSON.stringify(log));
|
||
}
|
||
|
||
var curFilter = 'all';
|
||
var searchQ = '';
|
||
var curRow = null;
|
||
var editingId = null;
|
||
var confirmCb = null;
|
||
|
||
function save() {
|
||
localStorage.setItem(LS_KEY, JSON.stringify(data));
|
||
localStorage.setItem(LOG_KEY, JSON.stringify(log));
|
||
render();
|
||
}
|
||
|
||
function addLog(action, who) {
|
||
log.unshift({
|
||
when: new Date().toISOString(),
|
||
what: action,
|
||
who: who || 'Сотрудник'
|
||
});
|
||
if (log.length > 100) log.pop();
|
||
}
|
||
|
||
function fmtDate(d) {
|
||
var dt = new Date(d);
|
||
var dd = String(dt.getDate()).padStart(2, '0');
|
||
var mm = String(dt.getMonth() + 1).padStart(2, '0');
|
||
var yyyy = dt.getFullYear();
|
||
return dd + '.' + mm + '.' + yyyy;
|
||
}
|
||
|
||
function fmtDateTime(iso) {
|
||
var dt = new Date(iso);
|
||
return fmtDate(iso) + ' ' + String(dt.getHours()).padStart(2,'0') + ':' + String(dt.getMinutes()).padStart(2,'0');
|
||
}
|
||
|
||
function getStatusCfg(key) {
|
||
return CFG.statuses[key] || { label: key, tone: 'info' };
|
||
}
|
||
|
||
function renderKpis() {
|
||
var total = data.length;
|
||
var newC = data.filter(function(r){ return r.status === 'new'; }).length;
|
||
var prog = data.filter(function(r){ return r.status === 'in_progress'; }).length;
|
||
var done = data.filter(function(r){ return r.status === 'done'; }).length;
|
||
document.getElementById('kpiTotal').textContent = total;
|
||
document.getElementById('kpiNew').textContent = newC;
|
||
document.getElementById('kpiProgress').textContent = prog;
|
||
document.getElementById('kpiDone').textContent = done;
|
||
}
|
||
|
||
function renderFilters() {
|
||
var cont = document.getElementById('statusFilter');
|
||
var opts = [
|
||
{ key: 'all', label: 'Все' }
|
||
].concat(Object.keys(CFG.statuses).map(function(k){
|
||
return { key: k, label: CFG.statuses[k].label };
|
||
}));
|
||
cont.innerHTML = opts.map(function(o){
|
||
var on = curFilter === o.key;
|
||
return '<button class="kt-ai-chip' + (on ? ' on' : '') + '" data-filter="' + o.key + '">' + o.label + '</button>';
|
||
}).join('');
|
||
}
|
||
|
||
function getFiltered() {
|
||
var rows = data.slice();
|
||
if (curFilter !== 'all') {
|
||
rows = rows.filter(function(r){ return r.status === curFilter; });
|
||
}
|
||
if (searchQ.trim()) {
|
||
var q = searchQ.toLowerCase();
|
||
rows = rows.filter(function(r){
|
||
return r.person.toLowerCase().indexOf(q) >= 0 ||
|
||
r.title.toLowerCase().indexOf(q) >= 0;
|
||
});
|
||
}
|
||
return rows;
|
||
}
|
||
|
||
function renderTable() {
|
||
var cont = document.getElementById('tableContainer');
|
||
var rows = getFiltered();
|
||
if (!rows.length) {
|
||
cont.innerHTML = '<div class="empty-state"><svg class="kt-icon"><use href="#table"/></svg><h3>Нет заявок</h3><p>Нажмите «Добавить заявку», чтобы создать первую запись</p></div>';
|
||
return;
|
||
}
|
||
var html = '<div class="kt-ai-card"><table class="kt-ai-table"><thead><tr>';
|
||
CFG.table.columns.forEach(function(col){
|
||
html += '<th>' + col.label + '</th>';
|
||
});
|
||
html += '<th style="width:80px"></th></tr></thead><tbody>';
|
||
rows.forEach(function(r){
|
||
var scfg = getStatusCfg(r.status);
|
||
html += '<tr data-id="' + r.id + '">';
|
||
CFG.table.columns.forEach(function(col){
|
||
var v = r[col.key];
|
||
if (col.type === 'status') {
|
||
html += '<td><span class="kt-ai-chip" data-tone="' + scfg.tone + '">' + scfg.label + '</span></td>';
|
||
} else if (col.type === 'date') {
|
||
html += '<td>' + v + '</td>';
|
||
} else {
|
||
html += '<td>' + v + '</td>';
|
||
}
|
||
});
|
||
html += '<td class="row-act"><div class="row-kebab"><button class="kt-ai-btn" data-variant="ghost" data-size="sm" title="Редактировать" onclick="event.stopPropagation();editRow(' + r.id + ')"><svg class="kt-icon"><use href="#edit"/></svg></button><button class="kt-ai-btn" data-variant="ghost" data-size="sm" title="Удалить" onclick="event.stopPropagation();askDelete(' + r.id + ')"><svg class="kt-icon"><use href="#trash"/></svg></button></div></td>';
|
||
html += '</tr>';
|
||
});
|
||
html += '</tbody></table></div>';
|
||
cont.innerHTML = html;
|
||
}
|
||
|
||
function renderLog() {
|
||
var cont = document.getElementById('logContainer');
|
||
if (!log.length) {
|
||
cont.innerHTML = '<div class="empty-state"><svg class="kt-icon"><use href="#file-text"/></svg><h3>Журнал пуст</h3><p>Здесь будет история всех изменений</p></div>';
|
||
return;
|
||
}
|
||
var html = '<div class="kt-ai-card"><div style="padding:var(--kt-ai-space-4)">';
|
||
log.forEach(function(e){
|
||
html += '<div class="log-entry"><div class="log-time">' + fmtDateTime(e.when) + '</div><div class="log-what">' + e.what + '</div><div class="log-who">' + e.who + '</div></div>';
|
||
});
|
||
html += '</div></div>';
|
||
cont.innerHTML = html;
|
||
}
|
||
|
||
function openDrawer(row) {
|
||
curRow = row;
|
||
var scfg = getStatusCfg(row.status);
|
||
document.getElementById('drawerTitle').textContent = row.title;
|
||
var html = '<div class="drawer-sec">';
|
||
CFG.drawer.fields.forEach(function(f){
|
||
var v = row[f.key];
|
||
if (f.key === 'status') {
|
||
html += '<div class="kt-ai-field"><label>' + f.label + '</label><div><span class="kt-ai-chip" data-tone="' + scfg.tone + '">' + v + '</span></div></div>';
|
||
} else {
|
||
html += '<div class="kt-ai-field"><label>' + f.label + '</label><div>' + v + '</div></div>';
|
||
}
|
||
});
|
||
html += '</div><div class="drawer-sec"><div class="kt-ai-field"><label>' + CFG.drawer.flagsLabel + '</label><div class="drawer-flags">';
|
||
html += '<div class="agent-signal"><svg class="kt-icon" style="color:var(--kt-ai-primary)"><use href="#check"/></svg><div>Заявка зарегистрирована</div></div>';
|
||
html += '<div class="agent-signal"><svg class="kt-icon" style="color:var(--kt-ai-primary)"><use href="#calendar"/></svg><div>Срок: ' + row.deadline + '</div></div>';
|
||
html += '</div></div></div>';
|
||
document.getElementById('drawerContent').innerHTML = html;
|
||
var actions = '<button class="kt-ai-btn" data-variant="ghost" onclick="closeDrawer()">Закрыть</button>';
|
||
actions += '<button class="kt-ai-btn" data-variant="primary" onclick="editRow(' + row.id + ');closeDrawer()">Редактировать</button>';
|
||
document.getElementById('drawerActions').innerHTML = actions;
|
||
document.getElementById('drawer').setAttribute('data-open', 'true');
|
||
}
|
||
|
||
function closeDrawer() {
|
||
document.getElementById('drawer').setAttribute('data-open', 'false');
|
||
curRow = null;
|
||
}
|
||
|
||
function openModal(id) {
|
||
editingId = id;
|
||
var row = data.find(function(r){ return r.id === id; });
|
||
document.getElementById('modalTitle').textContent = row ? 'Редактирование заявки' : 'Новая заявка';
|
||
if (row) {
|
||
document.getElementById('formPerson').value = row.person;
|
||
document.getElementById('formTitle').value = row.title;
|
||
document.getElementById('formDeadline').value = isoDate(row.deadline);
|
||
document.getElementById('formStatus').value = row.status;
|
||
} else {
|
||
document.getElementById('formPerson').value = '';
|
||
document.getElementById('formTitle').value = '';
|
||
document.getElementById('formDeadline').value = '';
|
||
document.getElementById('formStatus').value = 'new';
|
||
}
|
||
document.getElementById('modal').setAttribute('data-open', 'true');
|
||
}
|
||
|
||
function closeModal() {
|
||
document.getElementById('modal').setAttribute('data-open', 'false');
|
||
editingId = null;
|
||
}
|
||
|
||
function isoDate(ddmmyyyy) {
|
||
if (!ddmmyyyy || ddmmyyyy.length !== 10) return '';
|
||
var p = ddmmyyyy.split('.');
|
||
return p[2] + '-' + p[1] + '-' + p[0];
|
||
}
|
||
|
||
function saveForm() {
|
||
var person = document.getElementById('formPerson').value.trim();
|
||
var title = document.getElementById('formTitle').value.trim();
|
||
var deadline = document.getElementById('formDeadline').value;
|
||
var status = document.getElementById('formStatus').value;
|
||
if (!person || !title || !deadline) {
|
||
alert('Заполните все поля');
|
||
return;
|
||
}
|
||
var dObj = new Date(deadline);
|
||
var dStr = String(dObj.getDate()).padStart(2,'0') + '.' + String(dObj.getMonth()+1).padStart(2,'0') + '.' + dObj.getFullYear();
|
||
if (editingId) {
|
||
var idx = data.findIndex(function(r){ return r.id === editingId; });
|
||
if (idx >= 0) {
|
||
var old = data[idx];
|
||
data[idx] = { id: editingId, person: person, title: title, deadline: dStr, status: status };
|
||
addLog('Изменена заявка #' + editingId + ': ' + old.title + ' → ' + title);
|
||
}
|
||
} else {
|
||
var newId = data.length ? Math.max.apply(null, data.map(function(r){ return r.id; })) + 1 : 1;
|
||
data.push({ id: newId, person: person, title: title, deadline: dStr, status: status });
|
||
addLog('Добавлена заявка #' + newId + ': ' + title);
|
||
}
|
||
closeModal();
|
||
save();
|
||
}
|
||
|
||
function editRow(id) {
|
||
openModal(id);
|
||
}
|
||
|
||
function askDelete(id) {
|
||
var row = data.find(function(r){ return r.id === id; });
|
||
if (!row) return;
|
||
document.getElementById('confirmText').textContent = 'Удалить заявку "' + row.title + '"? Это действие будет записано в журнал.';
|
||
confirmCb = function() {
|
||
var idx = data.findIndex(function(r){ return r.id === id; });
|
||
if (idx >= 0) {
|
||
var del = data.splice(idx, 1)[0];
|
||
addLog('Удалена заявка #' + id + ': ' + del.title);
|
||
save();
|
||
}
|
||
document.getElementById('confirmOverlay').classList.remove('on');
|
||
confirmCb = null;
|
||
};
|
||
document.getElementById('confirmOverlay').classList.add('on');
|
||
}
|
||
|
||
function exportCsv() {
|
||
var rows = getFiltered();
|
||
var head = ['ФИО', 'Описание заявки', 'Срок', 'Статус'];
|
||
var body = rows.map(function(r){
|
||
return [r.person, r.title, r.deadline, getStatusCfg(r.status).label]
|
||
.map(function(v){ return '"' + String(v).replace(/"/g, '""') + '"'; })
|
||
.join(';');
|
||
});
|
||
var csv = '\uFEFF' + [head.join(';')].concat(body).join('\n');
|
||
var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||
var url = URL.createObjectURL(blob);
|
||
var a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = 'zayavki_' + new Date().toISOString().slice(0,10) + '.csv';
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
}
|
||
|
||
function render() {
|
||
renderKpis();
|
||
renderFilters();
|
||
renderTable();
|
||
if (document.getElementById('logContainer').style.display !== 'none') {
|
||
renderLog();
|
||
}
|
||
}
|
||
|
||
document.getElementById('statusFilter').addEventListener('click', function(e){
|
||
if (e.target.classList.contains('kt-ai-chip')) {
|
||
curFilter = e.target.getAttribute('data-filter');
|
||
render();
|
||
}
|
||
});
|
||
|
||
document.getElementById('createBtn').addEventListener('click', function(){
|
||
openModal(null);
|
||
});
|
||
|
||
document.getElementById('closeDrawer').addEventListener('click', closeDrawer);
|
||
document.getElementById('drawer').querySelector('.kt-ai-drawer-overlay').addEventListener('click', closeDrawer);
|
||
|
||
document.getElementById('closeModal').addEventListener('click', closeModal);
|
||
document.getElementById('cancelModal').addEventListener('click', closeModal);
|
||
document.getElementById('modal').querySelector('.kt-ai-modal-overlay').addEventListener('click', closeModal);
|
||
document.getElementById('saveBtn').addEventListener('click', saveForm);
|
||
|
||
document.getElementById('confirmCancel').addEventListener('click', function(){
|
||
document.getElementById('confirmOverlay').classList.remove('on');
|
||
confirmCb = null;
|
||
});
|
||
document.getElementById('confirmOk').addEventListener('click', function(){
|
||
if (confirmCb) confirmCb();
|
||
});
|
||
|
||
document.getElementById('logBtn').addEventListener('click', function(){
|
||
var tbl = document.getElementById('tableContainer');
|
||
var logC = document.getElementById('logContainer');
|
||
if (logC.style.display === 'none') {
|
||
tbl.style.display = 'none';
|
||
logC.style.display = 'block';
|
||
renderLog();
|
||
this.classList.add('on');
|
||
} else {
|
||
tbl.style.display = 'block';
|
||
logC.style.display = 'none';
|
||
this.classList.remove('on');
|
||
}
|
||
});
|
||
|
||
document.getElementById('exportBtn').addEventListener('click', exportCsv);
|
||
|
||
document.getElementById('themeToggle').addEventListener('click', function(){
|
||
var html = document.documentElement;
|
||
var cur = html.getAttribute('data-theme');
|
||
html.setAttribute('data-theme', cur === 'light' ? 'dark' : 'light');
|
||
this.innerHTML = '<svg class="kt-icon"><use href="' + (cur === 'light' ? '#moon' : '#sun') + '"/></svg>';
|
||
});
|
||
|
||
document.addEventListener('keydown', function(e){
|
||
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
||
e.preventDefault();
|
||
var inp = document.querySelector('.kt-ai-search-input');
|
||
if (inp) inp.focus();
|
||
}
|
||
});
|
||
|
||
var tableCont = document.getElementById('tableContainer');
|
||
tableCont.addEventListener('click', function(e){
|
||
var tr = e.target.closest('tr[data-id]');
|
||
if (tr && !e.target.closest('button')) {
|
||
var id = parseInt(tr.getAttribute('data-id'));
|
||
var row = data.find(function(r){ return r.id === id; });
|
||
if (row) openDrawer(row);
|
||
}
|
||
});
|
||
|
||
var searchField = document.querySelector('.kt-ai-search-field');
|
||
if (searchField) {
|
||
var inp = searchField.querySelector('input');
|
||
if (inp) {
|
||
inp.placeholder = CFG.search.placeholder;
|
||
inp.addEventListener('input', function(){
|
||
searchQ = this.value;
|
||
renderTable();
|
||
});
|
||
}
|
||
}
|
||
|
||
render();
|
||
})();
|
||
</script>
|
||
|
||
<link rel="stylesheet" href="design-system/kt-ai-fonts.css">
|
||
<link rel="stylesheet" href="design-system/kt-ai-page.css">
|
||
<script src="design-system/kt-ai-components.js"></script>
|
||
|
||
</body>
|
||
</html>
|