feat(M02): 면적 → 도면 표 여러 장 — 새 T번호 모든 장 · 연결표 장 · 저장 때 모든 장 표(82-3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPJGtfXo6GMbcpiboQC4uK
This commit is contained in:
2026-10-08 12:10:50 +09:00
co-authored by Claude Opus 5.5
parent caf38ccb3e
commit b15a945bf8
2 changed files with 36 additions and 10 deletions
@@ -8,6 +8,8 @@
* 표 style.연결 = 연결 · 도면에서 지운 표는 기록에서 뺌(면적 [저장] 때 서버가 칸 글 · 값을 다시 적음 · 41-1)
* ③ 도면을 열 때 — 연결표마다 지금 면적 값과 마지막 값이 다르면 알림 한 줄 + [업데이트](`updateTable`)
* ④ 표 여러 개 — id 마다 한 줄
* ⑤ 여러 장(82-3) — 놓을 장 = 지금 탭 · 새 T번호 = 모든 장에서 안 쓴 것 · 연결표 줄 `장` = 표가 놓인 장id
* (1장 = 빈 값) · 안 실린 장 표 갱신 = 도면 손잡이(받아 둔 장) · 면적 [저장] 때는 서버가 모든 장
* ========================================================================== */
import { API_BASE_URL } from "@config/config_frontend";
@@ -108,6 +110,12 @@ async function fetchTable(layer: Layer, column: string, spec: LinkSpec): Promise
return data as AreaTable;
}
/** 장마다 `[장id, entities]` — 1장 = 빈 값(서버 `drawing_sheets` 와 같음) */
const sheetsOf = (doc: StructureDoc): [string, unknown[]][] => [
["", doc.도면?.entities ?? []],
...(doc.더한장 ?? []).map((s): [string, unknown[]] => [s.장id, s.도면?.entities ?? []]),
];
const isTable = (e: unknown): e is TableEntityJson =>
!!e && typeof e === "object" && (e as { type?: unknown }).type === "Table";
const linkOf = (e: TableEntityJson): string | null => {
@@ -241,7 +249,8 @@ export function createAreaToDrawing(opt: AreaToDrawingOptions): AreaToDrawing {
/** 다음 연결 id — 기록 · 도면 표지에 없는 가장 작은 T번호 */
const nextId = (doc: StructureDoc, taken: Set<string>): string => {
for (const row of doc.연결표 ?? []) taken.add(row.연결);
for (const e of doc.도면?.entities ?? []) if (isTable(e)) taken.add(linkOf(e) ?? "");
for (const [, entities] of sheetsOf(doc))
for (const e of entities) if (isTable(e)) taken.add(linkOf(e) ?? "");
let n = 1;
while (taken.has(`T${n}`)) n += 1;
return `T${n}`;
@@ -323,12 +332,14 @@ export function createAreaToDrawing(opt: AreaToDrawingOptions): AreaToDrawing {
waiting = false;
const old = new Map((doc.연결표 ?? []).map((row) => [row.연결, row]));
const rows: TableLink[] = [];
for (const e of doc.도면?.entities ?? []) {
if (!isTable(e)) continue;
const id = linkOf(e);
const row = id ? (old.get(id) ?? fresh.get(id)) : undefined;
if (!id || !row || rows.some((r) => r.연결 === id)) continue;
rows.push({ ...row, 값: latest.get(id) ?? row.값 });
for (const [sheet, entities] of sheetsOf(doc)) {
for (const e of entities) {
if (!isTable(e)) continue;
const id = linkOf(e);
const row = id ? (old.get(id) ?? fresh.get(id)) : undefined;
if (!id || !row || rows.some((r) => r.연결 === id)) continue;
rows.push({ ...row, 장: sheet, 값: latest.get(id) ?? row.값 });
}
}
// 기록이 없던 도면은 칸을 안 만듦(옛 문서 그대로)
return rows.length || doc.연결표 ? { ...doc, 연결표: rows } : doc;
+18 -3
View File
@@ -112,6 +112,16 @@ globalThis.fetch = async (url, init) => {
await slot.kids[0].on.click();
await new Promise((r) => setTimeout(r, 20));
ad.onDrawing(handle, { ...area, kind: "structure", name: "item2" }, drawing([]));
const other = placed.length - before;
// 여러 장(82-3) — 둘째 장 T2 가 있으니 새 표는 T3 · 저장 연결표 줄 장 = 표가 놓인 장id
ad.select({ 시트: "산출", 칸: "A1", 범위: "A1" });
await slot.kids[0].on.click();
await new Promise((r) => setTimeout(r, 20));
const multi = drawing([table([["a"]], { 연결: "T1" })], [old]);
multi.더한장 = [{ 장id: "s2", 이름: "2장", 도면: { entities: [table([["b"]], { 연결: "T2" })] } }];
ad.onDrawing(handle, { ...area, kind: "structure" }, multi);
const multiId = placed.at(-1)[1];
const multiSaved = ad.withLinks({ ...multi, 연결표: [old, { ...old, 연결: "T2" }] }).연결표.map((r) => [r.연결, r.장]);
// 빈 칸만 고른 범위 — 크기를 묻기 전에 막고 알림 · 도면 안 엶
now = [["", " "]];
const openedBefore = opened.length;
@@ -119,7 +129,7 @@ globalThis.fetch = async (url, init) => {
await slot.kids[0].on.click();
await new Promise((r) => setTimeout(r, 20));
const empty = { opened: opened.length - openedBefore, toast: toasts.at(-1) };
console.log(JSON.stringify({ ro, buttons: slot.kids.length, sent, placed, notice: notice.map((x) => (typeof x === "string" ? x : x.label)), saved: saved.연결표, plain: "연결표" in plain, other: placed.length - before, waitPlaced, waitDropped, waitSaved: ad.waiting(), empty }));
console.log(JSON.stringify({ ro, buttons: slot.kids.length, sent, placed, notice: notice.map((x) => (typeof x === "string" ? x : x.label)), saved: saved.연결표, multiId, multiSaved, plain: "연결표" in plain, other, waitPlaced, waitDropped, waitSaved: ad.waiting(), empty }));
})();
"""
@@ -161,12 +171,17 @@ def test_다시_열면_다름_알림과_업데이트(got):
def test_저장_연결표는_남은_표만(got):
now = {"s1!B2": "1.5", "s1!C2": "2"}
assert got["saved"] == [
{"연결": "T1", "시트": "s1", "범위": "A1:A1", "글자크기": 3, "값": now}, # [업데이트] 값
{"연결": "T2", "시트": "s1", "범위": "B2:C2", "글자크기": 2.5, "값": now},
{"연결": "T1", "장": "", "시트": "s1", "범위": "A1:A1", "글자크기": 3, "값": now},
{"연결": "T2", "장": "", "시트": "s1", "범위": "B2:C2", "글자크기": 2.5, "값": now},
]
assert got["plain"] is False
def test_여러_장_새_번호는_모든_장에서_안_쓴_것_연결표_장(got):
assert got["multiId"] == "T3"
assert got["multiSaved"] == [["T1", ""], ["T2", "s2"]]
def test_다른_열_도면에는_안_놓음(got):
assert got["other"] == 0