Files
Pier_v1/수량산출/project-inputs.ts
T
ypshimandClaude Haiku 5.5 fc520ec03d feat: 프로젝트 공통 지반모델·교대 지반 통합·재료표/수량 진입·옥성교 랜딩 히어로 (11-06~11-09 작업분)
공용 파일(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>
2026-11-09 18:01:02 +09:00

49 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type {PierInput} from '../src/core/types';
import type {PierModel} from '../src/core/geometry';
import {calculate,withTemplateDefaults,type QuantityInputs} from './model';
import {inputFields} from './input-panel';
import {node} from './dashboard-types';
export type QuantitySnapshot={project:{name:string;bridge:string};selected:string|null;piers:{input:PierInput;model:PierModel|null;dirty:boolean;rebarTon?:number|null;rebarIssues?:string[]}[]};
export function projectRecords(s:QuantitySnapshot){return s.piers.map(p=>{
const stored=withTemplateDefaults(p.input.quantityTakeoff??{values:{},reviewed:false});
const calculated=p.model?calculate(p.input,p.model,stored,p.rebarTon):null;
const result=calculated?{...calculated,issues:[...calculated.issues,...(p.rebarIssues??[]).map(m=>'철근재료표 일부 계산: '+m)],complete:calculated.complete&&!p.rebarIssues?.length}:null;
return {pier:p,stored,result};
});}
export function openProjectInputs(read:()=>QuantitySnapshot,save:(name:string,data:QuantityInputs)=>void,onClose:()=>void,onExport:()=>Promise<void>){
const dialog=node('dialog');dialog.className='tq-project-dialog';dialog.dataset.projectInputs='true';
const head=node('header'),title=node('h2','전체 교각 입력 · 수량산출'),close=node('button','닫기 ×');close.type='button';close.onclick=()=>dialog.close();head.append(title,close);dialog.append(head);
const status=node('p'),tools=node('div');tools.className='tq-project-tools';
const filter=node('label'),missingOnly=node('input');missingOnly.type='checkbox';missingOnly.checked=true;filter.append(missingOnly,document.createTextNode('미입력 항목만'));tools.append(filter);
const recalc=node('button','전체 재계산'),exporter=node('button','전체 교각 Excel 출력');recalc.type=exporter.type='button';tools.append(recalc,exporter);dialog.append(tools,status);
dialog.append(node('p','자동값은 모델에서 가져옵니다. 직접 입력값은 교각별 저장됩니다. 표에 Excel 셀 범위를 붙여넣을 수 있습니다. 받침 형상이 없거나 서로 다른 치수이면 직접 확인이 필요합니다.'));
const scroll=node('div');scroll.className='tq-project-table';dialog.append(scroll);
const draw=()=>{
const records=projectRecords(read());const rows=records.map(r=>({...r,fields:r.result?inputFields(r.result,r.stored,r.pier):[]}));
const columns=new Map<string,(typeof rows)[number]['fields'][number]>();
for(const r of rows)for(const f of r.fields)if(f.required&&(!missingOnly.checked||f.missing))columns.set(f.key,f);
// Show real bearing geometry even while reviewing only missing conditions.
for(const r of rows)for(const f of r.fields)if(['r40','r41'].includes(f.key))columns.set(f.key,f);
const keys=[...columns.keys()],table=node('table'),thead=node('thead'),tr=node('tr');tr.append(node('th','교각 / 상태'));
for(const f of columns.values()){const th=node('th',f.name+(f.unit?' · '+f.unit:''));th.title=f.source;tr.append(th);}tr.append(node('th','원본 기준 검토'));thead.append(tr);table.append(thead);
const body=node('tbody');table.append(body);let missing=0;
for(const [ri,r]of rows.entries()){
const row=node('tr');row.dataset.projectPier=r.pier.input.name;
const label=node('th',r.pier.input.name+' · '+r.pier.input.type);const issues=r.pier.dirty?['형상 재생성 필요']:!r.result?['형상 없음']:r.result.issues;
label.append(node('small',issues.length?issues.join(' / '):r.result?.missing.length?'누락 '+r.result.missing.length+'개':r.stored.reviewed?'계산 가능':'기준 검토 필요'));row.append(label);missing+=r.result?.missing.length??1;
for(const key of keys){const f=r.fields.find(f=>f.key===key),td=node('td');if(!f){td.textContent='—';row.append(td);continue;}
if(f.kind==='auto'){td.append(node('output',f.value),node('small','자동'));td.title=f.source;}
else{const control=node('input');control.type=f.numeric?'number':'text';if(f.numeric)control.step='any';control.value=f.value;control.dataset.batchKey=key;control.setAttribute('aria-label',r.pier.input.name+' '+f.name);control.classList.toggle('is-missing',f.missing);control.onchange=()=>{const live=projectRecords(read()).find(x=>x.pier.input.name===r.pier.input.name)!;save(r.pier.input.name,{...live.stored,values:{...live.stored.values,[key]:control.value===''?null:f.numeric?Number(control.value):control.value},lastSignature:undefined});control.classList.toggle('is-missing',control.value==='');status.textContent='입력값 저장됨 · 전체 재계산으로 누락 및 적용 범위를 확인하세요.';};
control.onpaste=event=>{const raw=event.clipboardData?.getData('text/plain');if(!raw||!/[\t\n]/.test(raw))return;event.preventDefault();for(const [dy,line]of raw.trimEnd().split(/\r?\n/).entries())for(const [dx,value]of line.split('\t').entries()){const target=body.rows[ri+dy]?.querySelector<HTMLInputElement>(`[data-batch-key="${keys[keys.indexOf(key)+dx]}"]`);if(target){target.value=value.trim();target.dispatchEvent(new Event('change'));}}};td.append(control,node('small',f.kind==='template'?'원본 Excel':'직접 입력'));
}row.append(td);
}
const td=node('td'),review=node('input');review.type='checkbox';review.checked=r.stored.reviewed;review.setAttribute('aria-label',r.pier.input.name+' 원본 기준 확인');review.onchange=()=>{const live=projectRecords(read()).find(x=>x.pier.input.name===r.pier.input.name)!;save(r.pier.input.name,{...live.stored,reviewed:review.checked,lastSignature:undefined});};td.append(review);row.append(td);body.append(row);
}
scroll.replaceChildren(table);status.textContent=`전체 ${rows.length}개 교각 · 미입력 ${missing}개 · 교각별 산출서 합산 방식`;
};
missingOnly.onchange=draw;
recalc.onclick=()=>{for(const r of projectRecords(read()))if(r.result)save(r.pier.input.name,{...r.stored,lastSignature:r.result.signature});draw();};
exporter.onclick=async()=>{exporter.disabled=true;try{await onExport();status.textContent='전체 교각 Excel 출력 완료';}catch(error){status.textContent=(error as Error).message;status.classList.add('tq-project-error');}finally{exporter.disabled=false;}};
dialog.addEventListener('close',()=>{dialog.remove();onClose();},{once:true});document.body.append(dialog);draw();dialog.showModal();
}