import type {PierModel} from '../src/core/geometry'; import {createExcelPreview} from './excel-preview'; import {DETAIL,INPUTS,extraNumbers} from './schema'; import {cellKey} from './formula'; import {node,fmt,type TakeoffResult,type Selection} from './dashboard-types'; import {rowStatus} from './results-panel'; import type {QuantityInputs} from './model'; export function createReviewPanel(o:{result:TakeoffResult;stored:QuantityInputs;pier:string;project:{name:string;bridge:string};projectRows:{name:string;result:TakeoffResult}[];plan:{selected:PierModel;models:readonly PierModel[]};stale:boolean;miniState:{sheet:string;zoom:number};onCell:(cell:string)=>void}){ const panel=node('aside',undefined,'tq-right-panel'),preview=node('section',undefined,'tq-panel tq-mini-panel');const heading=node('div',undefined,'tq-panel-heading');heading.append(node('h3','산출서 미리보기'));const expand=node('button','⛶');expand.type='button';expand.setAttribute('aria-label','산출서 미리보기 전체화면');heading.append(expand);preview.append(heading); const mini=createExcelPreview({result:o.result,stored:o.stored,pier:o.pier,project:o.project,plan:o.plan,projectRows:o.projectRows,state:o.miniState,compact:true,onCell:o.onCell});preview.append(mini);panel.append(preview); const trace=node('section',undefined,'tq-panel tq-formula-panel takeoff-trace');trace.setAttribute('aria-live','polite');panel.append(trace); let dialogTrace:HTMLElement|undefined,hasSelection=false; const open=()=>{const dialog=node('dialog',undefined,'tq-preview-dialog tq-report-dialog');dialog.dataset.quantityPreviewDialog='true';dialog.setAttribute('aria-label','수량산출서 확대 및 산식 상세');const close=node('button','닫기 ×');close.type='button';close.className='tq-dialog-close';close.onclick=()=>dialog.close();const grid=node('div',undefined,'tq-report-grid'),documentView=createExcelPreview({result:o.result,stored:o.stored,pier:o.pier,project:o.project,plan:o.plan,projectRows:o.projectRows,state:{sheet:'표지및목차',zoom:0},onCell:o.onCell});dialogTrace=trace.cloneNode(true) as HTMLElement;dialogTrace.classList.add('tq-dialog-trace');grid.append(documentView,dialogTrace);dialog.append(close,grid);dialog.addEventListener('close',()=>{dialogTrace=undefined;dialog.remove();expand.focus({preventScroll:true});},{once:true});document.body.append(dialog);dialog.showModal();}; expand.onclick=open; const select=(s:Selection)=>{ trace.replaceChildren(node('h3','산식 상세'),node('p',s.title,'tq-selected-title')); const block=(label:string,content:HTMLElement)=>{const row=node('div',undefined,'tq-formula-block');row.append(node('b',label),content);trace.append(row);}; block('산식',node('pre',s.rows.length===1?String(s.rows[0].description??s.formula):s.formula)); const inputs=node('dl',undefined,'tq-formula-inputs');const deps=new Set(s.rows.flatMap(r=>r.dependencies)); for(const v of o.result.mapped){const key=cellKey(INPUTS,'N'+v.key.slice(1));if(!deps.has(key))continue;let value:string;try{value=String(o.result.book.cell(INPUTS,'N'+v.key.slice(1)));}catch{value='미입력';}inputs.append(node('dt',v.symbol+' · '+v.name),node('dd',value+' '+v.unit));} for(const v of extraNumbers){if(!deps.has(cellKey(DETAIL,v.cell)))continue;let value:string;try{value=String(o.result.book.cell(DETAIL,v.cell));}catch{value='미입력';}inputs.append(node('dt',v.name),node('dd',value+' '+v.unit));} if(inputs.children.length)block('입력값',inputs); block('계산식',node('pre',s.substitution)); const answer=node('div',undefined,'tq-formula-answer');answer.append(node('b','결과'),node('strong',fmt(s.value)+' '+s.unit));const status=rowStatus(s.rows,o.stale,!!o.result.issues.length,o.stored.reviewed);answer.append(node('span',status.text,'tq-status-tag '+status.kind));trace.append(answer); const source=node('details',undefined,'tq-formula-source');source.append(node('summary','원본식 · 중간 계산값 · 참조 셀'));source.append(node('pre',s.formula)); for(const r of s.rows)if(r.formula!==r.sourceFormula)source.append(node('p',r.cell==='W64'?'W64 · 생성 모델의 판별 체적 합계 사용. Excel 입력을 바꾸면 앱에서 형상을 재생성해 다시 출력하세요.':r.cell+' · 직선 모델: 원본 곡률항의 R→∞ 극한 적용'),node('pre','원본: '+r.sourceFormula)); for(const key of deps){const ix=key.lastIndexOf('!'),sheet=key.slice(0,ix),cell=key.slice(ix+1);if(s.rows.some(r=>sheet===DETAIL&&r.cell===cell))continue;let value:string;try{value=String(o.result.book.cell(sheet,cell));}catch{value='미입력';}source.append(node('p',key+' = '+value));}trace.append(source); if(dialogTrace)dialogTrace.replaceChildren(...Array.from(trace.childNodes,n=>n.cloneNode(true))); if(hasSelection)mini.dispatchEvent(new CustomEvent('takeoff:focus-cells',{detail:s.rows.map(r=>r.cell)}));hasSelection=true; }; return {panel,select,open,focusPreview:()=>{preview.tabIndex=-1;preview.focus();}}; }