feat(m02): 면적 아래 알약 막대에 도면 알약 — 범위 · 상태 · 누르면 고름 · × 보내기 취소(114-3)

- 「T1 도면 E7:J17」 + 공용 꼬리표 상태(값 맞음 · 값 다름 · 도면에 표 없음 · 범위 없음) · 테두리 = 시트 위 점선 색
- 상태는 지금 책으로 판정(areaTable → sameValues) · 값이 바뀌면 150ms 모아 다시
- × = 기존 [보내기 취소](화면에서만 · 면적 저장 때 기록) · 시트 위 이름표 그대로
- 보낸 범위 몫을 spreadsheet_link_range.ts 로 나눔(700줄)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
This commit is contained in:
2026-10-08 18:33:53 +09:00
co-authored by Claude Opus 5.5
parent ad511175bf
commit 8352e2e5b8
5 changed files with 340 additions and 48 deletions
@@ -13,6 +13,8 @@
* 면적 [저장] 문서에 `보내기취소[]` 를 실어 서버가 그 연결표 줄 · 이름을 뺌(도면 표는 남음 · 112-2)
* ⑤ 여러 장(82-3) — 놓을 장 = 지금 탭 · 새 T번호 = 모든 장에서 안 쓴 것 · 연결표 줄 `장` = 표가 놓인 장id
* (1장 = 빈 값) · 안 실린 장 표 갱신 = 도면 손잡이(받아 둔 장) · 면적 [저장] 때는 서버가 모든 장
* ⑦ 이름 없는 옛 연결 — 면적을 열 때 책에 `도면_{연결}` 이름(114-1 · 면적 [저장] 때 서버가 줄에 적음) ·
* 아래 막대 알약 상태 = 도면에 표 있나 · 지금 값 = 마지막 도면 값인가(114-3)
* ========================================================================== */
import { API_BASE_URL } from "@config/config_frontend";
@@ -31,6 +33,7 @@ import {
type StructureDoc,
} from "./M02_MasterTemplete_Api_Fetch";
import type { LinkOptions } from "../A00_Common/spreadsheet/spreadsheet";
import type { RangeState } from "../A00_Common/spreadsheet/spreadsheet_link_range";
import type { Workbook } from "../A00_Common/spreadsheet/spreadsheet_types";
import type { Selection } from "./M02_MasterTemplete_UI_Main";
@@ -87,6 +90,14 @@ const TEXT = {
tip: "도면 표 {id} 로 보낸 범위 {range} · 글자 {size}",
updated: "표 {n}개를 지금 면적 값으로 바꿈 — [저장]하면 남음",
rangeGone: "보낸 범위가 지워져 연결을 끊음 — {ids}(도면 표는 남음)",
same: "값 맞음",
sameTip: "도면 표 칸 글 = 지금 면적 값",
diff: "값 다름",
diffTip: "면적 값이 도면 표와 다름 — 면적 [저장] 때 도면 표를 맞춤",
noTable: "도면에 표 없음",
noTableTip: "도면(저장본)에 이 연결 표가 없음 — × 로 보내기 취소",
gone: "범위 없음",
goneTip: "보낸 범위가 지워짐 — 면적 [저장] 때 연결을 끊음",
} as const;
const SIZE = 2.5; // 표 글자 크기 기본(정함 41)
@@ -156,6 +167,25 @@ async function nameOld(book: Workbook, row: TableLink): Promise<string | undefin
return name;
}
/** 아래 막대 도면 알약 상태(114-3) — 도면에 표 없음 · 범위 없음 · 지금 책 값 = 마지막 도면 값이면 맞음 */
async function stateOf(
book: Workbook,
row: TableLink,
이름: string | undefined,
placed: boolean,
): Promise<RangeState> {
if (!placed) return { 글: TEXT.noTable, 종류: "danger", 설명: TEXT.noTableTip };
const { areaTable } = await import("../A00_Common/spreadsheet/spreadsheet_area_table");
try {
const got = areaTable({ book, 시트: row.시트, 범위: row.범위, 이름, 글자크기: row.글자크기 });
return sameValues(row.값, got.값)
? { 글: TEXT.same, 종류: "success", 설명: TEXT.sameTip }
: { 글: TEXT.diff, 종류: "warning", 설명: TEXT.diffTip };
} catch {
return { 글: TEXT.gone, 종류: "danger", 설명: TEXT.goneTip };
}
}
/** 면적 시트에 겹쳐 보일 「도면으로 보낸 범위」 — 같은 열 도면 `연결표` 에서(받기 실패 = 없음 · 조용히) ·
* `book`(면적 책 · 화면에 싣기 전) 있으면 이름 없는 옛 연결에 이름을 둠(114-1 · 행열 넣기를 따라감) */
export async function sentRanges(
@@ -165,7 +195,12 @@ export async function sentRanges(
): Promise<NonNullable<LinkOptions["표시범위"]>> {
try {
const got = await readTemplate(at.layer, "structure", at.name, at.projectId);
const rows = (got.문서 as StructureDoc | undefined)?.연결표 ?? [];
const doc = got.문서 as StructureDoc | undefined;
const rows = doc?.연결표 ?? [];
// 도면에 그 표가 있나(114-3 · 모든 장)
const placed = new Set(
doc ? sheetsOf(doc).flatMap(([, es]) => es.filter(isTable).map(linkOf)) : [],
);
const names = await Promise.all(
rows.map((row) =>
row.이름 || !book || !Array.isArray(book.시트)
@@ -180,6 +215,7 @@ export async function sentRanges(
이름: fill(TEXT.tag, "id", row.연결),
설명: fill(fill(fill(TEXT.tip, "id", row.연결), "range", row.범위), "size", row.글자크기),
취소: async () => (onCancel(row.연결), true), // 화면에서만 — 서버는 면적 [저장] 때
상태: (now: Workbook) => stateOf(now, row, names[i], placed.has(row.연결)),
}));
} catch {
return [];