Spaces:
Sleeping
Sleeping
exec report: add a separate 116-year work-plan section + Word export, listing each persons current owner116 duties (roster view, not a before/after diff since 116 has no baseline)
Browse files- static/index.html +117 -0
static/index.html
CHANGED
|
@@ -1769,6 +1769,36 @@ td.wrap-cell-note{white-space:normal; min-width:100px; max-width:220px;}
|
|
| 1769 |
'</div>';
|
| 1770 |
}
|
| 1771 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1772 |
function renderDiffSection(){
|
| 1773 |
var backups = STATE.backupsByYear[STATE.year] || [];
|
| 1774 |
var html = '<div class="section-title" id="sec-diff"><h2>⑧調整前後對照</h2>'+
|
|
@@ -2071,6 +2101,25 @@ td.wrap-cell-note{white-space:normal; min-width:100px; max-width:220px;}
|
|
| 2071 |
groupRows.forEach(function(row){ html += renderPersonDiffCard(row); });
|
| 2072 |
html += '</div>';
|
| 2073 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2074 |
return html;
|
| 2075 |
}
|
| 2076 |
|
|
@@ -2239,6 +2288,73 @@ td.wrap-cell-note{white-space:normal; min-width:100px; max-width:220px;}
|
|
| 2239 |
});
|
| 2240 |
}
|
| 2241 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2242 |
function bindAll(){
|
| 2243 |
document.querySelectorAll('.chip[data-filter]').forEach(function(el){
|
| 2244 |
el.addEventListener('click', function(){ STATE.groupFilter = el.getAttribute('data-filter'); render(); });
|
|
@@ -2288,6 +2404,7 @@ td.wrap-cell-note{white-space:normal; min-width:100px; max-width:220px;}
|
|
| 2288 |
document.querySelectorAll('[data-action="showExecReport"]').forEach(function(el){ el.addEventListener('click', showExecReport); });
|
| 2289 |
document.querySelectorAll('[data-action="exitExecReport"]').forEach(function(el){ el.addEventListener('click', exitExecReport); });
|
| 2290 |
document.querySelectorAll('[data-action="exportExecReportWord"]').forEach(function(el){ el.addEventListener('click', function(){ exportExecReportWord(el); }); });
|
|
|
|
| 2291 |
document.querySelectorAll('[data-action="toggleRledSort"]').forEach(function(el){ el.addEventListener('click', function(){ toggleRledSort(el.getAttribute('data-sort-field')); }); });
|
| 2292 |
document.querySelectorAll('[data-action="syncRled"]').forEach(function(el){ el.addEventListener('click', function(){ syncRledFromLive115(el); }); });
|
| 2293 |
|
|
|
|
| 1769 |
'</div>';
|
| 1770 |
}
|
| 1771 |
|
| 1772 |
+
// 116年度分工規劃:單一狀態的名單(不是調整前後對照,因為116沒有「調整前」
|
| 1773 |
+
// 基準),依 ownerField(給'owner116')分組——跟 personBeforeAfterDiff 回傳
|
| 1774 |
+
// 同樣的 {owner,title,groupIds,...} 外型,所以能直接沿用 rledGroupRowsSorted
|
| 1775 |
+
// 做已結案/預計新增人力佔位的排序,不用另外寫一套。
|
| 1776 |
+
function personRoster(list, ownerField){
|
| 1777 |
+
var owners = {};
|
| 1778 |
+
list.forEach(function(p){ var o = p[ownerField]; if (o) owners[o] = true; });
|
| 1779 |
+
var names = Object.keys(owners).sort(function(a,b){ return a.localeCompare(b, 'zh-Hant'); });
|
| 1780 |
+
return names.map(function(owner){
|
| 1781 |
+
var items = list.filter(function(p){ return p[ownerField]===owner; });
|
| 1782 |
+
var groupIds = [];
|
| 1783 |
+
items.forEach(function(p){ if (groupIds.indexOf(p.groupId)===-1) groupIds.push(p.groupId); });
|
| 1784 |
+
return { owner: owner, title: STAFF_TITLES[owner]||null, groupIds: groupIds, items: items };
|
| 1785 |
+
});
|
| 1786 |
+
}
|
| 1787 |
+
|
| 1788 |
+
function renderPersonRosterCard(row){
|
| 1789 |
+
var chipsHtml = row.items.length
|
| 1790 |
+
? row.items.map(function(p){ return renderPlanChip(p, 'same'); }).join('')
|
| 1791 |
+
: '<span class="tag">(目前沒有業務)</span>';
|
| 1792 |
+
return '<div class="diff-card">'+
|
| 1793 |
+
'<div class="diff-card-head"><b>'+escapeHtml(row.owner)+'</b>'+
|
| 1794 |
+
(row.title?('<span class="tag">'+escapeHtml(row.title)+'</span>'):'')+
|
| 1795 |
+
row.groupIds.map(function(g){ return '<span class="tag">'+escapeHtml(groupName(g))+'</span>'; }).join('')+
|
| 1796 |
+
'<span class="tag">共'+row.items.length+'項</span>'+
|
| 1797 |
+
'</div>'+
|
| 1798 |
+
'<div class="diff-chips">'+chipsHtml+'</div>'+
|
| 1799 |
+
'</div>';
|
| 1800 |
+
}
|
| 1801 |
+
|
| 1802 |
function renderDiffSection(){
|
| 1803 |
var backups = STATE.backupsByYear[STATE.year] || [];
|
| 1804 |
var html = '<div class="section-title" id="sec-diff"><h2>⑧調整前後對照</h2>'+
|
|
|
|
| 2101 |
groupRows.forEach(function(row){ html += renderPersonDiffCard(row); });
|
| 2102 |
html += '</div>';
|
| 2103 |
});
|
| 2104 |
+
|
| 2105 |
+
// 116年度分工規劃:獨立區塊,不是調整前後對照(116沒有「調整前」基準可比),
|
| 2106 |
+
// 單純列出每一位同仁目前在⑨規劃的116年度業務清單,依owner116欄位分組。
|
| 2107 |
+
html += '<h3 style="font-size:15px; margin:34px 0 10px;">📋116年度分工規劃</h3>'+
|
| 2108 |
+
'<div style="margin:-6px 0 16px; display:flex; gap:8px; align-items:center;">'+
|
| 2109 |
+
'<button class="btn small primary" data-action="export116PlanWord">⬇️ 下載Word(116年度・各組A4分頁)</button>'+
|
| 2110 |
+
'<span id="plan116-export-msg" class="tag"></span>'+
|
| 2111 |
+
'</div>';
|
| 2112 |
+
var rows116 = personRoster(after, 'owner116');
|
| 2113 |
+
GROUP_ORDER.forEach(function(gid){
|
| 2114 |
+
var groupRows116 = rledGroupRowsSorted(rows116, gid);
|
| 2115 |
+
if (!groupRows116.length) return;
|
| 2116 |
+
var blockClass116 = 'exec-report-group'+(gid==='edu' ? ' exec-report-group-tint' : '');
|
| 2117 |
+
html += '<div class="'+blockClass116+'">';
|
| 2118 |
+
html += '<h4 style="font-size:14px; margin:0 0 10px;">'+escapeHtml(groupName(gid))+'</h4>';
|
| 2119 |
+
groupRows116.forEach(function(row){ html += renderPersonRosterCard(row); });
|
| 2120 |
+
html += '</div>';
|
| 2121 |
+
});
|
| 2122 |
+
|
| 2123 |
return html;
|
| 2124 |
}
|
| 2125 |
|
|
|
|
| 2288 |
});
|
| 2289 |
}
|
| 2290 |
|
| 2291 |
+
// 116年度分工規劃下載Word:跟上面調整前後對照那份是「兩份獨立檔案」,不會
|
| 2292 |
+
// 混在一起(要給主管看115的、給董事長看116規劃的,各自下載即可)。單欄
|
| 2293 |
+
// 名單(不是兩欄對照),沿用同一套execReportItemParagraph/execReportEmptyNote
|
| 2294 |
+
// 畫chip,視覺語言跟另一份Word檔、跟畫面上都一致。
|
| 2295 |
+
function build116PlanDocx(D, after){
|
| 2296 |
+
var rows116 = personRoster(after, 'owner116');
|
| 2297 |
+
var sections = GROUP_ORDER.map(function(gid){
|
| 2298 |
+
var groupRows = rledGroupRowsSorted(rows116, gid);
|
| 2299 |
+
var children = [
|
| 2300 |
+
new D.Paragraph({
|
| 2301 |
+
heading: D.HeadingLevel.HEADING_1, alignment: D.AlignmentType.CENTER,
|
| 2302 |
+
children:[ new D.TextRun({ text: groupName(gid)+' 116年度分工規劃' }) ],
|
| 2303 |
+
}),
|
| 2304 |
+
new D.Paragraph({
|
| 2305 |
+
alignment: D.AlignmentType.CENTER, spacing:{ after:300 },
|
| 2306 |
+
children:[ new D.TextRun({ text:'資料時間:'+new Date().toISOString().slice(0,10), color:EXEC_DOC_COLOR.muted, size:20 }) ],
|
| 2307 |
+
}),
|
| 2308 |
+
];
|
| 2309 |
+
if (!groupRows.length){
|
| 2310 |
+
children.push(execReportEmptyNote(D, '(這組目前116年度還沒有人被指派業務)'));
|
| 2311 |
+
}
|
| 2312 |
+
groupRows.forEach(function(row){
|
| 2313 |
+
var items = row.items.length ? row.items.map(function(p){ return execReportItemParagraph(D, p, 'same'); }) : [execReportEmptyNote(D, '(目前沒有業務)')];
|
| 2314 |
+
children.push(new D.Paragraph({ heading: D.HeadingLevel.HEADING_2, spacing:{ before:240, after:80 }, children:[ new D.TextRun({ text: row.owner+(row.title?' '+row.title:'') }) ] }));
|
| 2315 |
+
children = children.concat(items);
|
| 2316 |
+
children.push(new D.Paragraph({ text:'', spacing:{ after:200 } }));
|
| 2317 |
+
});
|
| 2318 |
+
return {
|
| 2319 |
+
properties: { page: {
|
| 2320 |
+
size: { width:A4_WIDTH_TWIP, height:A4_HEIGHT_TWIP, orientation: D.PageOrientation.PORTRAIT },
|
| 2321 |
+
margin: { top:PAGE_MARGIN_TWIP, bottom:PAGE_MARGIN_TWIP, left:PAGE_MARGIN_TWIP, right:PAGE_MARGIN_TWIP },
|
| 2322 |
+
} },
|
| 2323 |
+
children: children,
|
| 2324 |
+
};
|
| 2325 |
+
});
|
| 2326 |
+
return new D.Document({ sections: sections });
|
| 2327 |
+
}
|
| 2328 |
+
|
| 2329 |
+
function export116PlanWord(btn){
|
| 2330 |
+
var msgEl = document.getElementById('plan116-export-msg');
|
| 2331 |
+
var D = window.docx;
|
| 2332 |
+
if (!D){ alert('匯出功能還在載入,請稍後再試一次'); return; }
|
| 2333 |
+
if (!canSeeRledPlan()){ return; }
|
| 2334 |
+
var after = STATE.rledProjects;
|
| 2335 |
+
if (!after){ alert('資料還在載入,請稍候再試一次'); return; }
|
| 2336 |
+
btn.disabled = true;
|
| 2337 |
+
if (msgEl) msgEl.textContent = '產生中…';
|
| 2338 |
+
var doc = build116PlanDocx(D, after);
|
| 2339 |
+
D.Packer.toBlob(doc).then(function(blob){
|
| 2340 |
+
var dateStr = new Date().toISOString().slice(0,10).replace(/-/g,'');
|
| 2341 |
+
var url = URL.createObjectURL(blob);
|
| 2342 |
+
var a = document.createElement('a');
|
| 2343 |
+
a.href = url;
|
| 2344 |
+
a.download = '116年度分工規劃_'+dateStr+'.docx';
|
| 2345 |
+
document.body.appendChild(a);
|
| 2346 |
+
a.click();
|
| 2347 |
+
document.body.removeChild(a);
|
| 2348 |
+
URL.revokeObjectURL(url);
|
| 2349 |
+
if (msgEl) msgEl.textContent = '✓ 已下載';
|
| 2350 |
+
btn.disabled = false;
|
| 2351 |
+
})['catch'](function(err){
|
| 2352 |
+
btn.disabled = false;
|
| 2353 |
+
if (msgEl) msgEl.textContent = '';
|
| 2354 |
+
alert('產生Word失敗,請再試一次:'+(err && err.message ? err.message : err));
|
| 2355 |
+
});
|
| 2356 |
+
}
|
| 2357 |
+
|
| 2358 |
function bindAll(){
|
| 2359 |
document.querySelectorAll('.chip[data-filter]').forEach(function(el){
|
| 2360 |
el.addEventListener('click', function(){ STATE.groupFilter = el.getAttribute('data-filter'); render(); });
|
|
|
|
| 2404 |
document.querySelectorAll('[data-action="showExecReport"]').forEach(function(el){ el.addEventListener('click', showExecReport); });
|
| 2405 |
document.querySelectorAll('[data-action="exitExecReport"]').forEach(function(el){ el.addEventListener('click', exitExecReport); });
|
| 2406 |
document.querySelectorAll('[data-action="exportExecReportWord"]').forEach(function(el){ el.addEventListener('click', function(){ exportExecReportWord(el); }); });
|
| 2407 |
+
document.querySelectorAll('[data-action="export116PlanWord"]').forEach(function(el){ el.addEventListener('click', function(){ export116PlanWord(el); }); });
|
| 2408 |
document.querySelectorAll('[data-action="toggleRledSort"]').forEach(function(el){ el.addEventListener('click', function(){ toggleRledSort(el.getAttribute('data-sort-field')); }); });
|
| 2409 |
document.querySelectorAll('[data-action="syncRled"]').forEach(function(el){ el.addEventListener('click', function(){ syncRledFromLive115(el); }); });
|
| 2410 |
|