v33: регионы как измерение отчётности — выбор региона в модалке
This commit is contained in:
parent
c7a3911abc
commit
53513321ba
97
index.html
97
index.html
@ -149,7 +149,6 @@ td{border-bottom:1px solid var(--gray-200)}tr:hover td{background:var(--cyan-50)
|
||||
var sections=["I. Люди","II. Оборудование","III. Аварии и ЧС","IV. Информ. работа","V. ИИ и цифровизация"];
|
||||
var branches=["Дирекция производственной безопасности","Дивизион «Сеть»","Дивизион по корпоративному бизнесу","Дивизион по розничному бизнесу","Сервисная фабрика","Дирекция «Телеком Комплект»","Корпоративный университет","Дирекция управления проектами","Дивизион цифрового бизнеса"];
|
||||
var regions=["Центральный регион","Алматинский регион","Южный регион","Северный регион","Восточный регион","Западный регион"];
|
||||
var branchRegion=[0,3,1,1,2,3,4,0,5]; // branch index -> region index
|
||||
var statusMap={done:"Исполнено",warn:"На контроле",late:"Просрочено",wait:"В процессе"};
|
||||
var months=["2026-01","2026-02","2026-03","2026-04","2026-05","2026-06","2026-07","2026-08","2026-09","2026-10","2026-11","2026-12"];
|
||||
var monthNames=["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"];
|
||||
@ -177,7 +176,7 @@ var users={
|
||||
"east@telecom.kz":{name:"Отв. Восточный регион",branch:6,role:"region",region:4},
|
||||
"west@telecom.kz":{name:"Отв. Западный регион",branch:8,role:"region",region:5}
|
||||
};
|
||||
var curUser=null,curMonth=5,curFilter=null; // curFilter: null=all, {type:"branch",id:N} or {type:"region",id:N}
|
||||
var curUser=null,curMonth=5,curRegion=0,editSubIdx=-1;
|
||||
|
||||
// Load events
|
||||
var events=null;
|
||||
@ -429,22 +428,70 @@ function renderAnalytics(){
|
||||
// ===== EDIT MODAL =====
|
||||
var editSubIdx = -1; // -1=none, click 📎 to expand
|
||||
|
||||
function openEdit(id, mi, si){
|
||||
function openEdit(id, mi, ri, si){
|
||||
if(typeof mi==="number")curMonth=mi;
|
||||
if(typeof ri==="number")curRegion=ri;
|
||||
editSubIdx = (typeof si==="number") ? si : -1; // reset to -1 if not specified
|
||||
var e=null;for(var i=0;i<events.length;i++){if(events[i].id===id){e=events[i];break}}if(!e)return;
|
||||
var hasSub = e.sub && e.sub.length;
|
||||
var cm=months[curMonth];
|
||||
var sc=getSC(e.id);
|
||||
|
||||
// Main event data
|
||||
var md=getMD(e.id,-1);
|
||||
var cd=md[cm]||{report:"",files:[]}, cfs=cd.files||[];
|
||||
var md=getMD(e.id,curRegion,-1), cd=md[cm]||{report:"",files:[]}, cfs=cd.files||[];
|
||||
|
||||
var html='<button class="close" onclick="closeEM()">×</button>';
|
||||
html+='<span class="badge blue">Раздел '+["I","II","III","IV","V"][e.sec]+'</span>';
|
||||
html+='<h3 style="margin:8px 0">'+esc(e.t)+'</h3>';
|
||||
html+='<div class="meta-row"><div class="fld">Регион<strong>'+regions[branchRegion[e.b]]+'</strong></div><div class="fld">Дивизион<strong>'+esc(branches[e.b])+'</strong></div><div class="fld">Ответственный<strong>'+esc(e.r)+'</strong></div><div class="fld">Срок<strong>'+e.due+'</strong></div><div class="fld">Факт<strong>'+e.done+'</strong></div></div>';
|
||||
html+='<div class="meta-row"><div class="fld">Дивизион<strong>'+esc(branches[e.b])+'</strong></div><div class="fld">Ответственный<strong>'+esc(e.r)+'</strong></div><div class="fld">Срок<strong>'+e.due+'</strong></div><div class="fld">Факт<strong>'+e.done+'</strong></div></div>';
|
||||
html+='<div class="field"><label>Статус</label><select id="es"><option value="wait"'+(e.s==="wait"?" selected":"")+'>В процессе</option><option value="warn"'+(e.s==="warn"?" selected":"")+'>На контроле</option><option value="late"'+(e.s==="late"?" selected":"")+'>Просрочено</option><option value="done"'+(e.s==="done"?" selected":"")+'>Исполнено</option></select></div>';
|
||||
html+='<div class="field"><label>Прогресс (%)</label><input type="range" id="ep" min="0" max="100" value="'+e.p+'" oninput="document.getElementById(\'pv\').textContent=this.value+\'%\'"><span id="pv" style="font-weight:700">'+e.p+'%</span></div>';
|
||||
html+='<div class="field"><label>Комментарий</label><textarea id="ec" placeholder="Комментарий..."></textarea></div>';
|
||||
|
||||
// Month tabs + Region tabs
|
||||
var mh='<div class="month-tabs">';months.forEach(function(m,i){mh+='<span class="month-tab'+(i===curMonth?" active":"")+'" onclick="openEdit('+e.id+','+i+','+curRegion+','+editSubIdx+')">'+M(i)+'</span>'});mh+='</div>';
|
||||
var rh='<div class="month-tabs" style="margin-bottom:12px"><span style="font-weight:600;font-size:13px;margin-right:8px">Регион:</span>';
|
||||
regions.forEach(function(r,i){rh+='<span class="month-tab'+(i===curRegion?" active":"")+'" onclick="openEdit('+e.id+','+curMonth+','+i+','+editSubIdx+')">'+r+'</span>'});rh+='</div>';
|
||||
|
||||
// Main event files (for selected region)
|
||||
html+='<div style="border-top:1px solid var(--gray-200);padding-top:16px;margin-top:12px"><div style="font-weight:600;font-size:14px;margin-bottom:8px">📎 Отчётность — '+regions[curRegion]+'</div>';
|
||||
html+=mh;html+=rh;
|
||||
html+='<div class="field"><label>Текст отчёта за '+M(curMonth)+' ('+regions[curRegion]+')</label><textarea id="mr" placeholder="Опишите ход исполнения... Можно без файлов." style="min-height:80px">'+esc(cd.report||"")+'</textarea></div>';
|
||||
cfs.forEach(function(f,i){html+='<div class="file-row"><span class="file-info"><span class="file-name" onclick="dlF('+e.id+','+curMonth+','+i+','+curRegion+',-1)">📄 '+esc(f.name)+'</span>'+(f.desc?'<span class="file-desc">'+esc(f.desc)+'</span>':'')+'</span><span class="file-meta">'+(f.size/1024).toFixed(0)+' КБ · '+f.date+'</span><button class="file-del" onclick="rmF('+e.id+','+curMonth+','+i+','+curRegion+',-1)">×</button></div>'});
|
||||
html+='<div class="upload-row"><input type="text" id="fd" placeholder="Описание файла"><input type="file" id="fi" multiple style="max-width:220px"><button class="btn btn-sm" id="ub" onclick="uploadFiles('+e.id+','+curMonth+','+curRegion+',-1)">📤 Загрузить</button></div>';
|
||||
html+='<p style="font-size:11px;color:var(--gray-500);margin-top:6px">Формы завершения: '+esc(e.dname)+'</p></div>';
|
||||
|
||||
// Sub-items
|
||||
var hasSub = e.sub && e.sub.length;
|
||||
if(hasSub){
|
||||
html+='<div style="border-top:2px solid var(--cyan);padding-top:16px;margin-top:16px"><div style="font-weight:700;font-size:15px;margin-bottom:4px">📋 Подпункты</div><p style="font-size:12px;color:var(--gray-500);margin-bottom:12px">Нажмите 📎 для управления файлами подпункта</p>';
|
||||
e.sub.forEach(function(s,i){
|
||||
var ch=sc.indexOf(i)>=0,sd=getMD(e.id,curRegion,i),scd=sd[cm]||{report:"",files:[]},scfs=scd.files||[];
|
||||
var isActive=editSubIdx===i;
|
||||
html+='<div class="sub-item" style="flex-wrap:wrap;padding:12px 14px;margin-bottom:8px;'+(isActive?'border:2px solid var(--cyan)':'')+'">';
|
||||
html+='<input type="checkbox" id="sc_'+i+'" '+(ch?"checked":"")+'><span class="sub-label" style="font-size:16px">'+s.l+')</span><span class="sub-text" style="flex:1">'+esc(s.t)+'</span>';
|
||||
html+='<span style="font-size:11px;color:var(--gray-500);margin-right:8px">Файлов: '+scfs.length+'</span>';
|
||||
html+='<button class="btn btn-sm" onclick="openEdit('+e.id+','+curMonth+','+curRegion+','+i+')" style="font-size:12px;'+(isActive?'background:var(--cyan);font-weight:700':'')+'">'+(isActive?'📂 Открыто':'📎 Файлы')+'</button>';
|
||||
html+='</div>';
|
||||
if(isActive){
|
||||
html+='<div style="margin-left:20px;margin-bottom:16px;padding:16px;background:var(--cyan-50);border-radius:8px;border:2px solid var(--cyan)">';
|
||||
html+='<div style="font-weight:700;font-size:14px;margin-bottom:4px">'+s.l+') '+esc(s.t)+'</div>';
|
||||
html+='<div style="font-size:11px;color:var(--gray-500);margin-bottom:12px">Файлы подпункта: '+regions[curRegion]+' · '+M(curMonth)+'</div>';
|
||||
html+='<div class="field"><label>Текст отчёта</label><textarea id="mr_s'+i+'" placeholder="Опишите ход исполнения..." style="min-height:60px">'+esc(scd.report||"")+'</textarea></div>';
|
||||
if(scfs.length)html+='<div style="font-weight:600;font-size:13px;margin:8px 0">Файлы ('+scfs.length+'):</div>';
|
||||
scfs.forEach(function(f,fi){html+='<div class="file-row"><span class="file-info"><span class="file-name" onclick="dlF('+e.id+','+curMonth+','+fi+','+curRegion+','+i+')">📄 '+esc(f.name)+'</span>'+(f.desc?'<span class="file-desc">'+esc(f.desc)+'</span>':'')+'</span><span class="file-meta">'+(f.size/1024).toFixed(0)+' КБ · '+f.date+'</span><button class="file-del" onclick="rmF('+e.id+','+curMonth+','+fi+','+curRegion+','+i+')">×</button></div>'});
|
||||
html+='<div class="upload-row"><input type="text" id="fd_s'+i+'" placeholder="Описание файла"><input type="file" id="fi_s'+i+'" multiple style="max-width:180px"><button class="btn btn-sm" id="ub_s'+i+'" onclick="uploadFiles('+e.id+','+curMonth+','+curRegion+','+i+')">📤 Загрузить</button></div>';
|
||||
html+='</div>';
|
||||
}
|
||||
});
|
||||
html+='</div>';
|
||||
}
|
||||
|
||||
html+='<div class="ai-block"><h4>🤖 Вывод ИИ-агента</h4>'+esc(e.ai)+'</div>';
|
||||
html+='<div style="font-weight:600;margin:8px 0 4px">История:</div><div>';e.h.forEach(function(h){html+='<div class="history-item"><div class="dot"></div>'+esc(h)+'</div>'});html+='</div>';
|
||||
html+='<div style="margin-top:20px;display:flex;gap:12px"><button class="btn" onclick="saveEdit('+e.id+','+curMonth+')">Сохранить</button><button class="btn btn-outline" onclick="closeEM()">Отмена</button></div>';
|
||||
|
||||
document.getElementById("editModalContent").innerHTML=html;
|
||||
document.getElementById("editModalOverlay").classList.add("open");
|
||||
}
|
||||
html+='<div class="field"><label>Статус</label><select id="es"><option value="wait"'+(e.s==="wait"?" selected":"")+'>В процессе</option><option value="warn"'+(e.s==="warn"?" selected":"")+'>На контроле</option><option value="late"'+(e.s==="late"?" selected":"")+'>Просрочено</option><option value="done"'+(e.s==="done"?" selected":"")+'>Исполнено</option></select></div>';
|
||||
html+='<div class="field"><label>Прогресс (%)</label><input type="range" id="ep" min="0" max="100" value="'+e.p+'" oninput="document.getElementById(\'pv\').textContent=this.value+\'%\'"><span id="pv" style="font-weight:700">'+e.p+'%</span></div>';
|
||||
html+='<div class="field"><label>Комментарий</label><textarea id="ec" placeholder="Комментарий..."></textarea></div>';
|
||||
@ -504,13 +551,12 @@ function saveEdit(id, mk){
|
||||
e.s=document.getElementById("es").value;e.p=parseInt(document.getElementById("ep").value);
|
||||
var cmt=(document.getElementById("ec").value||"").trim();
|
||||
// Save main event report
|
||||
var mr=document.getElementById("mr");if(mr){var ad=getMD(id,-1);if(!ad[mk])ad[mk]={report:"",files:[]};ad[mk].report=mr.value;setMD(id,ad,-1)}
|
||||
// Save sub-item reports
|
||||
var mr=document.getElementById("mr");if(mr){var ad=getMD(id,curRegion,-1);if(!ad[mk])ad[mk]={report:"",files:[]};ad[mk].report=mr.value;setMD(id,ad,curRegion,-1)}
|
||||
if(e.sub&&e.sub.length){
|
||||
var cks=[];
|
||||
e.sub.forEach(function(_,i){
|
||||
var el=document.getElementById("sc_"+i);if(el&&el.checked)cks.push(i);
|
||||
var sr=document.getElementById("mr_s"+i);if(sr){var sd=getMD(id,i);if(!sd[mk])sd[mk]={report:"",files:[]};sd[mk].report=sr.value;setMD(id,sd,i)}
|
||||
var sr=document.getElementById("mr_s"+i);if(sr){var sd=getMD(id,curRegion,i);if(!sd[mk])sd[mk]={report:"",files:[]};sd[mk].report=sr.value;setMD(id,sd,curRegion,i)}
|
||||
});
|
||||
setSC(id,cks);
|
||||
}
|
||||
@ -520,15 +566,15 @@ function saveEdit(id, mk){
|
||||
}
|
||||
function closeEM(){document.getElementById("editModalOverlay").classList.remove("open")}
|
||||
|
||||
// File storage: sf_<id> for main, sf_<id>_s<i> for sub-item i
|
||||
function getMD(id,si){var k=si>=0?'sf_'+id+'_s'+si:'sf_'+id;var r=localStorage.getItem(k);return r?JSON.parse(r):{}}
|
||||
function setMD(id,o,si){var k=si>=0?'sf_'+id+'_s'+si:'sf_'+id;localStorage.setItem(k,JSON.stringify(o))}
|
||||
// File storage: sf_<eventId>_r<region> for main, sf_<eventId>_s<sub>_r<region> for sub-item
|
||||
function getMD(id,ri,si){ri=ri||0;var k=si>=0?'sf_'+id+'_s'+si+'_r'+ri:'sf_'+id+'_r'+ri;var r=localStorage.getItem(k);return r?JSON.parse(r):{}}
|
||||
function setMD(id,o,ri,si){ri=ri||0;var k=si>=0?'sf_'+id+'_s'+si+'_r'+ri:'sf_'+id+'_r'+ri;localStorage.setItem(k,JSON.stringify(o))}
|
||||
function getSC(id){var r=localStorage.getItem("ss_"+id);return r?JSON.parse(r):[]}
|
||||
function setSC(id,a){localStorage.setItem("ss_"+id,JSON.stringify(a))}
|
||||
|
||||
function storageUsed(){var t=0;for(var i=0;i<localStorage.length;i++){var k=localStorage.key(i);if(k.indexOf('sf_')===0||k.indexOf('ss_')===0)t+=localStorage.getItem(k).length*2}return t}
|
||||
function fmtStorage(){var b=storageUsed();return b>1048576?(b/1048576).toFixed(1)+' МБ':(b/1024).toFixed(0)+' КБ'}
|
||||
function clearAllFiles(){if(!confirm('Удалить ВСЕ загруженные файлы и отчёты? Это нельзя отменить.'))return;var keys=[];for(var i=0;i<localStorage.length;i++){var k=localStorage.key(i);if(k.indexOf('sf_')===0||k.indexOf('ss_')===0)keys.push(k)}keys.forEach(function(k){localStorage.removeItem(k)});alert('Файлы очищены. Хранилище: '+fmtStorage());renderDashboard()}
|
||||
function clearAllFiles(){if(!confirm('Удалить ВСЕ загруженные файлы и отчёты?'))return;var keys=[];for(var i=0;i<localStorage.length;i++){var k=localStorage.key(i);if(k.indexOf('sf_')===0||k.indexOf('ss_')===0)keys.push(k)}keys.forEach(function(k){localStorage.removeItem(k)});alert('Очищено');renderDashboard()}
|
||||
|
||||
function exportAll(){
|
||||
var data={events:events,user:curUser,date:new Date().toISOString(),files:{},subChecks:{}};
|
||||
@ -566,28 +612,27 @@ function countFiles(id){
|
||||
return total;
|
||||
}
|
||||
|
||||
function uploadFiles(eid,mk,si){
|
||||
mk=months[mk]; // convert index to month key
|
||||
var prefix=si>=0?'_s'+si:'',fi=document.getElementById('fi'+prefix);
|
||||
function uploadFiles(eid,mk,ri,si){
|
||||
mk=months[mk];var prefix=si>=0?'_s'+si:'',fi=document.getElementById('fi'+prefix);
|
||||
if(!fi||!fi.files.length)return;
|
||||
var desc=(document.getElementById('fd'+prefix)||{}).value;desc=(desc||'').trim();
|
||||
var btn=document.getElementById('ub'+prefix);btn.textContent="Загружается...";btn.disabled=true;
|
||||
var MAX=3072*1024,ad=getMD(eid,si);if(!ad[mk])ad[mk]={report:"",files:[]};
|
||||
var MAX=3072*1024,ad=getMD(eid,ri,si);if(!ad[mk])ad[mk]={report:"",files:[]};
|
||||
var arr=ad[mk].files,pr=0,sk=0;
|
||||
function fin(){
|
||||
try{setMD(eid,ad,si)}catch(e){
|
||||
var bak=JSON.parse(JSON.stringify(ad));bak[mk].files=bak[mk].files.slice(0,-(pr-sk)||0);try{setMD(eid,bak,si)}catch(e2){}
|
||||
alert("⚠️ Хранилище заполнено. Удалите старые файлы (кнопка «Очистить файлы» на дашборде).");
|
||||
try{setMD(eid,ad,ri,si)}catch(e){
|
||||
var bak=JSON.parse(JSON.stringify(ad));bak[mk].files=bak[mk].files.slice(0,-(pr-sk)||0);try{setMD(eid,bak,ri,si)}catch(e2){}
|
||||
alert("⚠️ Хранилище заполнено. Очистите файлы на дашборде.");
|
||||
}
|
||||
addLog("загрузил файлы",eid,(pr-sk)+" файл(ов) за "+M(mk));
|
||||
if(sk)alert(sk+" файл(ов) > 3 МБ пропущены");closeEM();openEdit(eid,curMonth,si>=0?si:undefined)
|
||||
addLog("загрузил файлы",eid,(pr-sk)+" файл(ов) за "+M(mk)+" ("+regions[ri]+")");
|
||||
if(sk)alert(sk+" файл(ов) > 3 МБ пропущены");closeEM();openEdit(eid,curMonth,ri,si>=0?si:undefined)
|
||||
}
|
||||
for(var i=0;i<fi.files.length;i++){(function(f){if(f.size>MAX){sk++;pr++;if(pr===fi.files.length)fin();return}
|
||||
var r=new FileReader();r.onload=function(ev){arr.push({name:f.name,size:f.size,type:f.type,desc:desc,date:new Date().toLocaleDateString(),data:ev.target.result});pr++;if(pr===fi.files.length)fin()};
|
||||
r.onerror=function(){pr++;if(pr===fi.files.length)fin()};r.readAsDataURL(f)})(fi.files[i])}
|
||||
}
|
||||
function dlF(eid,mk,idx,si){si=si||-1;mk=months[mk];var ad=getMD(eid,si),arr=ad[mk]?ad[mk].files:null;if(!arr||!arr[idx]||!arr[idx].data)return;var f=arr[idx],a=document.createElement("a");a.href=f.data;a.download=f.name;document.body.appendChild(a);a.click();document.body.removeChild(a)}
|
||||
function rmF(eid,mk,idx,si){si=si||-1;mk=months[mk];var ad=getMD(eid,si);if(!ad[mk]||!ad[mk].files)return;ad[mk].files.splice(idx,1);setMD(eid,ad,si);closeEM();openEdit(eid,curMonth,si>=0?si:undefined)}
|
||||
function dlF(eid,mk,idx,ri,si){si=si||-1;mk=months[mk];var ad=getMD(eid,ri,si),arr=ad[mk]?ad[mk].files:null;if(!arr||!arr[idx]||!arr[idx].data)return;var f=arr[idx],a=document.createElement("a");a.href=f.data;a.download=f.name;document.body.appendChild(a);a.click();document.body.removeChild(a)}
|
||||
function rmF(eid,mk,idx,ri,si){si=si||-1;mk=months[mk];var ad=getMD(eid,ri,si);if(!ad[mk]||!ad[mk].files)return;ad[mk].files.splice(idx,1);setMD(eid,ad,ri,si);closeEM();openEdit(eid,curMonth,ri,si>=0?si:undefined)}
|
||||
|
||||
function renderJournal(){
|
||||
var log=getLog().reverse(),h='<div class="panel"><h3>Журнал действий</h3>';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user