Skip to content

Commit ccf5144

Browse files
authored
Merge pull request #11 from yuuuta21/claude/project-thread-220qft
小分類×ドナー:重要レポート表の小分類から、そのドナーの案件に絞って開く
2 parents de81ac1 + a2a8769 commit ccf5144

1 file changed

Lines changed: 44 additions & 12 deletions

File tree

‎monitor.html‎

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,9 @@
257257
.lg li:hover{background:var(--panel2)}
258258
.lg li.nolink{cursor:default}
259259
.lg li.on{background:var(--accent-soft);font-weight:600}
260+
.fbtns{display:flex;gap:6px;flex-wrap:wrap}
261+
.rg-d{display:flex;flex-wrap:wrap;gap:4px;margin:4px 0 8px}
262+
.rg-d .chip{font-size:12px;line-height:2}
260263
.fband{display:flex;flex-wrap:wrap;gap:8px 12px;align-items:center;justify-content:space-between;margin-top:10px;padding:10px 14px;border:1px solid var(--accent);background:var(--accent-soft);border-radius:8px;font-size:13px}
261264
.lg i{width:10px;height:10px;border-radius:2px;display:block}
262265
.lg .nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@@ -665,7 +668,7 @@
665668
$("#jica").onclick = ()=>{ SHOW_JP = !SHOW_JP; lsSet("km-jp", SHOW_JP); syncJP(); toast(SHOW_JP?"JICA・日本の協力を表示":"JICA・日本の協力を非表示"); render(); };
666669

667670
/* ---------- state & routing ---------- */
668-
const DEF = {v:"home", s:"", t:"all", p:90, d:"", q:"", ps:"active", psort:"start", sub:"pa", k:"", my:"", ua:"", st:"", doc:""};
671+
const DEF = {v:"home", s:"", t:"all", p:90, d:"", q:"", ps:"active", psort:"start", sub:"pa", k:"", my:"", ua:"", st:"", doc:"", g:""};
669672
// chart switches (not part of the address): donut scope/metric/range, year chart metric
670673
// ym: new = started that year, ongoing = running that year, cum = everything started up to that year
671674
const CH = Object.assign({scope:"donor", metric:"amt", range:"active", ym:"new", ymet:"amt", ysel:0, side:"gov"}, lsGet("km-charts", {}));
@@ -1326,7 +1329,8 @@ <h2>${esc(s.ja)} ${favStar(s.id)}</h2></div>${jica}</div>
13261329
const list = filterItems({...S, t:"all"});
13271330
const all = projects().filter(p=>p.donor===d.id);
13281331
const base = filterProjects({d:S.d, s:S.s, q:S.q, ps:S.ps, psort:S.psort});
1329-
const pj = filterProjects({d:S.d, s:S.s, q:S.q, k:S.k, ps:S.ps, psort:S.psort});
1332+
const sfp = S.g ? subField(S.g) : null;
1333+
const pj = filterProjects({d:S.d, s:S.s, q:S.q, k:S.k, ps:S.ps, psort:S.psort}).filter(p=>!sfp || sfp.set.has(p));
13301334
// where this donor works: amount (or count when no amounts) by sector, all periods
13311335
const bySec = {}; let useAmt = all.some(p=>p.amt);
13321336
const nSec = {};
@@ -1335,15 +1339,17 @@ <h2>${esc(s.ja)} ${favStar(s.id)}</h2></div>${jica}</div>
13351339
// keep the narrowed sector in the list even when it is outside the top 12
13361340
const secRows = secAll.filter((r,i)=>i<12 || fs0.includes(r[0])), smax = Math.max(1,...secRows.map(r=>r[1]));
13371341
// narrowed to one or more sectors: the top numbers count that part only (multi-sector projects by their share)
1338-
const fs = secList(S.s), inS = p => !fs.length || p.c.some(c=>fs.includes(c));
1342+
const fs = secList(S.s), sf = S.g ? subField(S.g) : null;
1343+
const inS = p => sf ? sf.set.has(p) : (!fs.length || p.c.some(c=>fs.includes(c)));
13391344
const sub = all.filter(inS), part = p => fs.length ? shareAmt(p)*p.c.filter(c=>fs.includes(c)).length : (p.amt||0);
13401345
const kinds = {}; for(const p of sub) kinds[sch(p)]=(kinds[sch(p)]||0)+1;
13411346
const act = sub.filter(p=>p.st==="active"), actAmt = act.reduce((a,p)=>a+part(p),0);
1342-
const band = fs.length ? `<div class="fband"><span><b>${esc(fs.map(c=>SEC[c].ja).join("・"))} × ${esc(d.ja)}</b> に絞り込み中</span><button class="btn" data-allsec>全体を見る</button></div>` : "";
1347+
const band = sf ? `<div class="fband"><span><b>${esc(SEC[sf.sec].ja)} › ${esc(sf.name)} × ${esc(d.ja)}</b> に絞り込み中<br><span class="note">樹形図でこの小分類の枝に当てはめた実施中の案件です(題名からの自動判定)。</span></span><span class="fbtns"><button class="btn" data-allsub>${esc(SEC[sf.sec].ja)}全体</button><button class="btn" data-allsec>全体を見る</button></span></div>`
1348+
: fs.length ? `<div class="fband"><span><b>${esc(fs.map(c=>SEC[c].ja).join("・"))} × ${esc(d.ja)}</b> に絞り込み中</span><button class="btn" data-allsec>全体を見る</button></div>` : "";
13431349
return `<div class="panel shead"><div><div class="k">${esc(DGROUP[d.g]||"")}</div><h2>${esc(d.ja)}</h2></div>
13441350
<div class="minis"><div class="mini"><div class="v">${projReady()?act.length:"…"}</div><div class="l">実施中案件</div></div>
13451351
<div class="mini"><div class="v">${projReady()?money(actAmt)||"—":"…"}</div><div class="l">実施中の支援額${fs.length?"(按分)":""}${unk(act)?`<br><span class="unk">金額不明 ${unk(act)}件</span>`:""}</div></div>
1346-
<div class="mini"><div class="v">${projReady()?sub.filter(p=>!isOpen(p)).length:"…"}</div><div class="l">過去の案件</div></div>
1352+
<div class="mini"><div class="v">${!projReady()?"…":sf?"—":sub.filter(p=>!isOpen(p)).length}</div><div class="l">過去の案件</div></div>
13471353
<div class="mini"><div class="v">${list.length}</div><div class="l">関連ニュース(${esc(PERIODS.find(p=>p[0]===S.p)?.[1]||"")})</div></div></div></div>${band}
13481354
${secRows.length?`<div class="cols" style="margin-top:10px">
13491355
<div class="panel chart"><div class="phk">By sector · all periods</div><h3 class="ph">分野別の${useAmt?"支援額と件数":"案件数"}</h3>
@@ -1353,7 +1359,7 @@ <h2>${esc(s.ja)} ${favStar(s.id)}</h2></div>${jica}</div>
13531359
</div>`:""}
13541360
<div class="filters" style="margin-top:10px">${periodSelect(S.p)} ${sectorSelect(S.s)} ${qInput()}</div>
13551361
<div class="cols">
1356-
<section>${secHead("Projects","案件")}<div class="filters">${seg("ps",[["all","すべて"],["active","実施中"],["pipeline","計画中"],["closed","終了"]],S.ps)} ${kindSelect(base)} ${seg("psort",[["start","開始日"],["amount","金額"]],S.psort)}</div>${projectListHTML(pj)}</section>
1362+
<section>${secHead("Projects","案件")}<div class="filters">${S.g?"":seg("ps",[["all","すべて"],["active","実施中"],["pipeline","計画中"],["closed","終了"]],S.ps)} ${kindSelect(base)} ${seg("psort",[["start","開始日"],["amount","金額"]],S.psort)}</div>${projectListHTML(pj)}</section>
13571363
<section>${secHead("News","関連ニュース")}${listHTML(list)}</section>
13581364
</div>`;
13591365
}
@@ -1578,7 +1584,10 @@ <h2>${esc(s.ja)} ${favStar(s.id)}</h2></div>${jica}</div>
15781584
const total = new Set(R.groups.flatMap(g=>g.reports.map(r=>r.u))).size;
15791585
const rep = r => `<li class="rp"><a href="${esc(r.u)}" target="_blank" rel="noopener">${esc(r.t)}</a>${r.pdf?'<span class="tag lang">PDF</span>':""}
15801586
<span class="rp-m">${esc(r.by)}・${esc(r.y)}</span>${r.note?`<span class="rp-n">${esc(r.note)}</span>`:""}<span class="rp-o">${esc(r.o)}</span></li>`;
1581-
const grp = g => {
1587+
const grp = (g, gi) => {
1588+
const sf = projReady() ? subField(sec+"."+gi) : null, dn = {};
1589+
if(sf) for(const p of sf.set) if(p.donor && p.donor!=="gov" && DONORS[p.donor]) dn[p.donor] = (dn[p.donor]||0)+1;
1590+
const dchips = Object.entries(dn).sort((a,b)=>b[1]-a[1]);
15821591
// Kyrgyz side: branches of the national programme tree; donor side: the reading list
15831592
// "tree:node" points into another sector's tree (e.g. irrigation under the water tree)
15841593
const st = (g.strat || (g.node ? [g.node] : [])).map(ref=>{ const [t, id] = ref.includes(":") ? ref.split(":") : [R.strategy, ref]; return [t, id, treeNode(t, id)]; }).filter(([,,n])=>n);
@@ -1587,12 +1596,13 @@ <h2>${esc(s.ja)} ${favStar(s.id)}</h2></div>${jica}</div>
15871596
return `<details class="rg"><summary><span class="rg-n">${esc(g.n)}</span><span class="rg-c">${st.length+pg.length?`戦略${st.length+pg.length}・`:""}文書${g.reports.length}</span></summary>
15881597
<p class="note">${esc(g.d)}</p>
15891598
${st.length||pg.length?`<h4 class="rg-h">キルギスの戦略・プログラム</h4><ul class="rpl">${st.map(([t,id,n])=>`<li class="rp"><button class="linkbtn rg-st" data-st="${esc(t)}" data-tnode="${esc(id)}">${n.type?`<span class="ttype t-${TYPE_CLS[n.type]||"x"}">${esc(n.type)}</span> `:""}${esc(n.n)} →</button>${n.m?`<span class="rp-o">${esc(n.m)}</span>`:""}</li>`).join("")}${pg.map(x=>`<li class="rp"><button class="linkbtn rg-st" data-st="${esc(x.id)}"><span class="ttype t-prog">プログラム</span> ${esc(x.name)} →</button><span class="rp-o">${esc([x.basis, x.status].filter(Boolean).join("・"))}</span></li>`).join("")}</ul>`:""}
1599+
${dchips.length?`<h4 class="rg-h">この小分類で動いているドナー(実施中の案件)</h4><div class="rg-d">${dchips.map(([d,n])=>`<button class="chip" data-gdonor="${esc(d)}" data-g="${esc(sec+"."+gi)}">${esc(donorName(d))} ${n}件</button>`).join("")}</div>`:""}
15901600
<h4 class="rg-h">ドナー・国際機関の文書</h4>
15911601
${g.reports.length?`<ul class="rpl">${g.reports.map(rep).join("")}</ul>`:'<p class="note">まだ選んでいません(準備中)。</p>'}
15921602
</details>`;
15931603
};
15941604
return `${secHead("Reports","重要レポート",`<span class="note">小分類ごとのキルギスの戦略と必読文書 ${total}本・${esc(R.updated)}時点</span>`)}
1595-
<div class="panel rgs">${R.groups.map(grp).join("")}</div>`;
1605+
<div class="panel rgs">${R.groups.map((g,i)=>grp(g,i)).join("")}</div>`;
15961606
}
15971607
// active projects of the tree's sector, each placed on the first branch whose words match its title (then its description)
15981608
function treePlace(t){
@@ -1609,6 +1619,26 @@ <h4 class="rg-h">ドナー・国際機関の文書</h4>
16091619
}
16101620
return {by, un, n};
16111621
}
1622+
// one sub-field of a sector's key-report table ("health.1" = REPORTS.health.groups[1]) and the active projects
1623+
// placed on its tree branches (with their sub-branches); null when the sub-field has no tree branch
1624+
const placeCache = new Map();
1625+
function placed(strategy){
1626+
const key = strategy+"|"+SHOW_JP+"|"+(PROJECTS?PROJECTS.length:0);
1627+
if(!placeCache.has(key)) placeCache.set(key, treePlace(TREES[strategy]));
1628+
return placeCache.get(key);
1629+
}
1630+
function subField(ref){
1631+
const [sec, i] = String(ref||"").split("."), R = REPORTS[sec], g = R && R.groups[+i];
1632+
if(!g) return null;
1633+
const refs = (g.strat || (g.node ? [g.node] : [])).map(r=>r.includes(":") ? r.split(":") : [R.strategy, r]).filter(([t, id])=>TREES[t] && treeNode(t, id));
1634+
if(!refs.length) return null;
1635+
const set = new Set();
1636+
for(const [t, id] of refs){
1637+
const pl = placed(t), walk = n => { for(const p of pl.by[n.id]||[]) set.add(p); (n.kids||[]).forEach(walk); };
1638+
walk(treeNode(t, id));
1639+
}
1640+
return {sec, name:g.n, set};
1641+
}
16121642
const TYPE_CLS = {"プログラム":"prog","分野":"area","戦略":"strat","規則":"rule","制度":"sys","計画":"strat","法律":"rule"};
16131643
const CONF_CLS = {"法令":"c-law","本文":"c-law","本文一部":"c-doc","本文旧版":"c-doc","資料":"c-doc","報道":"c-news","案":"c-draft","要確認":"c-guess","推定":"c-guess"};
16141644
function treeHTML(t, title=""){
@@ -1846,7 +1876,7 @@ <h2>${esc(x.name)}</h2>${x.ru?`<p class="ru">${esc(x.ru)}</p>`:""}
18461876
}
18471877

18481878
/* ---------- events ---------- */
1849-
$("#tabs").addEventListener("click", e=>{ const b = e.target.closest("button"); if(b) go({v:b.dataset.v, s:"", d:"", t:"all", sub:"pa", ps:"active", k:""}); });
1879+
$("#tabs").addEventListener("click", e=>{ const b = e.target.closest("button"); if(b) go({v:b.dataset.v, s:"", d:"", g:"", t:"all", sub:"pa", ps:"active", k:""}); });
18501880
$("#home").onclick = ()=>{ $("#q").value=""; go({v:"home", s:"", d:"", q:""}); };
18511881
$("#sform").addEventListener("submit", e=>{ e.preventDefault(); $("#q").blur(); go({v:"feed", q:$("#q").value.trim(), t:"all", p:0, s:"", d:""}); });
18521882
const main = $("#main");
@@ -1878,8 +1908,10 @@ <h2>${esc(x.name)}</h2>${x.ru?`<p class="ru">${esc(x.ru)}</p>`:""}
18781908
const ys = t.closest("[data-ysel]"); if(ys){ CH.ysel = 0; lsSet("km-charts", CH); render(); return; }
18791909
if(t.closest("[data-cover]")){ e.stopPropagation(); SHOW_COVER = !SHOW_COVER; if(S.v!=="home") go({v:"home"}); else render(); return; }
18801910
const uk = t.closest("[data-unknown]"); if(uk){ go({v:"projects", s:uk.dataset.unknown, ua:"1", ps:"all", d:"", k:""}); return; }
1881-
const don = t.closest("[data-donor]"); if(don){ go({v:"donor", d:don.dataset.donor, s:S.v==="sector"&&secList(S.s).length?S.s:"", q:"", ps:"all", k:""}); return; }
1882-
if(t.closest("[data-allsec]")){ go({s:""}); return; }
1911+
const gd = t.closest("[data-gdonor]"); if(gd){ go({v:"donor", d:gd.dataset.gdonor, s:gd.dataset.g.split(".")[0], g:gd.dataset.g, q:"", ps:"active", k:""}); return; }
1912+
const don = t.closest("[data-donor]"); if(don){ go({v:"donor", d:don.dataset.donor, s:S.v==="sector"&&secList(S.s).length?S.s:"", g:"", q:"", ps:"all", k:""}); return; }
1913+
if(t.closest("[data-allsec]")){ go({s:"", g:""}); return; }
1914+
if(t.closest("[data-allsub]")){ go({g:"", ps:"all"}); return; }
18831915
const st = t.closest("[data-star]");
18841916
if(st){
18851917
const id = st.dataset.star;
@@ -1913,7 +1945,7 @@ <h2>${esc(x.name)}</h2>${x.ru?`<p class="ru">${esc(x.ru)}</p>`:""}
19131945
});
19141946
main.addEventListener("change", e=>{
19151947
const c = e.target.closest("[data-chsel]"); if(c){ CH[c.dataset.chsel] = c.value; lsSet("km-charts", CH); render(); return; }
1916-
const s = e.target.closest("[data-sel]"); if(s) go({[s.dataset.sel]: s.dataset.sel==="p" ? +s.value : s.value}, false); });
1948+
const s = e.target.closest("[data-sel]"); if(s) go({[s.dataset.sel]: s.dataset.sel==="p" ? +s.value : s.value, ...(s.dataset.sel==="s" ? {g:""} : {})}, false); });
19171949
let qTimer = 0, composing = null; // the filter box a Japanese word is being composed in
19181950
function filterLater(i){
19191951
clearTimeout(qTimer);

0 commit comments

Comments
 (0)