ILRDF-Lowking commited on
Commit
b888ad5
·
verified ·
1 Parent(s): 9dc49e0

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
Files changed (1) hide show
  1. 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