workspace-ui.js
'use strict';
(() => {
const W=window.WORKSPACE;
const e=s=>W.ctx.esc(s),i=n=>W.ctx.icon(n);
const sectionName=id=>({marketing:'マーケティング',investment:'投融資管理',treasury:'資金管理'}[id]||'マーケティング');
const photo=(doc,style,cls='')=>W.ctx.tile(['index'].includes(doc.parent)?'plan':doc.parent,W.candidateNumber(doc,style),'doc-photo '+cls,true);
const href=doc=>'#document/'+doc.id;
W.nav=()=>{
const parts=location.hash.slice(1).split('/'),doc=W.find(parts[1]);
const active=parts[0]==='department'?parts[1]:doc?.department||(['home','monthly'].includes(parts[0])?parts[0]:'marketing');
const groups=[...new Map(W.docs.filter(d=>d.department==='marketing').map(d=>[d.parent,d.parentTitle])).entries()];
const drop=id=>id==='marketing'?groups.map(([parent,title])=>`<section><a class="nav-parent" href="#folder/${parent}">${e(title)}</a>${W.docs.filter(d=>d.parent===parent).map(d=>`<a href="${href(d)}">${e(d.title)}</a>`).join('')}</section>`).join(''):`<section><a class="nav-parent" href="#department/${id}">${sectionName(id)}の一覧</a>${W.docs.filter(d=>d.department===id).map(d=>`<a href="${href(d)}">${e(d.title)}<small>構成案</small></a>`).join('')}</section>`;
return `<header class="workspace-header"><a class="brand" href="#home"><strong>SAMURAI</strong><span>社内ワークスペース</span></a><nav class="workspace-nav" aria-label="メインメニュー"><a class="top-link ${active==='home'?'is-current':''}" href="#home" ${active==='home'?'aria-current="page"':''}>TOPページ</a>${['marketing','investment','treasury'].map(id=>`<div class="nav-family" data-nav-family="${id}"><div class="nav-family-title"><a class="top-link ${active===id?'is-current':''}" href="${id==='marketing'?'#marketing':'#department/'+id}">${sectionName(id)}</a><button class="nav-disclosure" data-nav-toggle="${id}" aria-label="${sectionName(id)}の詳細フォルダー" aria-expanded="false" aria-controls="nav-drop-${id}">${i('ArrowRight')}</button></div><div class="nav-drop ${id==='marketing'?'wide':''}" id="nav-drop-${id}"><div class="nav-drop-grid">${drop(id)}</div></div></div>`).join('')}</nav><a class="month-close-link" href="#monthly">月締め・PDF ${i('FilePdf')}</a></header>`;
};
W.toolbar=id=>`<div class="month-toolbar"><label>対象月<input type="month" class="report-month" value="${e(W.state.month)}" min="2000-01" max="2099-12" aria-label="対象月"></label><span>月を切り替えて資料を確認</span><div><button class="button" data-import-values="true">数値を取り込む ${i('UploadSimple')}</button><button class="button primary" data-pdf="${e(id)}">PDFダウンロード ${i('FilePdf')}</button></div></div>`;
const cards=doc=>{const info=W.values(doc);return `<section class="document-metrics" aria-label="主要数値">${doc.metrics.map(([key,label,unit])=>`<article class="document-metric"><h2>${e(label)}</h2><p><strong>${e(W.format(info.data[key]))}</strong><span>${e(unit)}</span></p><small>${info.data[key]===undefined?'資料未接続':info.sample?'検算用サンプル':'このブラウザの入力値'}</small></article>`).join('')}</section>`;};
const valueTable=doc=>{const info=W.values(doc);return `<section class="document-table document-surface"><div class="document-section-title"><h2>終了月の数値を一覧で見る</h2><button class="text-button" data-edit-values="${e(doc.id)}">数値を入力・修正</button></div><table><caption>${e(W.state.month)} / ${e(info.source)}</caption><thead><tr><th scope="col">項目</th><th scope="col">値</th><th scope="col">単位</th></tr></thead><tbody>${doc.metrics.map(([key,label,unit])=>`<tr><th scope="row">${e(label)}</th><td>${e(W.format(info.data[key]))}</td><td>${e(unit)}</td></tr>`).join('')}</tbody></table>${Object.keys(info.data).length?'':'<p class="empty-explanation">この月の資料はまだ接続されていません。数値を入力するか、CSV・JSONを取り込むと、ここで確認してPDF保存できます。</p>'}</section>`;};
const notePanel=doc=>`<section class="document-note document-surface"><h2>${e(W.state.month)} の月締めメモ</h2><label for="document-note">差異や翌月への申し送り</label><textarea id="document-note" data-note="${e(doc.id)}" rows="4" maxlength="3000" placeholder="確認したこと、次に調べることを記録">${e(W.state.notes[W.key(doc.id)]||'')}</textarea><p class="local-save-note">${W.storageOK?'このブラウザ内に保存します。':'保存が利用できません。必要な内容はPDFへ保存してください。'}</p></section>`;
const siblingNav=(doc,all=false)=>{const list=W.docs.filter(d=>all?d.department===doc.department:d.parent===doc.parent);return `<aside class="document-siblings document-surface"><h2>${e(doc.parentTitle)}</h2><nav aria-label="関連する資料">${list.map(d=>`<a href="${href(d)}" ${d.id===doc.id?'aria-current="page"':''}>${e(d.title)}</a>`).join('')}</nav></aside>`;};
W.documentPage=(id,preview)=>{
const doc=W.find(id);if(!doc)return W.notFound();const style=Math.max(1,Math.min(10,Number(preview)||W.look(id))),info=W.values(doc),list=W.docs.filter(d=>d.parent===doc.parent),at=list.findIndex(d=>d.id===id);
W.state.recent=[id,...W.state.recent.filter(k=>k!==id)].slice(0,6);W.save();
const content=`${W.toolbar(id)}<div class="document-breadcrumb"><a href="#home">TOP</a><span>/</span><a href="${doc.department==='marketing'?'#marketing':'#department/'+doc.department}">${sectionName(doc.department)}</a><span>/</span><a href="#folder/${doc.parent}">${e(doc.parentTitle)}</a></div><div class="document-heading"><div><p class="eyebrow">${e(W.state.month)} MONTHLY REVIEW</p><h1>${e(doc.title)}</h1><p class="data-source ${info.sample?'is-sample':''}">${e(info.source)}${doc.proposed?' / フォルダー構成案':''}</p></div><div class="document-heading-actions"><button class="button" data-favorite="${id}" aria-pressed="${W.state.favorites.includes(id)}">${W.state.favorites.includes(id)?'お気に入り済み':'お気に入りに追加'}</button><a class="button" href="#document-looks/${id}">詳細10案を見る</a></div></div><div class="look-status"><span>${String(style).padStart(2,'0')} ${e(W.styles[style-1].name)}</span>${preview?`<button class="text-button" data-doc-look="${id}" data-style="${style}">この案を選ぶ</button>`:'<span>この資料の表示案</span>'}</div><div class="document-layout"><div class="document-body">${cards(doc)}<section class="sheet-visualization" aria-label="グラフと表示設定">${W.sheetControls(doc)}${W.sheetGraph(doc)}</section>${valueTable(doc)}${W.periodMonths().length>1||W.sheetChartMode(doc).id==='table'?W.periodTable(doc):''}${notePanel(doc)}</div>${siblingNav(doc)}</div><nav class="document-pagination" aria-label="前後の資料">${at>0?`<a class="button" href="${href(list[at-1])}">${i('ArrowLeft')} ${e(list[at-1].title)}</a>`:'<span></span>'}${at<list.length-1?`<a class="button" href="${href(list[at+1])}">${e(list[at+1].title)} ${i('ArrowRight')}</a>`:''}</nav>`;
return W.ctx.shell(content,'workspace-sheet document-style-'+style,doc.title);
};
W.looksPage=id=>{
const doc=W.find(id);if(!doc)return W.notFound();const info=W.values(doc);
const content=`<header class="look-intro"><a class="back-link" href="${href(doc)}">${i('ArrowLeft')} 資料へ戻る</a><h1>${e(doc.title)}の10案</h1><p>未設定だった下位フォルダーの入口と詳細画面を、異なる10のスタイルで比較できます。数値・対象月は同じです。</p><p class="local-save-note">既存のAI生成画像を資料の用途に合わせて選び、配色・字体・構成と組み合わせています。</p></header><section class="document-look-grid" aria-label="画像と詳細画面の10候補">${W.styles.map(s=>`<article class="document-look-option document-style-${s.id} ${W.look(id)===s.id?'is-selected':''}"><a class="document-look-preview" href="#document/${id}/${s.id}" aria-label="${e(s.name)}をプレビュー"><div class="mini-layout">${photo(doc,s.id)}<div><span>${e(doc.title)}</span>${doc.metrics.slice(0,2).map(([key,label,unit])=>`<p>${e(label)} <strong>${e(W.format(info.data[key]))}</strong> ${e(unit)}</p>`).join('')}</div></div></a><div class="document-look-copy"><span class="look-number">${String(s.id).padStart(2,'0')}</span><h2>${e(s.name)}</h2><p>${e(s.description)}</p><div><a class="button" href="#document/${id}/${s.id}">プレビュー</a><button class="button ${W.look(id)===s.id?'primary':''}" data-doc-look="${id}" data-style="${s.id}" aria-pressed="${W.look(id)===s.id}">${W.look(id)===s.id?'選択中':'この案を選ぶ'}</button></div></div></article>`).join('')}</section>`;
return W.ctx.shell(content,'workspace-sheet looks-page',doc.title+'の10案');
};
W.folderPage=id=>{
const docs=W.docs.filter(d=>d.parent===id);if(!docs.length)return W.notFound();
const title=docs[0].parentTitle;const content=`<header class="folder-heading"><div><p class="eyebrow">${sectionName(docs[0].department)}</p><h1>${e(title)}</h1><p>${docs.length}資料 / 必要な資料を選んで、数値と月締めの確認へ。</p></div><label class="folder-search-label">資料を探す<input id="folder-search" type="search" placeholder="資料名で検索" autocomplete="off"></label></header>${id==='aum'?'<div class="screenshot-entry"><a class="button outline" href="#aum-images">登録画像を確認・保存</a><p>登録済みの画像は自動反映。元画像の一括保存と追加・変更はこちら。</p></div>':''}<section class="subdocument-grid" aria-label="詳細フォルダー">${docs.map(doc=>`<article class="subdocument-card document-style-${W.look(doc.id)}" data-doc-title="${e(doc.title)}"><a class="subdocument-main" href="${href(doc)}">${photo(doc,W.look(doc.id))}<div><span class="subdocument-status">${W.values(doc).sample?'基準値サンプル':Object.keys(W.values(doc).data).length?'このブラウザの入力値':'資料未接続'}</span><h2>${e(doc.title)}</h2></div>${i('ArrowUpRight')}</a><div class="subdocument-bottom"><a href="#document-looks/${doc.id}">画像・詳細10案を見る</a><span>${String(W.look(doc.id)).padStart(2,'0')} 選択中</span></div></article>`).join('')}</section><p id="folder-search-empty" hidden class="search-empty">一致する資料がありません。</p>`;
return W.ctx.shell(content,'workspace forest folder-workspace',title);
};
W.departmentPage=id=>{
const docs=W.docs.filter(d=>d.department===id),title=sectionName(id);if(!docs.length)return W.notFound();
return W.ctx.shell(`<header class="folder-heading"><div><p class="eyebrow">WORKSPACE</p><h1>${title}</h1><p>以下はフォルダーの構成案です。実業務の資料と数値はまだ接続していません。</p></div></header><section class="department-grid">${docs.map(d=>`<a class="department-card" href="${href(d)}">${photo(d,W.look(d.id))}<div><span>構成案</span><h2>${e(d.title)}</h2><p>数値・資料・月締めの確認へ</p></div>${i('ArrowUpRight')}</a>`).join('')}</section>`,'workspace forest department-page',title);
};
const checks=['数値を照合','資料を確認','差異・申し送りを記録','PDFを保存'];
W.monthlyPage=()=>{
const month=W.state.month,available=W.docs.filter(d=>Object.keys(W.values(d).data).length),fav=W.docs.filter(d=>W.state.favorites.includes(d.id));
const content=`${W.toolbar('monthly')}<header class="monthly-heading"><p class="eyebrow">MONTHLY CLOSE</p><h1>${e(month)} 月締め確認</h1><p>結果の数値から資料を開き、確認した内容をPDFへ残す。</p></header><section class="monthly-stats"><article><span>資料一覧</span><strong>${W.docs.length}<small>資料</small></strong></article><article><span>数値のある資料</span><strong>${available.length}<small>資料</small></strong><p>基準値サンプルを含みます</p></article><article><span>確認チェック</span><strong data-check-count="true">${checks.filter((_,n)=>W.state.checks[month]?.[n]).length}<small>/ 4</small></strong></article></section><section class="monthly-check-panel document-surface"><h2>この月の確認</h2><div class="monthly-checks">${checks.map((label,n)=>`<label><input type="checkbox" data-month-check="${n}" ${W.state.checks[month]?.[n]?'checked':''}>${label}</label>`).join('')}</div><p class="local-save-note">チェックはこのブラウザの確認用です。承認や確定処理ではありません。</p></section><section class="monthly-available document-surface"><h2>数値から資料へ</h2>${available.length?available.map(d=>`<a href="${href(d)}"><div><h3>${e(d.title)}</h3><p>${e(W.values(d).source)}</p></div><div>${d.metrics.slice(0,2).map(([k,label,u])=>`<span>${e(label)} <strong>${e(W.format(W.values(d).data[k]))}</strong> ${e(u)}</span>`).join('')}</div>${i('ArrowUpRight')}</a>`).join(''):'<p class="empty-explanation">この月の数値はまだありません。「数値を取り込む」か各資料の入力から追加できます。</p>'}</section><section class="monthly-shortcuts"><div class="document-surface"><h2>お気に入り</h2>${fav.length?fav.map(d=>`<a href="${href(d)}">${e(d.title)} ${i('ArrowUpRight')}</a>`).join(''):'<p>各資料の「お気に入りに追加」で、ここからすぐ開けます。</p>'}</div><div class="document-surface"><h2>このブラウザで最近開いた資料</h2>${W.state.recent.length?W.state.recent.map(id=>W.find(id)).filter(Boolean).map(d=>`<a href="${href(d)}">${e(d.title)} ${i('ArrowUpRight')}</a>`).join(''):'<p>資料を開くと表示します。</p>'}</div></section><div class="monthly-csv-link"><button class="button" data-values-csv="true">この月の数値一覧をCSVで保存</button></div><section class="monthly-index document-surface"><h2>月締め資料の一覧</h2><p>PDFには数値のある資料とこの一覧、確認チェック、メモを収録します。</p><div>${W.docs.map(d=>`<a href="${href(d)}"><span>${e(d.parentTitle)}</span><strong>${e(d.title)}</strong><small>${e(W.values(d).source)}</small>${i('ArrowUpRight')}</a>`).join('')}</div></section>`;
return W.ctx.shell(content,'workspace-sheet monthly-page',month+' 月締め確認');
};
W.notFound=()=>W.ctx.shell('<h1>資料が見つかりません</h1><p>一覧から資料を選んでください。</p><a class="button primary" href="#marketing">マーケティング資料へ</a>','workspace-sheet','資料が見つかりません');
W.updateNavigation=()=>{
const families=[...document.querySelectorAll('[data-nav-family]')];let timer;
const close=()=>families.forEach(f=>{f.classList.remove('is-open');f.querySelector('button').setAttribute('aria-expanded','false');});
const open=f=>{clearTimeout(timer);close();f.classList.add('is-open');f.querySelector('button').setAttribute('aria-expanded','true');};
for(const f of families){f.addEventListener('pointerenter',()=>{if(matchMedia('(hover:hover) and (pointer:fine)').matches)open(f);});f.addEventListener('pointerleave',()=>{timer=setTimeout(()=>{if(!f.contains(document.activeElement))close();},160);});f.addEventListener('focusin',event=>{if(!event.target.matches('[data-nav-toggle]'))open(f);});f.querySelector('button').addEventListener('click',()=>{const expanded=f.classList.contains('is-open');if(expanded)close();else open(f);});f.addEventListener('focusout',()=>setTimeout(()=>{if(!f.contains(document.activeElement))close();},0));}
document.querySelector('.workspace-header')?.addEventListener('keydown',event=>{if(event.key==='Escape'){const active=families.find(f=>f.classList.contains('is-open'));close();active?.querySelector('button').focus();event.preventDefault();}});
W.closeNavigation=close;
};
})();