Карточки заявок обновляются сами: отпечаток состояния, перерисовка без ручного обновления, индикатор «обновляется автоматически»

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
This commit is contained in:
zuevav
2026-09-25 16:07:11 +03:00
parent 27c350a087
commit a0f6ce9f7b
4 changed files with 85 additions and 8 deletions
@@ -35,7 +35,7 @@
<b>Перевод не доставлен в НРД</b> — {{sendFailText .}}.<br>
<span class="muted">Попытка {{.At.Format "02.01.2006 15:04:05"}}{{if .PackageID}}, пакет ИШ {{.PackageID}}{{end}}{{if gt .Attempts 1}}, попыток: {{.Attempts}}{{end}}. Отправка в DEPO не отмечена, блокировка в QUIK МНП сохраняется. Когда НРД снова будет доступен, нажмите «Отправить повторно» — уйдёт тот же запрос с тем же GUID.</span>
</div>{{end}}
{{$l := liveOut .Claim}}<div class="live live-big live-{{$l.Kind}}" data-live="out:{{.Claim.ID}}"{{if $l.Active}} data-active="1"{{else}} title="Завершено: состояние больше не проверяется"{{end}}>{{$l.Text}}</div>
{{$l := liveOut .Claim}}<div class="live live-big live-{{$l.Kind}}" data-live="out:{{.Claim.ID}}" data-sig="{{$l.Sig}}"{{if $l.Active}} data-active="1"{{else}} title="Завершено: состояние больше не проверяется"{{end}}>{{$l.Text}}</div>
<table>
<tr><td style="width:200px" class="muted">Создана</td><td>{{.Claim.CreatedAt.Format "02.01.2006 15:04:05"}}</td></tr>
<tr><td class="muted">Обновлена</td><td>{{.Claim.UpdatedAt.Format "02.01.2006 15:04:05"}}</td></tr>
@@ -21,7 +21,7 @@
<h2>Входящий перевод <code>{{short 8 .It.GUID}}</code>
{{if $answered}}<span class="badge confirmed">отвечено</span>{{else}}<span class="badge awaiting_decision">ждёт решения</span>{{end}}
</h2>
{{$l := liveIn .It}}<div class="live live-big live-{{$l.Kind}}" data-live="in:{{.It.ID}}"{{if $l.Active}} data-active="1"{{else}} title="Завершено: состояние больше не проверяется"{{end}}>{{$l.Text}}</div>
{{$l := liveIn .It}}<div class="live live-big live-{{$l.Kind}}" data-live="in:{{.It.ID}}" data-sig="{{$l.Sig}}"{{if $l.Active}} data-active="1"{{else}} title="Завершено: состояние больше не проверяется"{{end}}>{{$l.Text}}</div>
<table>
<tr><td style="width:220px" class="muted">Запрос сформирован (отправитель)</td><td>{{msk .It.RequestCreated}}</td></tr>
<tr><td class="muted">Забрали из шлюза (получено нами)</td><td>{{msk .It.ReceivedAt}}{{if not .It.RequestCreated.IsZero}} <span class="muted" style="font-size:12px">· прохождение: {{transit .It.RequestCreated .It.ReceivedAt}}</span>{{end}}</td></tr>
+35 -2
View File
@@ -158,6 +158,12 @@ tbody tr:hover td { background:var(--accent-weak); }
.live-bad { color:var(--err) } .live-bad::before { background:var(--err) }
.live-big { font-size:14px; margin:-6px 0 12px; font-weight:500 }
.live[data-active] { color:var(--text-2) } .live-warn[data-active] { color:var(--warn) }
.live-meta { font-size:12px; color:var(--muted); margin:2px 0 10px; display:flex; align-items:center; gap:6px; }
.live-meta .dot { width:7px; height:7px; border-radius:50%; background:var(--ok,#2e7d32); display:inline-block; animation:livedot 2s ease-in-out infinite; }
.live-meta.changed { color:var(--warn,#8a5a00); }
.live-meta.lost { color:var(--err,#b3261e); }
@keyframes livedot { 50% { opacity:.3 } }
@media (prefers-reduced-motion: reduce) { .live-meta .dot { animation:none } }
.badge.draft, .badge.validated, .badge.submitted_to_nsd { background:var(--accent-weak); color:var(--accent); }
.badge.awaiting_decision, .badge.manual_approval { background:var(--warn-weak); color:var(--warn); }
.badge.confirmed, .badge.awaiting_sub16, .badge.done { background:var(--ok-weak); color:var(--ok); }
@@ -550,7 +556,22 @@ label { font-size:13px; font-weight:500; }
poll();
setInterval(poll, 12000); // каждые 12 сек
// Онлайн-статус заявок в работе: строки с data-live и data-active опрашиваются, пока ждут.
// Карточка заявки (live-big с data-sig): изменилось что-то кроме строки статуса — страница
// перерисовывается сама; если оператор уже начал заполнять форму — только предложение обновить.
(function(){
var dirty = false;
document.addEventListener('input', function(e){ if (e.target.closest('main form')) dirty = true; }, true);
document.addEventListener('change', function(e){ if (e.target.closest('main form')) dirty = true; }, true);
try { var u = new URL(location.href); if (u.searchParams.has('flash')) { u.searchParams.delete('flash'); history.replaceState(null, '', u.pathname + u.search + u.hash); } } catch(e){}
function hhmmss(){ var d = new Date(); return ('0'+d.getHours()).slice(-2)+':'+('0'+d.getMinutes()).slice(-2)+':'+('0'+d.getSeconds()).slice(-2); }
function meta(e, text, cls){
var m = e.nextElementSibling;
if (!m || !m.classList.contains('live-meta')) { m = document.createElement('div'); m.className = 'live-meta'; e.parentNode.insertBefore(m, e.nextSibling); }
m.className = 'live-meta' + (cls ? ' ' + cls : ''); m.innerHTML = text;
}
document.querySelectorAll('.live-big[data-live]').forEach(function(e){
meta(e, e.hasAttribute('data-active') ? '<span class="dot"></span>обновляется автоматически' : 'заявка завершена — автообновление не требуется');
});
function tick(){
var els = document.querySelectorAll('.live[data-live][data-active]');
if (!els.length) return;
@@ -561,11 +582,23 @@ label { font-size:13px; font-weight:500; }
els.forEach(function(e){
var k = e.getAttribute('data-live').split(':'), st = (d[k[0]]||{})[k[1]];
if (!st) return;
var big = e.classList.contains('live-big'), was = e.getAttribute('data-sig');
if (big && was && st.sig && st.sig !== was) {
if (!dirty) { location.reload(); return; }
meta(e, 'Заявка изменилась. <a href="' + location.pathname + location.search + '">Обновить страницу</a> (отмеченное в форме не сохранится)', 'changed');
e.setAttribute('data-sig', st.sig);
}
e.textContent = st.text;
e.className = 'live ' + (e.classList.contains('live-big') ? 'live-big ' : '') + 'live-' + st.kind;
e.className = 'live ' + (big ? 'live-big ' : '') + 'live-' + st.kind;
if (!st.active) e.removeAttribute('data-active');
var nx = e.nextElementSibling;
if (big && !(nx && nx.classList.contains('changed'))) {
meta(e, st.active ? '<span class="dot"></span>обновляется автоматически · проверено в ' + hhmmss() : 'заявка завершена — автообновление не требуется');
}
});
}).catch(function(){});
}).catch(function(){
document.querySelectorAll('.live-big[data-live][data-active]').forEach(function(e){ meta(e, 'нет связи с bj — статус не обновляется, повторим через несколько секунд', 'lost'); });
});
}
setInterval(tick, 8000);
if (document.querySelector('.live[data-live][data-active]')) setTimeout(tick, 1500);