feat(shorts): короткие сообщения резидентов с ИИ-коррекцией

Таблица short_messages (резидент, дата занятия, тема, текст, отметка
об использовании), CRUD API /api/shorts с мягким удалением и плановой
очисткой через purge_at, ИИ-коррекция текста по профилю, страница
shorts.html с массовым добавлением разбором строк «ФИО - текст».
This commit is contained in:
dev
2026-10-05 23:51:40 +03:00
parent 62239a35a4
commit ee09958644
6 changed files with 750 additions and 4 deletions
+135
View File
@@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Короткие сообщения — Админ-панель</title>
<link rel="stylesheet" href="admin.css">
</head>
<body data-page="shorts">
<div class="layout">
<div class="sidebar" id="sidebar"></div>
<div class="main">
<div class="page-head">
<h2>Короткие сообщения</h2>
<p class="page-sub">Создание и редактирование сообщений</p>
</div>
<div class="actions-row" style="margin-bottom:16px">
<button class="btn-primary" id="addShortsBtn" type="button">
<span style="font-size:1.2rem">+</span> Новое сообщение
</button>
</div>
<div class="actions-row" style="margin-bottom:12px">
<input type="text" id="shortsSearch" class="settings-input" placeholder="Поиск по резиденту, теме, сообщению..." style="max-width:360px">
<button class="btn-primary ghost" id="shortsSearchBtn">Найти</button>
<button class="btn-primary ghost" id="shortsClearBtn">Сброс</button>
</div>
<div class="audit-wrap">
<table class="audit-table" id="shortsTable">
<thead>
<tr>
<th>Резидент</th>
<th>Дата занятия</th>
<th>Тема</th>
<th>Сообщение</th>
<th>Автор</th>
<th>Использовано</th>
<th>Действия</th>
</tr>
</thead>
<tbody></tbody>
</table>
<div class="empty" id="shortsEmpty" style="display:none">Сообщения не найдены</div>
</div>
<div id="shortsPager" class="pager"></div>
</div>
</div>
<div class="modal-overlay" id="shortsModal">
<div class="edit-modal" style="max-width:620px">
<h3 id="shortsModalTitle">Новое сообщение</h3>
<div class="settings-stack" style="gap:12px;max-width:none">
<div class="settings-field">
<label>Дата занятия <span style="color:#ef4444">*</span></label>
<input type="date" id="shortsDate" class="settings-input">
</div>
<div class="settings-field">
<label>Тема занятия</label>
<input type="text" id="shortsTopic" class="settings-input" placeholder="Например: Photoshop 3/5">
</div>
<div class="settings-field">
<label>Список строк <span style="color:#ef4444">*</span>
<span id="shortsLinesHint" style="font-size:.7rem;color:var(--muted);font-weight:400"> (формат: ФИО - текст или ФИО — текст)</span>
<button type="button" id="shortsAiBtn" class="ai-btn" title="Улучшить текст ИИ"><i data-lucide="sparkles" style="width:14px;height:14px"></i></button>
</label>
<textarea id="shortsLines" class="settings-input" rows="5" placeholder="Иванов И.И. - Отличная работа на занятии&#10;Петров П.П. — Продолжили изучение Photoshop"></textarea>
</div>
<div class="settings-field">
<label>Резидент (для добавления без разбора строк)</label>
<input type="text" id="shortsResident" class="settings-input" placeholder="Иванов И.И.">
</div>
<div class="settings-field" style="flex-direction:row;align-items:center;gap:8px">
<input type="checkbox" id="shortsUsed">
<label style="margin:0">Использовано</label>
</div>
<div class="settings-field">
<label>Дата использования</label>
<input type="date" id="shortsUsedAt" class="settings-input">
</div>
<div class="card-foot" style="justify-content:flex-end;border-top:none;padding-top:0">
<button type="button" class="btn-primary ghost" id="shortsCancel">Отмена</button>
<button type="button" class="btn-primary" id="shortsSave">Сохранить</button>
</div>
</div>
</div>
</div>
<div class="modal-overlay" id="aiCorrectModal">
<div class="edit-modal" style="max-width:560px">
<h3><i data-lucide="sparkles" style="width:18px;height:18px;vertical-align:-3px"></i> Улучшение текста ИИ</h3>
<div style="font-size:.85rem;color:var(--muted);margin-bottom:8px">Выберите профиль и нажмите «Улучшить»</div>
<div class="settings-field">
<label>Профиль</label>
<select id="aiModelSelect" class="settings-input">
</select>
</div>
<div class="settings-field">
<label>Текст</label>
<textarea id="aiSourceText" class="settings-input" rows="4" placeholder="Текст для улучшения..."></textarea>
</div>
<div class="settings-field">
<button type="button" class="btn-primary ghost" id="aiImproveBtn">Улучшить</button>
</div>
<div class="settings-field">
<label>Результат</label>
<div id="aiSuggestion" style="background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:12px;min-height:60px;max-height:250px;overflow:auto;white-space:pre-wrap;font-family:inherit"></div>
</div>
<div class="card-foot" style="justify-content:flex-end;gap:8px;margin-top:12px">
<button type="button" class="btn-primary ghost" id="aiRejectBtn">Отклонить</button>
<button type="button" class="btn-primary" id="aiAcceptBtn">Применить</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script src="vendor/lucide.min.js"></script>
<script src="js/datetime.js"></script>
<script src="admin.js"></script>
<script src="js/shorts.js"></script>
<style>
#shortsModal .settings-field label { font-weight:600 }
.used-check { width:18px;height:18px;accent-color:var(--accent);cursor:pointer }
.ai-btn { background:none;border:1px solid var(--border);border-radius:6px;padding:4px 10px;cursor:pointer;font-size:.8rem;color:var(--muted) }
.ai-btn:hover { border-color:var(--accent);color:var(--accent) }
.ai-btn:disabled { opacity:.5;cursor:not-allowed }
.ai-status { font-size:.72rem;color:var(--muted);margin-top:4px }
.row-btn { background:none;border:none;color:var(--muted);cursor:pointer;font-size:.95rem;padding:4px 8px;border-radius:6px;margin-left:4px }
.row-btn:hover { background:var(--bg);color:var(--accent) }
.row-btn.del:hover { color:#ef4444 }
</style>
</body>
</html>