feat(M02): 항목 추가 창 = 종류 + 이름 + 일위대가 · 카드 넷 2×2 · 표 접기 (PLAN 31-2)

- 항목 추가(나무 「항목 추가」 · 「새 항목」) — 구조물 종류(있는 것 고름 · 새 이름 · 폴더를 골라 두었으면 그 안) + 이름 + 일위대가(세 층 조합 고르기 창 · 찾기 AND · 비워도 됨) · 단위 칸 없앰 · 빈 칸 안내 · 서버 add-item 이 일위대가 받음
- 일위대가 고르기 창을 pickCombo 로 내보내 표 칸 고르기와 같이 씀
- 카드 넷(도면 정보 · 면적산출 · 재료산출 · 선택 입력) 2행 2열 · 높이 360px · 넘치면 카드 안 스크롤 · 상세 칸 전체 스크롤(좁으면 1열)
- 「구조물 조합」 제목을 누르면 표 접힘 · 펴짐
- 시험 — 항목 추가 서버 · 창 · 격자 · 접기 · 옛 단위 필수 시험을 31 정함으로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 20:19:11 +09:00
co-authored by Claude Opus 5.5
parent 3913258df9
commit b981911755
15 changed files with 266 additions and 49 deletions
@@ -166,6 +166,34 @@ function choose(title: string, items: Choice[], clearable: boolean): Promise<str
});
}
/** 일위대가 고르기 창 — 이 층이 쓸 수 있는 세 층 조합 · 찾기 = 띄어쓰기 AND · 「비움」 = 키 빈 글 · 닫으면 null
* (표 칸 고르기 · 항목 추가 창이 함께 씀 · PLAN 31-2) */
export async function pickCombo(title: string): Promise<{ key: string; label: string } | null> {
let list: Awaited<ReturnType<typeof loadCombos>>;
try {
list = await loadCombos();
} catch (error) {
showToast(fill(TEXT.combosFailed, why(error)), "error");
return null;
}
const items = list.map((c) => ({
value: c.키,
key: c.키,
// 이름에 단위가 이미 붙은 조합이 있음 — 겹치면 뺌
label: [
c.이름,
c.단위 && !c.이름.includes(`(${c.단위})`) && `(${c.단위})`,
c.구분,
`· ${LAYER_WORD[c.layer]}`,
]
.filter(Boolean)
.join(" "),
}));
const key = await choose(title, items, true);
if (key === null) return null;
return { key, label: items.find((it) => it.value === key)?.label ?? "" };
}
/** 집계표 열 — 머리 글(제목행 종류 · 분류1 · 분류2 · 분류3) · 꼴만 씀 */
export interface StructureColumn {
id: string;
@@ -326,28 +354,9 @@ export function createStructurePanel(opts: StructurePanelOptions): StructurePane
};
const pickPrice = async (colId: string, columns: StructureColumn[]): Promise<string | null> => {
let list: Awaited<ReturnType<typeof loadCombos>>;
try {
list = await loadCombos();
} catch (error) {
showToast(fill(TEXT.combosFailed, why(error)), "error");
return null;
}
const col = columns.find((c) => c.id === colId);
const items = list.map((c) => ({
value: c.키,
key: c.키,
// 이름에 단위가 이미 붙은 조합이 있음 — 겹치면 뺌
label: [
c.이름,
c.단위 && !c.이름.includes(`(${c.단위})`) && `(${c.단위})`,
c.구분,
`· ${LAYER_WORD[c.layer]}`,
]
.filter(Boolean)
.join(" "),
}));
return choose(col ? `${TEXT.pickPrice} — ${columnHead(col)}` : TEXT.pickPrice, items, true);
const got = await pickCombo(col ? `${TEXT.pickPrice} — ${columnHead(col)}` : TEXT.pickPrice);
return got ? got.key : null;
};
/** 다른 열의 도면 · 면적산출 · 재료산출을 골라 내용만 복사 — 덮어쓰기 확인 · 보고 있던 열이면 다시 읽음 */