intra_monitor/public/index.html
2026-03-18 14:19:29 +03:00

310 lines
17 KiB
HTML
Raw 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" id="html-root">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Заявки — CleverParking</title>
<link rel="stylesheet" href="/css/app.css">
<link rel="stylesheet" href="/css/keyboard-additions.css">
<script>if(localStorage.getItem('theme')==='dark')document.documentElement.classList.add('dark');</script>
</head>
<body>
<div class="toast-wrap" id="toast-wrap"></div>
<!-- LOGIN -->
<div class="login-overlay" id="login-overlay">
<div class="login-box">
<div class="login-logo"><div class="login-logo-icon">//</div><div class="login-logo-text">Заявки</div></div>
<div class="login-sub">Сервисная поддержка · CleverParking</div>
<div class="login-field"><label>Логин</label><input type="text" id="login-username" autocomplete="username" placeholder="username"></div>
<div class="login-field"><label>Пароль</label><input type="password" id="login-password" autocomplete="current-password" placeholder="••••••"></div>
<button class="login-btn" onclick="doLogin()">Войти</button>
<div class="login-error" id="login-error"></div>
</div>
</div>
<!-- SETTINGS MODAL -->
<div class="settings-overlay" id="settings-modal" onclick="if(event.target===this)closeSettingsModal()">
<div class="settings-box">
<div class="settings-header">
<div class="settings-title">⚙ Настройки</div>
<button class="modal-close-btn" onclick="closeSettingsModal()">×</button>
</div>
<div class="settings-body">
<div class="settings-section-title">Подключение к IntraDesk API</div>
<div class="form-row">
<div class="form-field" style="flex:2;min-width:240px;">
<label>API Key <span id="apikey-status" style="color:var(--green);font-weight:500;text-transform:none;letter-spacing:0;"></span></label>
<input type="password" id="api-key" placeholder="Введите новый ключ для обновления">
</div>
<div class="form-field"><label>Статусы (ID через запятую)</label><input type="text" id="status-ids" placeholder="68051,68046,..."></div>
</div>
<div class="form-row">
<div class="form-field"><label>Фильтр событий (пусто = все)</label><input type="text" id="event-types" placeholder="50,55,10,30"></div>
<div class="form-field"><label>Автообновление (сек, 0=выкл)</label><input type="number" id="server-refresh-interval" placeholder="300" min="0"></div>
</div>
<button class="btn btn-primary" onclick="saveSettings()">💾 Сохранить</button>
<button class="btn btn-default" onclick="loadAllTasks()" id="refresh-all-btn" style="margin-left:8px;">🔄 Обновить ВСЕ заявки</button>
<span id="refresh-all-status" style="font-size:12px;color:var(--text3);margin-left:10px;"></span>
<hr class="divider">
<div class="settings-section-title">Управление пользователями</div>
<div class="form-row">
<div class="form-field"><label>Логин</label><input type="text" id="new-user-login" placeholder="username"></div>
<div class="form-field"><label>Пароль (мин. 6 символов)</label><input type="password" id="new-user-pass" placeholder="••••••"></div>
<div style="display:flex;align-items:flex-end;padding-bottom:1px;"><button class="btn btn-default" onclick="createUser()">+ Создать</button></div>
</div>
<div id="user-msg" style="font-size:12px;min-height:16px;margin-bottom:8px;"></div>
<table class="users-table">
<thead><tr><th>Логин</th><th>Создан</th><th>Новый пароль</th><th></th></tr></thead>
<tbody id="users-tbody"></tbody>
</table>
</div>
</div>
</div>
<!-- CONFIRM MODAL -->
<div class="confirm-overlay" id="confirm-modal" onclick="if(event.target===this)this.classList.remove('open')">
<div class="confirm-box">
<div class="confirm-title">Подтверждение</div>
<div class="confirm-msg">Отметить <span id="confirm-count">0</span> заявок как прочитанных?</div>
<div class="confirm-actions">
<button class="btn btn-primary" onclick="markAllRead();document.getElementById('confirm-modal').classList.remove('open')">Да</button>
<button class="btn btn-default" onclick="document.getElementById('confirm-modal').classList.remove('open')">Отмена</button>
</div>
</div>
</div>
<!-- APP -->
<div class="app-layout" id="app-shell" style="display:none;">
<!-- скрытые элементы для обратной совместимости -->
<span id="sb-email" style="display:none;"></span>
<span id="nav-badge-tasks" style="display:none;"></span>
<div class="main-area">
<div class="topbar">
<!-- Логотип -->
<div class="topbar-logo">
<div class="topbar-logo-icon">//</div>
<span class="topbar-logo-text">Заявки</span>
</div>
<div class="topbar-sep"></div>
<!-- Навигационные табы (рядом с логотипом) -->
<div class="topbar-nav-tabs">
<button class="nav-tab nav-tab-active" id="nav-btn-tasks" onclick="closeStatsPage()">
<svg width="13" height="13" fill="none" viewBox="0 0 16 16"><rect x="1" y="2" width="14" height="2.5" rx="1" fill="currentColor"/><rect x="1" y="6.75" width="14" height="2.5" rx="1" fill="currentColor"/><rect x="1" y="11.5" width="9" height="2.5" rx="1" fill="currentColor"/></svg>
Заявки
</button>
<button class="nav-tab" id="nav-btn-stats" onclick="openStatsPage()">
<svg width="13" height="13" fill="none" viewBox="0 0 16 16"><rect x="1" y="9" width="3" height="6" rx="1" fill="currentColor"/><rect x="6" y="5" width="3" height="10" rx="1" fill="currentColor"/><rect x="11" y="1" width="3" height="14" rx="1" fill="currentColor"/></svg>
Статистика
</button>
</div>
<div style="flex:1;"></div>
<!-- Мета -->
<div class="topbar-meta">
<span>Обновлено: <span id="last-update"></span></span>
<span>Загружено: <span id="total-loaded"></span></span>
</div>
<!-- Обновить -->
<button class="btn btn-primary" id="load-btn" onclick="loadTasks()" style="font-size:12px;padding:6px 14px;">↻ Обновить</button>
<!-- Уведомления -->
<div class="topbar-notif-wrap">
<button class="topbar-icon-btn" id="notif-btn" onclick="toggleMentionsDropdown()" title="Упоминания">
<svg width="14" height="14" fill="none" viewBox="0 0 16 16"><path d="M8 2a5 5 0 00-5 5v2l-1 2h12l-1-2V7a5 5 0 00-5-5z" stroke="currentColor" stroke-width="1.3"/><path d="M6.5 13a1.5 1.5 0 003 0" stroke="currentColor" stroke-width="1.3"/></svg>
<span class="topbar-notif-badge" id="notif-badge" style="display:none;">0</span>
</button>
<div class="mentions-dropdown" id="mentions-dropdown">
<div class="mentions-dd-header">🔔 Упоминания</div>
<div id="mentions-list"></div>
</div>
</div>
<!-- Пользователь -->
<div class="topbar-user">
<div class="user-avatar" id="sb-avatar" style="width:28px;height:28px;font-size:11px;flex-shrink:0;">?</div>
<span class="topbar-username" id="sb-username"></span>
</div>
<!-- Настройки (только admin) -->
<button class="topbar-icon-btn" id="nav-settings" onclick="openSettingsModal()" style="display:none;" title="Настройки">
<svg width="15" height="15" fill="none" viewBox="0 0 16 16"><circle cx="8" cy="8" r="2" stroke="currentColor" stroke-width="1.3"/><path d="M8 2v1M8 13v1M2 8h1M13 8h1M3.5 3.5l.7.7M11.8 11.8l.7.7M3.5 12.5l.7-.7M11.8 4.2l.7-.7" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
</button>
<!-- Тёмная тема -->
<button class="topbar-icon-btn" id="theme-toggle-btn" onclick="toggleTheme()" title="Переключить тему">
<svg id="theme-icon-light" width="15" height="15" fill="none" viewBox="0 0 16 16" style="display:none;"><circle cx="8" cy="8" r="3" stroke="currentColor" stroke-width="1.4"/><path d="M8 1v1.5M8 13.5V15M1 8h1.5M13.5 8H15M3.05 3.05l1.06 1.06M11.89 11.89l1.06 1.06M3.05 12.95l1.06-1.06M11.89 4.11l1.06-1.06" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
<svg id="theme-icon-dark" width="15" height="15" fill="none" viewBox="0 0 16 16"><path d="M13.5 10A6 6 0 016 2.5a6 6 0 100 11 6 6 0 007.5-3.5z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/></svg>
</button>
<!-- Выйти -->
<button class="topbar-logout-btn" onclick="doLogout()">Выйти</button>
</div>
<div class="content-area" style="position:relative;">
<div class="update-banner" id="update-banner">
🔄 Появились новые данные на сервере
<button class="update-banner-btn" onclick="reloadData()">Обновить</button>
</div>
<!-- STATISTICS PAGE -->
<div id="stats-page" style="display:none;flex-direction:column;position:absolute;inset:0;overflow-y:auto;background:var(--bg);z-index:10;">
<div id="stats-content"></div>
</div>
<!-- MAIN TASKS AREA -->
<div id="tasks-main-area" style="display:contents;">
<!-- TASKS SPLIT (list + side panel) -->
<div class="tasks-split">
<!-- TASKS PANEL -->
<div class="tasks-panel">
<!-- Filters -->
<div class="filter-area">
<!-- Tab bar: Все / Открытые / Выполненные / Пустые -->
<div class="tab-bar" id="main-tab-bar">
<button class="tab-btn tab-btn-active" id="tabbtn-all" onclick="setMainTab('all',this)">Все заявки <span class="tab-cnt" id="tcnt-all">0</span></button>
<button class="tab-btn" id="tabbtn-open" onclick="setMainTab('open',this)">Открытые <span class="tab-cnt" id="tcnt-open">0</span></button>
<button class="tab-btn" id="tabbtn-done" onclick="setMainTab('done',this)">Выполненные <span class="tab-cnt" id="tcnt-done">0</span></button>
<button class="tab-btn" id="tabbtn-empty" onclick="setMainTab('empty',this)">Пустые <span class="tab-cnt" id="tcnt-empty">0</span></button>
</div>
<div class="filter-row">
<select class="filter-select" id="date-filter" onchange="renderTable()">
<option value="0">Все даты</option>
<option value="today">Сегодня</option>
<option value="yesterday">Вчера</option>
<option value="3">3 дня</option>
<option value="7">Неделя</option>
<option value="14">2 недели</option>
<option value="30">Месяц</option>
</select>
<select class="filter-select" id="opt-page-size" onchange="onPageSizeChange()">
<option value="25">25 на стр.</option>
<option value="50" selected>50 на стр.</option>
<option value="100">100 на стр.</option>
<option value="250">250 на стр.</option>
<option value="500">500 на стр.</option>
<option value="0">Все</option>
</select>
<label style="display:flex;align-items:center;gap:6px;font-size:12px;color:var(--text3);cursor:pointer;white-space:nowrap;">
<input type="checkbox" id="opt-remote-only" onchange="renderTable()" style="accent-color:var(--accent);"> Удалённая техподдержка
</label>
<!-- Status filter dropdown -->
<div class="chip-dropdown-wrap" id="chip-dropdown-wrap">
<button class="chip-dropdown-btn" id="chip-dropdown-btn" onclick="toggleChipDropdown()">
<span id="chip-dropdown-label">Все статусы</span>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2 4l4 4 4-4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<div class="chip-dropdown-panel" id="chip-dropdown-panel">
<div class="chip-dd-item chip-dd-active" id="chipdd-all" onclick="toggleAllChip()">
<span class="chip-dd-dot" style="background:var(--accent)"></span>
<span class="chip-dd-lbl">Все</span>
<span class="chip-dd-cnt" id="chip-all-cnt">0</span>
</div>
<div id="mark-chips-container"></div>
<div class="chip-dd-item" id="chipdd-unmarked" onclick="chipClickLabel('unmarked')">
<span class="chip-dd-dot" style="background:var(--text2)"></span>
<span class="chip-dd-lbl">Без статуса</span>
<span class="chip-dd-cnt" id="chip-unmarked-cnt">0</span>
</div>
</div>
</div>
<input class="filter-search" type="text" id="search-input" placeholder="Поиск..." oninput="renderTable()">
</div>
</div>
<!-- Bulk bar -->
<div class="bulk-bar">
<label class="bulk-check-wrap"><input type="checkbox" id="check-all" onchange="toggleSelectAll(this)"> Выбрать все</label>
<button class="bulk-action-btn" onclick="promptMarkAllRead()">✓ Прочитано</button>
<button class="bulk-action-btn" onclick="exportCSV()">↓ CSV</button>
<div class="bulk-sel-info" id="bulk-sel-info" style="display:none;">
<span class="bulk-sel-count" id="bulk-sel-count">0 выбрано</span>
<span class="bulk-sep">·</span>
<span class="bulk-status-label">Статус:</span>
<!-- options generated from MARKS_CONFIG by initMarkChips() -->
<select class="filter-select bulk-status-sel" id="bulk-status-sel" onchange="bulkApplyStatusFromSelect(this)">
<option value="" disabled selected>— установить —</option>
<option value="clear">✕ Снять статус</option>
</select>
</div>
<div class="tf-info" id="tf-info"></div>
<!-- Column chooser -->
<!-- <div class="col-chooser-wrap">
<button class="col-chooser-btn" onclick="toggleColMenu(event)">
<svg width="12" height="12" fill="none" viewBox="0 0 12 12"><rect x="1" y="2" width="10" height="1.5" rx=".75" fill="currentColor"/><rect x="1" y="5.25" width="7" height="1.5" rx=".75" fill="currentColor"/><rect x="1" y="8.5" width="5" height="1.5" rx=".75" fill="currentColor"/></svg>
Колонки
</button>
<div class="col-menu" id="col-menu"></div>
</div> -->
</div>
<!-- Table -->
<div class="table-wrap" id="table-wrap">
<div class="state-box"><div class="spinner"></div><div class="msg">Загружаю заявки...</div></div>
</div>
<!-- Pagination -->
<div class="pagination-bar" id="pagination-bar" style="display:none;"></div>
</div><!-- /tasks-panel -->
<!-- DETAIL SIDE PANEL -->
<div class="detail-col hidden" id="detail-col">
<div id="detail-content" style="display:flex;flex-direction:column;height:100%;overflow:hidden;"></div>
</div>
</div><!-- /tasks-split -->
</div><!-- /tasks-main-area -->
</div><!-- /content-area -->
</div><!-- /main-area -->
</div><!-- /app-layout -->
<script src="/js/statuses.js"></script>
<script src="/js/state.js"></script>
<script src="/js/api.js"></script>
<script src="/js/sse.js"></script>
<script src="/js/mentions.js"></script>
<script src="/js/notes.js"></script>
<script src="/js/comments.js"></script>
<script src="/js/ui.js"></script>
<script src="/js/export.js"></script>
<script src="/js/auth.js"></script>
<script src="/js/diff.js"></script>
<script src="/js/keyboard.js"></script>
<script src="/js/statistics.js"></script>
<script>
// ── Theme ──────────────────────────────────────────────────────────
(function() {
if (localStorage.getItem('theme') === 'dark') {
document.documentElement.classList.add('dark');
}
})();
function updateThemeIcon() {
const dark = document.documentElement.classList.contains('dark');
const li = document.getElementById('theme-icon-light');
const di = document.getElementById('theme-icon-dark');
if (li) li.style.display = dark ? 'block' : 'none';
if (di) di.style.display = dark ? 'none' : 'block';
}
function toggleTheme() {
const dark = document.documentElement.classList.toggle('dark');
localStorage.setItem('theme', dark ? 'dark' : 'light');
updateThemeIcon();
}
window.addEventListener('DOMContentLoaded', updateThemeIcon);
// ── Stats page toggle ──────────────────────────────────────────────
function toggleStatsPage() {
if (statsPageOpen) closeStatsPage();
else openStatsPage();
const btn = document.getElementById('stats-nav-btn');
if (btn) btn.classList.toggle('active', statsPageOpen);
}
</script>
</body>
</html>