공용 파일(main.ts·index.html·style.css)에서 서로 얽혀 있어 한 커밋으로 묶음. - 지반: ① 형상 프로젝트 지반모델 관리창(생성·JSON 불러오기·교각별 비교·선택 적용), 프로젝트 JSON 내장 groundModel 추출, 교대 8행 통합 적용·공통지형 중복격자 제외 - 재료표·수량: 상단 ⑤ 메뉴·배근 바로가기, 결과 교각/탭 기억, 수량 워크북·정합 보정 - 랜딩: 옥성교 6교각 히어로 이미지·투영 강조점·좌→우 광선·지형 라운드/도로 연결 - 검증 스크립트(verify-project-ground/ground-abutments/okseong-user-files/quantity-*/landing-hero 등) 정본: DOC/04_실행/실행_*_2026-11-09_v0.1.00.md Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
33 lines
4.9 KiB
TypeScript
33 lines
4.9 KiB
TypeScript
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();}};
|
||
}
|