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}
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
671674const 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)
15981608function 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+ }
16121642const TYPE_CLS = { "プログラム" :"prog" , "分野" :"area" , "戦略" :"strat" , "規則" :"rule" , "制度" :"sys" , "計画" :"strat" , "法律" :"rule" } ;
16131643const CONF_CLS = { "法令" :"c-law" , "本文" :"c-law" , "本文一部" :"c-doc" , "本文旧版" :"c-doc" , "資料" :"c-doc" , "報道" :"c-news" , "案" :"c-draft" , "要確認" :"c-guess" , "推定" :"c-guess" } ;
16141644function 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 :"" } ) ; } ) ;
18521882const 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} ) ;
19141946main . 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 ) ; } ) ;
19171949let qTimer = 0 , composing = null ; // the filter box a Japanese word is being composed in
19181950function filterLater ( i ) {
19191951 clearTimeout ( qTimer ) ;
0 commit comments