samruk-ai-agent/viewer.html

142 lines
7.5 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>Просмотр бекапа ИИ-Агент ПБ</title>
<style>
:root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7;--gray-200:#E5E7EB;--green:#10B981;--red:#EF4444;--amber:#F59E0B}
*{box-sizing:border-box;margin:0;padding:0}
body{font:14px/1.5 Arial,sans-serif;color:var(--ink);background:var(--gray-100);padding:20px}
.container{max-width:960px;margin:0 auto}
h1{font-size:22px;margin-bottom:16px}h1 span{color:var(--cyan)}
.drop-zone{border:3px dashed var(--cyan);border-radius:12px;padding:50px 24px;text-align:center;cursor:pointer;background:var(--white);margin-bottom:20px}
.drop-zone:hover{background:var(--cyan-50)}
.drop-zone .icon{font-size:40px;display:block;margin-bottom:8px}
.drop-zone .txt{font-size:16px;font-weight:600;margin-bottom:4px}
.drop-zone .hint{font-size:13px;color:var(--gray-500)}
.drop-zone input{display:none}
.ev{border:1px solid var(--gray-200);border-radius:10px;padding:18px;margin-bottom:14px;background:var(--white)}
.ev h3{font-size:15px;margin-bottom:6px}
.meta{display:flex;gap:12px;flex-wrap:wrap;font-size:12px;color:var(--gray-500);margin-bottom:6px}
.meta strong{color:var(--ink)}
.badge{display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:700}
.g{background:#D1FAE5;color:#065F46}.a{background:#FEF3C7;color:#92400E}.r{background:#FEE2E2;color:#991B1B}.w{background:#eee;color:#666}
.month{background:var(--gray-100);padding:10px 14px;border-radius:6px;margin:6px 0}
.month strong{font-size:12px}.month .rep{font-size:13px;color:var(--gray-500);margin:2px 0}
.file-item{display:flex;align-items:center;gap:8px;padding:3px 0;font-size:13px}
.file-item a{color:var(--ink);font-weight:600;text-decoration:underline}.file-item a:hover{color:var(--cyan)}
.file-item .sz{font-size:11px;color:var(--gray-500)}
.sub-blk{border-left:3px solid var(--cyan);padding-left:14px;margin:6px 0 6px 16px}
.sub-blk strong{font-size:12px}
.err{color:var(--red);text-align:center;padding:20px}
.empty{text-align:center;padding:40px;color:var(--gray-500)}
</style>
</head>
<body>
<div class="container">
<h1><span>📂 Просмотр бекапа</span> ИИ-Агент ПБ</h1>
<div class="drop-zone" id="dropZone">
<span class="icon">📥</span>
<div class="txt">Нажмите или перетащите файл бекапа</div>
<div class="hint">Файл backup_pb_*.json из папки Загрузки</div>
<input type="file" id="fileInput" accept=".json">
</div>
<div id="result"></div>
</div>
<script>
var sm={done:"Исполнено",warn:"На контроле",late:"Просрочено",wait:"В процессе"};
var sec=["I. Люди","II. Оборудование","III. Аварии и ЧС","IV. Информ. работа","V. ИИ и цифровизация"];
var br=["Дирекция ПБ","Дивизион Сеть","Корп. бизнес","Розн. бизнес","Сервисная фабрика","Телеком Комплект","Корп. университет","Упр. проектами","Цифровой бизнес"];
var mn=["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"];
function esc(s){return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}
function M(k){var p=k.split("-");return mn[parseInt(p[1])-1]+" "+p[0]}
var dz=document.getElementById("dropZone"),fi=document.getElementById("fileInput");
dz.addEventListener("click",function(){fi.click()});
fi.addEventListener("change",function(){if(this.files.length)processFile(this.files[0])});
dz.addEventListener("dragover",function(e){e.preventDefault();dz.style.background="var(--cyan-50)"});
dz.addEventListener("dragleave",function(){dz.style.background="var(--white)"});
dz.addEventListener("drop",function(e){e.preventDefault();dz.style.background="var(--white)";if(e.dataTransfer.files.length)processFile(e.dataTransfer.files[0])});
function processFile(file){
if(!file){showErr("Файл не выбран");return}
if(!file.name.endsWith(".json")){showErr("Выберите .json файл");return}
var r=new FileReader();
r.onload=function(ev){
try{
var data=JSON.parse(ev.target.result);
if(!data.events){showErr("Неверный формат: нет поля events");return}
render(data);
}catch(e){showErr("Ошибка чтения JSON: "+e.message)}
};
r.onerror=function(){showErr("Не удалось прочитать файл")};
r.readAsText(file);
}
function showErr(msg){document.getElementById("result").innerHTML='<div class="err">❌ '+esc(msg)+'</div>'}
function getFiles(data,id,si){
var k=si>=0?"sf_"+id+"_s"+si:"sf_"+id;
if(!data.files||!data.files[k])return{};
var raw=data.files[k];
if(typeof raw==="string"){try{return JSON.parse(raw)}catch(e){return{}}}
return raw;
}
function render(data){
var h="";
h+="<p style='margin-bottom:16px;color:var(--gray-500)'>Дата: <strong>"+(data.date?new Date(data.date).toLocaleString():"—")+"</strong> · Мероприятий: <strong>"+data.events.length+"</strong></p>";
data.events.forEach(function(e){
var cl={done:"g",warn:"a",late:"r",wait:"w"}[e.s]||"w";
h+="<div class='ev'><h3>"+e.id+". "+esc(e.t)+"</h3>";
h+="<div class='meta'><span>Раздел: <strong>"+sec[e.sec]+"</strong></span><span>Дивизион: <strong>"+br[e.b]+"</strong></span><span>Срок: <strong>"+e.due+"</strong></span><span>Факт: <strong>"+(e.done||"—")+"</strong></span><span>Прогресс: <strong>"+e.p+"%</strong></span><span class='badge "+cl+"'>"+sm[e.s]+"</span></div>";
// Main event files
var fd=getFiles(data,e.id,-1);
var keys=Object.keys(fd).sort();
keys.forEach(function(key){
var d=fd[key];
if(!d.report&&(!d.files||!d.files.length))return;
h+="<div class='month'><strong>"+M(key)+"</strong>";
if(d.report)h+="<div class='rep'>"+esc(d.report)+"</div>";
if(d.files&&d.files.length)d.files.forEach(function(f){
h+="<div class='file-item'>"+(f.data?"<a href='"+f.data+"' download='"+esc(f.name)+"'>📄 "+esc(f.name)+"</a>":"📄 "+esc(f.name))+"<span class='sz'>"+(f.desc?esc(f.desc)+" · ":"")+(f.size?(f.size/1024).toFixed(0)+" КБ":"")+"</span></div>";
});
h+="</div>";
});
// Sub-items
if(e.sub)e.sub.forEach(function(s,i){
var sd=getFiles(data,e.id,i);
var skeys=Object.keys(sd).sort(),hasSub=false;
skeys.forEach(function(k){if(sd[k]&&(sd[k].report||(sd[k].files&&sd[k].files.length)))hasSub=true});
if(!hasSub)return;
h+="<div class='sub-blk'><strong>"+s.l+") "+esc(s.t)+"</strong>";
skeys.forEach(function(key){
var d=sd[key];
if(!d.report&&(!d.files||!d.files.length))return;
h+="<div class='month'><strong>"+M(key)+"</strong>";
if(d.report)h+="<div class='rep'>"+esc(d.report)+"</div>";
if(d.files&&d.files.length)d.files.forEach(function(f){
h+="<div class='file-item'>"+(f.data?"<a href='"+f.data+"' download='"+esc(f.name)+"'>📄 "+esc(f.name)+"</a>":"📄 "+esc(f.name))+"<span class='sz'>"+(f.desc?esc(f.desc)+" · ":"")+(f.size?(f.size/1024).toFixed(0)+" КБ":"")+"</span></div>";
});
h+="</div>";
});
h+="</div>";
});
h+="</div>";
});
document.getElementById("result").innerHTML=h||"<div class='empty'>Нет данных</div>";
dz.style.display="none";
}
</script>
</body>
</html>