- DataFlow: 표 넷 = 데이터표 · 함수 판 알림 · 선/알약 경고 · 끊긴 곳 = 띠 알림 · 알약 · 칸 종류 = 배지 · 고르는 줄 = 목록 줄 · 입력 넷 = 입력 · 도구줄 여섯 · 날 색 → 공통 값 - 구조물 옵션 · 구조물 · 도면 · 면적 · 품목 고르기: 판 띠 = 띠 알림 · 한눈/종류별/부품 = 탭 · 표 둘 = 데이터표 · 체크 둘 = 체크 · 카드 · 번호 = 카드 · 배지 · 도면 도구줄 - 일위대가 · 호표 · 실무자료 · 본 화면 · 서고: 알림 = 띠 알림 · 빈 상태 · 보조 글 · 도구줄 여섯 · 옛 보라 별칭 0 - M01 로직 입력칸 = 입력 부품(작은 칸) · 일위대가 상세 줄 수 고정 시험 693 · 지킴 시험 M02 몫 더함 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
130 lines
5.4 KiB
TypeScript
130 lines
5.4 KiB
TypeScript
/* =============================================================================
|
|
* M02_MasterTemplete_AreaLink.ts
|
|
* 면적산출 상세 — 칸 지정 모달(PLAN 39-4 D5). 오른쪽 패널의 담은 로직 · 자재 줄을 누르면 엶.
|
|
* 그 줄 알약 키(로직 물량 「이름|단위」 · 자재 「자재|MT키」)를 면적산출 칸에 잇기 · 옮기기 · 풀기.
|
|
* 정본 = 면적산출 문서 `연결` 한 곳 — 스프레드시트 손잡이 `setLink`(알약 누름과 같은 규칙 · 되돌리기 됨)라
|
|
* 아래 알약 줄 · 칸 세모 · 패널 수량이 같이 따라감. 손 입력 수량은 일위대가 몫(여기 없음) · 저장은 [저장].
|
|
* ========================================================================== */
|
|
|
|
import { createButton, createInputField, el } from "@ui/ui_template_elements";
|
|
import { openModal } from "@ui/ui_template_modal";
|
|
import type { CalcValue, LinkInfo, SpreadsheetHandle } from "../A00_Common/spreadsheet/spreadsheet";
|
|
import { createText } from "@ui/ui_template_text";
|
|
|
|
const TEXT = {
|
|
title: "칸 지정 — {value}",
|
|
close: "닫기",
|
|
now: "지금 연결",
|
|
none: "연결 없음",
|
|
empty: "빈 칸",
|
|
picked: "지금 고른 칸으로 ({value})",
|
|
address: "칸 주소",
|
|
hint: "예 B6",
|
|
link: "잇기",
|
|
unlink: "풀기",
|
|
bad: "칸 주소가 틀림 — 예 B6",
|
|
readOnly: "읽기만 — 고칠 수 없음",
|
|
};
|
|
|
|
const fill = (text: string, value: string): string => text.replace("{value}", value);
|
|
|
|
export interface AreaLinkOptions {
|
|
/** 줄 이름(모달 제목) */
|
|
label: string;
|
|
/** 알약 키 */
|
|
key: string;
|
|
/** 연결에 같이 적을 이름 · 단위 · 자재 */
|
|
info: LinkInfo;
|
|
sheet: SpreadsheetHandle;
|
|
/** 시트에서 지금 고른 칸(A1) */
|
|
picked: () => string;
|
|
readOnly: boolean;
|
|
}
|
|
|
|
/** 칸 값 글 — 수 · 글 · 참거짓 · 오류 */
|
|
export const cellText = (v: CalcValue | undefined): string =>
|
|
!v ? "" : "수" in v ? v.수 : "글" in v ? v.글 : "참" in v ? String(v.참) : v.오류;
|
|
|
|
/** 지금 연결 칸 · 값 글 — 「B6 = 12.5 ㎡」 · 없으면 null */
|
|
function linkedText(opts: AreaLinkOptions): string | null {
|
|
const link = opts.sheet
|
|
.getDoc()
|
|
.시트.flatMap((s) => s.연결 ?? [])
|
|
.find((l) => l.키 === opts.key);
|
|
if (!link) return null;
|
|
const value = opts.sheet.getLinkedValues()[opts.key];
|
|
if (!value) return `${link.칸} = ${TEXT.empty}`;
|
|
return [link.칸, "=", cellText(value), opts.info.단위 ?? ""].join(" ").trim();
|
|
}
|
|
|
|
export function openAreaLink(opts: AreaLinkOptions): void {
|
|
const { close } = openModal({
|
|
title: fill(TEXT.title, opts.label),
|
|
closeLabel: TEXT.close,
|
|
dialogClass: "m02-modal m02-area-link",
|
|
mount: (body) => {
|
|
const now = el("p", { className: "ui-text ui-text--muted m02-area-link__now" });
|
|
const paint = (): void => {
|
|
const text = linkedText(opts);
|
|
now.textContent = `${TEXT.now} · ${text ?? TEXT.none}`;
|
|
unlink.disabled = opts.readOnly || !text;
|
|
};
|
|
/** 잇기 · 풀기 — 그 칸에 다른 알약이 있으면 함께 · 바꾸기 · 취소 물음(취소 = 모달 그대로) · 틀린 주소 false */
|
|
const done = async (cell: string | null): Promise<boolean> => {
|
|
// 엔진 조각은 누를 때만(정적 import 금지 — 계약 `spreadsheet_types.ts` 머리)
|
|
const [{ askShare }, { linksAt, normA1 }] = await Promise.all([
|
|
import("../A00_Common/spreadsheet/spreadsheet_link_ask"),
|
|
import("../A00_Common/spreadsheet/spreadsheet_link_set"),
|
|
]);
|
|
const a1 = cell === null ? null : normA1(cell);
|
|
if (cell !== null && !a1) return false;
|
|
const book = opts.sheet.getDoc();
|
|
const sheetId = (book.시트.find((s) => s.id === book.활성) ?? book.시트[0])?.id ?? "";
|
|
const others = a1 ? linksAt(book, sheetId, a1, opts.key) : [];
|
|
const choice = others.length
|
|
? await askShare(
|
|
a1 ?? "",
|
|
others.map((l) => l.이름 ?? l.키),
|
|
)
|
|
: "함께";
|
|
if (!choice) return true;
|
|
if (!opts.sheet.setLink(opts.key, a1, opts.info, choice === "바꾸기")) return false;
|
|
close();
|
|
return true;
|
|
};
|
|
const here = opts.picked();
|
|
const pick = createButton({
|
|
label: fill(TEXT.picked, here),
|
|
variant: "filled",
|
|
size: "sm",
|
|
onClick: () => void done(here),
|
|
});
|
|
const address = createInputField({ label: TEXT.address, placeholder: TEXT.hint });
|
|
const link = createButton({
|
|
label: TEXT.link,
|
|
variant: "ghost",
|
|
size: "sm",
|
|
onClick: () =>
|
|
void done(address.input.value).then((ok) => ok || address.setError(TEXT.bad)),
|
|
});
|
|
const unlink = createButton({
|
|
label: TEXT.unlink,
|
|
variant: "ghost",
|
|
size: "sm",
|
|
onClick: () => void done(null),
|
|
});
|
|
address.input.addEventListener("input", () => address.setError(undefined));
|
|
address.input.addEventListener("keydown", (ev) => ev.key === "Enter" && link.click());
|
|
pick.disabled = link.disabled = address.input.disabled = opts.readOnly;
|
|
paint();
|
|
body.append(
|
|
now,
|
|
...(opts.readOnly ? [createText(TEXT.readOnly)] : []),
|
|
el("div", { className: "m02-area-link__row", children: [pick, unlink] }),
|
|
el("div", { className: "m02-area-link__row", children: [address.root, link] }),
|
|
);
|
|
if (!opts.readOnly) address.input.focus();
|
|
},
|
|
});
|
|
}
|