feat(M02): 엑셀 칸 고르기 모달 부품 AreaCellPick — 그 열 면적산출 읽기만 시트 · 칸 누름 = 주소 · 연결 풀기 · 순수 linkAreaCell(면적 문서 · 알약 키 · 칸) (PLAN 40-3 ①)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W4uKaV4ZBhsJN4GHG7yRj4
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
/* =============================================================================
|
||||
* M02_MasterTemplete_AreaCellPick.ts
|
||||
* 엑셀 칸 고르기 모달(PLAN 40-3) — 그 열 면적산출 문서를 읽기만 시트로 띄우고 칸을 누르면 그 주소를 돌려줌.
|
||||
* 일위대가 상세(sub2)가 「수량 → 엑셀에서 고르기」 에 씀 → 돌려받은 칸을 `linkAreaCell` 로 면적 문서에 이어
|
||||
* 일위대가 [저장] 때 함께 저장(여기는 저장 안 함).
|
||||
* · 이미 이은 칸은 시트에 색 · 세모(알약 색 · `spreadsheet_links`) · 이 알약이 이은 칸은 머리 글에
|
||||
* · 칸 누름 · Enter = 그 칸 · [연결 풀기] = 풀기 · 닫기 · Esc · 바깥 = 그만둠(null)
|
||||
* ========================================================================== */
|
||||
|
||||
import { createButton, el } from "@ui/ui_template_elements";
|
||||
import { openModal } from "@ui/ui_template_modal";
|
||||
import type { SpreadsheetHandle, Workbook } from "../A00_Common/spreadsheet/spreadsheet";
|
||||
import { readTemplate, type Layer } from "./M02_MasterTemplete_Api_Fetch";
|
||||
import "./M02_MasterTemplete_AreaPanel.css";
|
||||
|
||||
export { linkAreaCell } from "../A00_Common/spreadsheet/spreadsheet_link_set";
|
||||
|
||||
const TEXT = {
|
||||
title: "엑셀에서 고르기 — {value}",
|
||||
close: "닫기",
|
||||
hint: "칸을 누르면 그 칸으로 이음 · Enter 도 됨",
|
||||
now: "지금 연결 {value}",
|
||||
none: "지금 연결 없음",
|
||||
unlink: "연결 풀기",
|
||||
loading: "면적산출을 읽는 중…",
|
||||
failed: "면적산출을 못 읽음 — {value}",
|
||||
};
|
||||
|
||||
const fill = (text: string, value: string): string => text.replace("{value}", value);
|
||||
|
||||
export interface AreaCellPickOptions {
|
||||
/** 그 열 면적산출 문서(저장 안 한 것 포함) — 없으면 `layer` · `column` 저장본을 읽음 */
|
||||
doc?: Workbook | null;
|
||||
layer: Layer;
|
||||
/** 집계표 열 id */
|
||||
column: string;
|
||||
/** 알약 키(로직 「이름|단위」 · 자재 「자재|MT키」) — 지금 이은 칸을 보임 */
|
||||
pill: string;
|
||||
/** 모달 제목에 쓸 줄 이름 */
|
||||
label: string;
|
||||
}
|
||||
|
||||
/** 고른 결과 — `null` = 그만둠 · `{칸: null}` = 연결 풀기 · `{칸: "B6"}` = 그 칸 */
|
||||
export type AreaCellPick = { 칸: string | null } | null;
|
||||
|
||||
export function pickAreaCell(opts: AreaCellPickOptions): Promise<AreaCellPick> {
|
||||
return new Promise((resolve) => {
|
||||
let handle: SpreadsheetHandle | null = null;
|
||||
let done = false;
|
||||
let picked = "A1";
|
||||
let moved = false; // 누른 사이 고름이 바뀜 — 머리 · 굴림 막대를 눌렀다 뗀 것은 안 고름
|
||||
const finish = (value: AreaCellPick): void => {
|
||||
if (done) return;
|
||||
done = true;
|
||||
handle?.destroy();
|
||||
handle = null;
|
||||
resolve(value);
|
||||
};
|
||||
const { close } = openModal({
|
||||
title: fill(TEXT.title, opts.label),
|
||||
closeLabel: TEXT.close,
|
||||
dialogClass: "m02-modal m02-cellpick",
|
||||
beforeClose: () => (finish(null), true),
|
||||
mount: (body) => {
|
||||
const head = el("p", { className: "m02-cellpick__now", text: TEXT.loading });
|
||||
const unlink = createButton({
|
||||
label: TEXT.unlink,
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
onClick: () => (finish({ 칸: null }), close()),
|
||||
});
|
||||
unlink.hidden = true;
|
||||
const host = el("div", { className: "m02-cellpick__sheet" });
|
||||
body.append(
|
||||
el("div", { className: "m02-cellpick__head", children: [head, unlink] }),
|
||||
el("p", { className: "m02-cellpick__hint", text: TEXT.hint }),
|
||||
host,
|
||||
);
|
||||
const choose = (): void => (finish({ 칸: picked }), close());
|
||||
void (async () => {
|
||||
try {
|
||||
const [{ createSpreadsheet }, book] = await Promise.all([
|
||||
import("../A00_Common/spreadsheet/spreadsheet"),
|
||||
opts.doc
|
||||
? Promise.resolve(opts.doc)
|
||||
: readTemplate(opts.layer, "area", opts.column, null).then(
|
||||
(got) => got.문서 as Workbook,
|
||||
),
|
||||
]);
|
||||
if (done) return;
|
||||
const at = book.시트.flatMap((s) => s.연결 ?? []).find((l) => l.키 === opts.pill)?.칸;
|
||||
head.textContent = at ? fill(TEXT.now, at) : TEXT.none;
|
||||
unlink.hidden = !at;
|
||||
handle = createSpreadsheet(host, structuredClone(book), {
|
||||
readOnly: true,
|
||||
tabs: false,
|
||||
onSelect: (s) => {
|
||||
picked = s.칸;
|
||||
moved = true;
|
||||
},
|
||||
});
|
||||
// 칸을 눌러 뗌 = 그 칸(처음 고름 A1 · 화살표 옮김은 안 닫음)
|
||||
const grid = host.querySelector(".ss-grid-host");
|
||||
grid?.addEventListener("mousedown", () => (moved = false), true);
|
||||
grid?.addEventListener("mouseup", (e) => {
|
||||
if ((e as MouseEvent).button === 0 && moved) choose();
|
||||
});
|
||||
host.addEventListener("keydown", (e) => e.key === "Enter" && choose());
|
||||
} catch (error) {
|
||||
if (done) return;
|
||||
head.textContent = fill(TEXT.failed, error instanceof Error ? error.message : "");
|
||||
}
|
||||
})();
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -62,3 +62,26 @@
|
||||
flex: 1 1 auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 엑셀 칸 고르기 모달 — 머리(지금 연결 · [연결 풀기]) · 알림 · 읽기만 시트(높이를 정해야 시트가 보임) */
|
||||
.m02-cellpick {
|
||||
width: min(960px, 92vw);
|
||||
}
|
||||
|
||||
.m02-cellpick__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
.m02-cellpick__now,
|
||||
.m02-cellpick__hint {
|
||||
margin: 0 0 var(--spacing-8);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.m02-cellpick__sheet {
|
||||
height: min(60vh, 520px);
|
||||
min-height: 240px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user