v34: регионы везде — таблица, дашборд, CSV, HTML, без жёсткой привязки
This commit is contained in:
parent
53513321ba
commit
e63dab3c7e
90
index.html
90
index.html
@ -118,9 +118,6 @@ td{border-bottom:1px solid var(--gray-200)}tr:hover td{background:var(--cyan-50)
|
||||
<div class="brand"><span>ИИ-Агент</span> ПБ</div>
|
||||
<div class="right">
|
||||
<span class="user-info" id="userLabel"></span>
|
||||
<select id="branchFilter" onchange="setFilter(this.value)" style="padding:6px 10px;border:1px solid var(--gray-200);border-radius:6px;font-size:12px;display:none">
|
||||
<option value="">Все филиалы и регионы</option>
|
||||
</select>
|
||||
<div style="position:relative">
|
||||
<button class="notif-btn" onclick="toggleNotif()">🔔<span class="badge-count" id="notifCount">0</span></button>
|
||||
<div class="notif-drop" id="notifDrop"></div>
|
||||
@ -158,23 +155,17 @@ function sb(s){var m={done:"green",warn:"amber",late:"red",wait:"gray"};return'<
|
||||
function pct(p){var c=p>=80?"var(--green)":p>=40?"var(--amber)":"var(--red)";return'<div class="pct-bar"><div class="track"><div class="fill" style="width:'+p+'%;background:'+c+'"></div></div>'+p+'%</div>'}
|
||||
|
||||
var users={
|
||||
"curator@telecom.kz":{name:"Куратор Плана",branch:0,role:"curator",region:-1},
|
||||
"dpp@telecom.kz":{name:"Директор ДПБ (Вы)",branch:0,role:"branch",region:0},
|
||||
"admin@telecom.kz":{name:"Администратор",branch:0,role:"admin",region:-1},
|
||||
"ahmetov@telecom.kz":{name:"Ахметов К.Т.",branch:6,role:"branch",region:4},
|
||||
"serikov@telecom.kz":{name:"Сериков А.М.",branch:1,role:"branch",region:3},
|
||||
"nurlanov@telecom.kz":{name:"Нурланов Д.С.",branch:8,role:"branch",region:5},
|
||||
"aliev@telecom.kz":{name:"Алиев Г.С.",branch:4,role:"branch",region:2},
|
||||
"tulegenov@telecom.kz":{name:"Тулегенов Е.А.",branch:2,role:"branch",region:1},
|
||||
"saparov@telecom.kz":{name:"Сапаров А.Д.",branch:3,role:"branch",region:1},
|
||||
"maratov@telecom.kz":{name:"Маратов Ж.К.",branch:5,role:"branch",region:3},
|
||||
"iskakov@telecom.kz":{name:"Искаков Р.Н.",branch:7,role:"branch",region:0},
|
||||
"north@telecom.kz":{name:"Отв. Северный регион",branch:1,role:"region",region:3},
|
||||
"almaty@telecom.kz":{name:"Отв. Алматинский регион",branch:2,role:"region",region:1},
|
||||
"south@telecom.kz":{name:"Отв. Южный регион",branch:4,role:"region",region:2},
|
||||
"center@telecom.kz":{name:"Отв. Центральный регион",branch:0,role:"region",region:0},
|
||||
"east@telecom.kz":{name:"Отв. Восточный регион",branch:6,role:"region",region:4},
|
||||
"west@telecom.kz":{name:"Отв. Западный регион",branch:8,role:"region",region:5}
|
||||
"curator@telecom.kz":{name:"Куратор Плана",branch:0,role:"curator"},
|
||||
"dpp@telecom.kz":{name:"Директор ДПБ (Вы)",branch:0,role:"branch"},
|
||||
"admin@telecom.kz":{name:"Администратор",branch:0,role:"admin"},
|
||||
"ahmetov@telecom.kz":{name:"Ахметов К.Т.",branch:6,role:"branch"},
|
||||
"serikov@telecom.kz":{name:"Сериков А.М.",branch:1,role:"branch"},
|
||||
"nurlanov@telecom.kz":{name:"Нурланов Д.С.",branch:8,role:"branch"},
|
||||
"aliev@telecom.kz":{name:"Алиев Г.С.",branch:4,role:"branch"},
|
||||
"tulegenov@telecom.kz":{name:"Тулегенов Е.А.",branch:2,role:"branch"},
|
||||
"saparov@telecom.kz":{name:"Сапаров А.Д.",branch:3,role:"branch"},
|
||||
"maratov@telecom.kz":{name:"Маратов Ж.К.",branch:5,role:"branch"},
|
||||
"iskakov@telecom.kz":{name:"Искаков Р.Н.",branch:7,role:"branch"}
|
||||
};
|
||||
var curUser=null,curMonth=5,curRegion=0,editSubIdx=-1;
|
||||
|
||||
@ -193,30 +184,12 @@ function saveEvents(){localStorage.setItem("samruk_ev",JSON.stringify(events))}
|
||||
function addLog(action,eventId,detail){var l=JSON.parse(localStorage.getItem("samruk_log")||"[]");l.push({ts:new Date().toISOString(),user:curUser.name,role:curUser.role,action:action,eid:eventId,detail:detail||""});if(l.length>500)l=l.slice(-500);localStorage.setItem("samruk_log",JSON.stringify(l))}
|
||||
function getLog(){return JSON.parse(localStorage.getItem("samruk_log")||"[]")}
|
||||
|
||||
function getMy(){if(!curUser||!events)return[];var list;if(curUser.role==="admin"||curUser.role==="curator"){list=events}else if(curUser.role==="region"){list=events.filter(function(e){return branchRegion[e.b]===curUser.region})}else{list=events.filter(function(e){return e.b===curUser.branch})}
|
||||
// Apply curator's filter
|
||||
if(curFilter&&(curUser.role==="curator"||curUser.role==="admin")){
|
||||
if(curFilter.type==="branch")list=list.filter(function(e){return e.b===curFilter.id})
|
||||
else if(curFilter.type==="region")list=list.filter(function(e){return branchRegion[e.b]===curFilter.id})
|
||||
}
|
||||
return list}
|
||||
|
||||
function setFilter(val){
|
||||
if(!val){curFilter=null}else{var p=val.split("_");curFilter={type:p[0],id:parseInt(p[1])}}
|
||||
renderAll()}
|
||||
function getMy(){if(!curUser||!events)return[];if(curUser.role==="admin"||curUser.role==="curator")return events;return events.filter(function(e){return e.b===curUser.branch})}
|
||||
|
||||
// Auth
|
||||
function doLogin(e){e.preventDefault();var em=document.getElementById("loginEmail").value.trim().toLowerCase();if(users[em]){ curUser={email:em,name:users[em].name,branch:users[em].branch,role:users[em].role,region:users[em].region};localStorage.setItem("samruk_u",JSON.stringify(curUser));addLog("вошёл");showApp()}else{document.getElementById("loginErr").style.display="block"}return false}
|
||||
function doLogin(e){e.preventDefault();var em=document.getElementById("loginEmail").value.trim().toLowerCase();if(users[em]){ curUser={email:em,name:users[em].name,branch:users[em].branch,role:users[em].role};localStorage.setItem("samruk_u",JSON.stringify(curUser));addLog("вошёл");showApp()}else{document.getElementById("loginErr").style.display="block"}return false}
|
||||
function doLogout(){addLog("вышел");localStorage.removeItem("samruk_u");curUser=null;document.getElementById("loginScreen").style.display="flex";document.getElementById("app").style.display="none"}
|
||||
function showApp(){document.getElementById("loginScreen").style.display="none";document.getElementById("app").style.display="block";var label=curUser.name+" · "+(curUser.role==="curator"||curUser.role==="admin"?"Все регионы":curUser.role==="region"?regions[curUser.region]:branches[curUser.branch]);document.getElementById("userLabel").innerHTML="<strong>"+label+"</strong>";
|
||||
// Populate branch filter for curator/admin
|
||||
var sel=document.getElementById("branchFilter");
|
||||
if(curUser.role==="curator"||curUser.role==="admin"){
|
||||
sel.style.display="inline-block";sel.innerHTML='<option value="">Все филиалы и регионы</option>';
|
||||
regions.forEach(function(r,ri){sel.innerHTML+='<optgroup label="'+r+'"></optgroup>';branches.forEach(function(b,bi){if(branchRegion[bi]===ri)sel.innerHTML+='<option value="b_'+bi+'"> '+b+'</option>'})});
|
||||
sel.value=curFilter?(curFilter.type+"_"+curFilter.id):"";
|
||||
}else{sel.style.display="none"}
|
||||
renderAll()}
|
||||
function showApp(){document.getElementById("loginScreen").style.display="none";document.getElementById("app").style.display="block";var label=curUser.name+" · "+(curUser.role==="curator"||curUser.role==="admin"?"Все регионы":branches[curUser.branch]);document.getElementById("userLabel").innerHTML="<strong>"+label+"</strong>";renderAll()}
|
||||
|
||||
// Notifs
|
||||
function notifsUpdate(){
|
||||
@ -281,12 +254,11 @@ function renderDashboard(){
|
||||
h+='<input type="file" id="impF" accept=".json" style="display:none" onchange="importAll(this)">';
|
||||
h+='<button class="btn btn-sm" style="margin-top:6px;margin-left:4px;background:var(--red);color:#fff" onclick="clearAllFiles()">🗑 Очистить файлы</button></div></div>';
|
||||
|
||||
// Branch detail for curator
|
||||
if(curUser.role==="curator"||curUser.role==="admin"){
|
||||
h+='<div class="panel"><h3>📋 Сводка по филиалам — что загружено</h3><table><tr><th>Филиал</th><th>Регион</th><th>Меропр.</th><th>С отчётами</th><th>Файлов</th><th>Прогресс</th></tr>';
|
||||
h+='<div class="panel"><h3>📋 Сводка по филиалам — что загружено</h3><table><tr><th>Филиал</th><th>Меропр.</th><th>С отчётами</th><th>Файлов</th><th>Прогресс</th></tr>';
|
||||
branches.forEach(function(b,bi){var items=events.filter(function(e){return e.b===bi}),withRpt=0,totalFiles=0;
|
||||
items.forEach(function(e){var d=getMD(e.id,-1);var has=false;for(var k in d){if(d.hasOwnProperty(k)&&d[k]&&(d[k].report||(d[k].files&&d[k].files.length))){has=true;totalFiles+=(d[k].files||[]).length}}if(e.sub)e.sub.forEach(function(s,si){var sd=getMD(e.id,si);for(var sk in sd){if(sd.hasOwnProperty(sk)&&sd[sk]&&(sd[sk].report||(sd[sk].files&&sd[sk].files.length))){has=true;totalFiles+=(sd[sk].files||[]).length}}});if(has)withRpt++});
|
||||
h+='<tr style="cursor:pointer" onclick="setFilter(\'b_'+bi+'\');document.getElementById(\'branchFilter\').value=\'b_'+bi+'\'"><td><strong>'+b+'</strong></td><td style="font-size:11px">'+regions[branchRegion[bi]]+'</td><td>'+items.length+'</td><td>'+(withRpt?withRpt+' ✅':'—')+'</td><td>'+(totalFiles||'—')+'</td><td>'+pct(items.length?Math.round(withRpt/items.length*100):0)+'</td></tr>';
|
||||
items.forEach(function(e){var has=false;regions.forEach(function(r,ri){var d=getMD(e.id,ri,-1);for(var k in d){if(d.hasOwnProperty(k)&&d[k]&&(d[k].report||(d[k].files&&d[k].files.length))){has=true;totalFiles+=(d[k].files||[]).length}}if(e.sub)e.sub.forEach(function(s,si){var sd=getMD(e.id,ri,si);for(var sk in sd){if(sd.hasOwnProperty(sk)&&sd[sk]&&(sd[sk].report||(sd[sk].files&&sd[sk].files.length))){has=true;totalFiles+=(sd[sk].files||[]).length}}})});if(has)withRpt++});
|
||||
h+='<tr><td><strong>'+b+'</strong></td><td>'+items.length+'</td><td>'+(withRpt?withRpt+' ✅':'—')+'</td><td>'+(totalFiles||'—')+'</td><td>'+pct(items.length?Math.round(withRpt/items.length*100):0)+'</td></tr>';
|
||||
});
|
||||
h+='</table></div>';
|
||||
}
|
||||
@ -296,11 +268,14 @@ function renderDashboard(){
|
||||
|
||||
function downloadReport(){
|
||||
var from=parseInt(document.getElementById("rptFrom").value),to=parseInt(document.getElementById("rptTo").value);
|
||||
var my=getMy(),csv="№;Регион;Филиал;Мероприятие;Подпункт;Раздел;Статус;Прогресс;Срок;Факт;Отчёт (текст);Файлы\n";
|
||||
var my=getMy(),csv="№;Филиал;Мероприятие;Подпункт;Регион;Раздел;Статус;Прогресс;Срок;Факт;Отчёт (текст);Файлы\n";
|
||||
my.forEach(function(e){
|
||||
function addRow(subLabel,subIdx){var rep="",fls="",d=getMD(e.id,subIdx);
|
||||
function addRow(subLabel,subIdx){regions.forEach(function(r,ri){var rep="",fls="",d=getMD(e.id,ri,subIdx);
|
||||
for(var i=from;i<=to;i++){var m=months[i];if(d[m]){if(d[m].report)rep+=M(i)+": "+d[m].report.replace(/"/g,'""')+"; ";if(d[m].files&&d[m].files.length)fls+=M(i)+": "+d[m].files.map(function(f){return f.name}).join(", ")+"; "}}
|
||||
csv+=e.id+';'+regions[branchRegion[e.b]]+';'+branches[e.b]+';"'+e.t.replace(/"/g,'""')+'";'+(subLabel||"общее")+';'+sections[e.sec]+';'+statusMap[e.s]+';'+e.p+'%;'+e.due+';'+(e.done||"—")+';"'+rep+'";"'+fls+'"\n';}
|
||||
csv+=e.id+';'+branches[e.b]+';"'+e.t.replace(/"/g,'""')+'";'+(subLabel||"общее")+';'+r+';'+sections[e.sec]+';'+statusMap[e.s]+';'+e.p+'%;'+e.due+';'+(e.done||"—")+';"'+rep+'";"'+fls+'"\n'})}
|
||||
addRow("",-1);
|
||||
if(e.sub) e.sub.forEach(function(s,i){ addRow(s.l,i); });
|
||||
});
|
||||
addRow("",-1);
|
||||
if(e.sub) e.sub.forEach(function(s,i){ addRow(s.l,i); });
|
||||
});
|
||||
@ -315,12 +290,12 @@ function downloadHTML(){
|
||||
my.forEach(function(e){
|
||||
var scls={done:"g",warn:"a",late:"r",wait:"w"}[e.s];
|
||||
h+='<div class="ev"><h3>'+e.id+'. '+esc(e.t)+'</h3>';
|
||||
h+='<div class="meta"><span>Регион: <strong>'+regions[branchRegion[e.b]]+'</strong></span><span>Филиал: <strong>'+branches[e.b]+'</strong></span><span>Раздел: <strong>'+sections[e.sec]+'</strong></span><span>Срок: <strong>'+e.due+'</strong></span><span>Факт: <strong>'+(e.done||"—")+'</strong></span><span>Прогресс: <strong>'+e.p+'%</strong></span><span class="badge '+scls+'">'+statusMap[e.s]+'</span></div>';
|
||||
h+='<div class="meta"><span>Филиал: <strong>'+branches[e.b]+'</strong></span><span>Раздел: <strong>'+sections[e.sec]+'</strong></span><span>Срок: <strong>'+e.due+'</strong></span><span>Факт: <strong>'+(e.done||"—")+'</strong></span><span>Прогресс: <strong>'+e.p+'%</strong></span><span class="badge '+scls+'">'+statusMap[e.s]+'</span></div>';
|
||||
h+='<div class="meta"><span>Ответственный: <strong>'+esc(e.r)+'</strong></span></div>';
|
||||
// Main event data
|
||||
h+=renderMonthBlock(e.id,-1,"Общие материалы",from,to);
|
||||
// Sub-items
|
||||
if(e.sub) e.sub.forEach(function(s,i){ h+='<div class="sub-blk"><strong>'+s.l+') '+esc(s.t)+'</strong>';h+=renderMonthBlock(e.id,i,"",from,to);h+='</div>'});
|
||||
// Main event data — all regions
|
||||
regions.forEach(function(r,ri){h+=renderMonthBlock(e.id,ri,-1,r,from,to)});
|
||||
// Sub-items — all regions
|
||||
if(e.sub) e.sub.forEach(function(s,i){ h+='<div class="sub-blk"><strong>'+s.l+') '+esc(s.t)+'</strong>';regions.forEach(function(r,ri){h+=renderMonthBlock(e.id,ri,i,"",from,to)});h+='</div>'});
|
||||
h+='<div style="font-size:11px;color:#666;margin-top:8px">🤖 ИИ: '+esc(e.ai)+'</div></div>';
|
||||
});
|
||||
h+='</body></html>';
|
||||
@ -333,8 +308,8 @@ function downloadHTML(){
|
||||
}
|
||||
}
|
||||
|
||||
function renderMonthBlock(id,si,label,from,to){
|
||||
var d=getMD(id,si),has=false,html='';
|
||||
function renderMonthBlock(id,ri,si,label,from,to){
|
||||
var d=getMD(id,ri,si),has=false,html='';
|
||||
for(var i=from;i<=to;i++){var m=months[i];if(d[m]&&(d[m].report||(d[m].files&&d[m].files.length))){
|
||||
has=true;html+='<div class="month"><strong>'+M(i)+(label?' — '+label:'')+'</strong>';
|
||||
if(d[m].report)html+='<p style="font-size:13px;margin:4px 0">'+esc(d[m].report)+'</p>';
|
||||
@ -353,7 +328,7 @@ function renderMyEvents(){
|
||||
h+='<div class="filters"><select id="mySF" onchange="renderMyEvents()"><option value="">Все</option><option value="done">Исполнено</option><option value="warn">На контроле</option><option value="late">Просрочено</option><option value="wait">В процессе</option></select></div>';
|
||||
var sf=document.getElementById("mySF");sf=sf?sf.value:"";
|
||||
var list=my;if(sf)list=list.filter(function(e){return e.s===sf});
|
||||
h+='<table><tr><th>№</th><th>Мероприятие / Подпункты</th><th>Регион</th><th>Филиал</th><th>Раздел</th><th>Срок</th><th>Прогресс</th><th>Статус</th><th></th></tr>';
|
||||
h+='<table><tr><th>№</th><th>Мероприятие / Подпункты</th><th>Филиал</th><th>Раздел</th><th>Срок</th><th>Прогресс</th><th>Статус</th><th></th></tr>';
|
||||
list.forEach(function(e){
|
||||
var hasSub = e.sub && e.sub.length;
|
||||
var sc = getSC(e.id);
|
||||
@ -362,10 +337,9 @@ function renderMyEvents(){
|
||||
h+='<tr><td>'+e.id+'</td>';
|
||||
h+='<td style="font-size:12px;max-width:300px">';
|
||||
if(hasSub) h+='<span onclick="toggleExpand('+e.id+')" style="cursor:pointer;margin-right:6px">'+(expandedEvents[e.id]?'▼':'▶')+'</span>';
|
||||
h+='<span title="'+esc(e.t)+'" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:240px;display:inline-block;vertical-align:middle">'+esc(e.t)+'</span>';
|
||||
h+='<span title="'+esc(e.t)+'" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:260px;display:inline-block;vertical-align:middle">'+esc(e.t)+'</span>';
|
||||
if(hasSub) h+=' <span style="font-size:11px;color:var(--gray-500)">('+subDone+'/'+subTotal+')</span>';
|
||||
h+='</td>';
|
||||
h+='<td style="font-size:11px">'+regions[branchRegion[e.b]]+'</td>';
|
||||
h+='<td style="font-size:11px">'+branches[e.b]+'</td>';
|
||||
h+='<td><span class="badge blue">'+["I","II","III","IV","V"][e.sec]+'</span></td>';
|
||||
h+='<td>'+e.due+'</td><td>'+pct(e.p)+'</td><td>'+sb(e.s)+'</td>';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user