feat(M02): 구조물 조합 카드 빈 상태 [새로 만들기] · [복사 생성] 없앰 · 일위대가 · 면적산출 가져오기 = 함께 덮어씀(43-9b)
- 빈 카드도 보통 카드 — 빈 몸 + 가져오기 · 수정 · 삭제(새 항목 칸 셋은 서버가 자동 생성) - 일위대가 · 면적산출 카드 가져오기 · 집계표 줄 조회 후 선택 = copyLaborAreaFrom(알약 연결 포함) · 도면은 그 칸만 - 가져오기 창 안내 한 줄 「일위대가 · 면적산출 함께 가져옴」(pickFrom note) - 화면 검증 시나리오 ui_checks/43-9b_빈카드_함께가져오기.js Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
This commit is contained in:
@@ -79,9 +79,6 @@ const TEXT = {
|
||||
clearAsk: "「{name}」 의 {value} 을(를) 비울까? — 번호 · 열은 그대로 · 되돌릴 수 없음",
|
||||
clearFailed: "비우지 못함 — {value}",
|
||||
takeTip: "다른 항목의 {value} 을(를) 골라 이 칸에 덮어씀 — 번호 · 열은 그대로",
|
||||
blank: "비어 있음 — 새로 만들거나 다른 항목에서 복사해 옴",
|
||||
makeNew: "새로 만들기",
|
||||
makeCopy: "복사 생성",
|
||||
};
|
||||
|
||||
/** 엑셀 카드 단축키 — 스프레드시트 미리보기(`spreadsheet_keys.ts`)가 받는 것만 */
|
||||
@@ -98,11 +95,6 @@ const SHEET_KEYS: [string, string][] = [
|
||||
];
|
||||
|
||||
const why = (error: unknown): string => (error instanceof Error ? error.message : "");
|
||||
/** 빈 통합문서 — 값 · 식이 든 칸도 알약 연결도 하나도 없음 */
|
||||
const emptyBook = (book: Workbook): boolean =>
|
||||
(book.시트 ?? []).every(
|
||||
(s) => !s.연결?.length && Object.values(s.칸 ?? {}).every((c) => (c.값 ?? "") === "" && !c.식),
|
||||
);
|
||||
const fill = (text: string, value: string): string => text.replace("{value}", value);
|
||||
/** 스프레드시트는 면적산출 카드를 열 때만 받음 */
|
||||
const loadSpreadsheet = () => import("../A00_Common/spreadsheet/spreadsheet");
|
||||
@@ -124,13 +116,13 @@ export interface ItemCardsOptions {
|
||||
onDirty?: (dirty: boolean) => void;
|
||||
/** 면적산출 카드를 미리보기에서 고침(31-3) — 없으면 보기만 */
|
||||
sheetEdit?: SheetEditOptions;
|
||||
/** 「복사해 가져오기」 · 빈 카드 [복사 생성](39-5) — 다른 항목의 이 칸을 골라 덮어씀 · 없으면 단추 없음 */
|
||||
/** 「가져오기」(39-5 · 43-9b) — 다른 항목의 이 칸을 골라 덮어씀 · 빈 카드도 같음 · 없으면 단추 없음 */
|
||||
onImport?: (kind: CardKind) => void;
|
||||
/** 카드 [삭제](40-2) — 그 항목 비우기(번호 · 열 그대로 · 물음은 카드가 한 번) · 없으면 단추 없음 ·
|
||||
* 끝나면 카드를 다시 읽음(빈 카드 = [새로 만들기] [복사 생성]) */
|
||||
* 끝나면 카드를 다시 읽음(빈 카드 = 빈 몸 + 같은 단추) */
|
||||
onClear?: (kind: CardKind) => Promise<void>;
|
||||
/** 일위대가 카드(39-5 · 몸 = 일위대가 상세 카드 갈래 `Labor_Detail` · 43-4) — 도면 다음 · 지금 층에서 읽음 ·
|
||||
* [일위대가 수정] · 빈 카드 [새로 만들기] = 이것(일위대가 상세) */
|
||||
* [일위대가 수정] = 이것(일위대가 상세) */
|
||||
labor?: () => void;
|
||||
}
|
||||
|
||||
@@ -184,9 +176,9 @@ interface Card {
|
||||
saver: SaveControl;
|
||||
help: HTMLElement;
|
||||
edit: HTMLButtonElement;
|
||||
/** 「복사해 가져오기」 — 찬 카드만 */
|
||||
/** 「가져오기」 — 빈 카드도 */
|
||||
take: HTMLButtonElement;
|
||||
/** [삭제] — 찬 카드만 · 쓸 수 있을 때만 */
|
||||
/** [삭제] — 쓸 수 있을 때만 */
|
||||
del: HTMLButtonElement;
|
||||
no: HTMLElement;
|
||||
name: HTMLElement;
|
||||
@@ -199,8 +191,6 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle {
|
||||
let docVersion: string | null = null;
|
||||
let preview: DrawingCardHandle | null = null;
|
||||
let seq = 0;
|
||||
/** 빈 카드에서 [새로 만들기] 를 누른 칸 — 빈 문서라도 미리보기 편집을 띄움(열이 바뀌면 비움) */
|
||||
const started = new Set<CardKind>();
|
||||
|
||||
const numberOf = (kind: CardKind, id: string): string => opts.numberOf?.(kind, id) ?? "";
|
||||
const anyDirty = (): boolean => cards.some((c) => c.saver.dirty());
|
||||
@@ -252,44 +242,9 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle {
|
||||
return void card?.shell.fail(fill(TEXT.clearFailed, why(error)), reload);
|
||||
}
|
||||
for (const c of cards) c.saver.mark(false);
|
||||
started.delete(kind);
|
||||
reload();
|
||||
}
|
||||
|
||||
/** 빈 카드(39-5) — 고칠 수 있고(가져오기 길이 있는 M02 화면) [새로 만들기] 를 안 눌렀으면 두 단추 · 아니면 미리보기 */
|
||||
const blank = (c: Card, empty: boolean): boolean => {
|
||||
const show = empty && opts.editable() && !!opts.onImport && !started.has(c.kind);
|
||||
c.take.hidden = show || !opts.editable() || !opts.onImport;
|
||||
c.del.hidden = show || !opts.editable() || !opts.onClear; // 빈 카드엔 비울 것이 없음
|
||||
return show;
|
||||
};
|
||||
/** 빈 카드 몸 — [새로 만들기] = 그 자리에서 빈 미리보기 편집 · [복사 생성] = 가져오기 창 */
|
||||
const startBox = (c: Card, paint: () => void): HTMLElement => {
|
||||
const fresh = createButton({
|
||||
label: TEXT.makeNew,
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
onClick: () => {
|
||||
started.add(c.kind);
|
||||
paint();
|
||||
},
|
||||
});
|
||||
const copy = createButton({
|
||||
label: TEXT.makeCopy,
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
onClick: () => opts.onImport?.(c.kind),
|
||||
});
|
||||
copy.hidden = !opts.onImport;
|
||||
return el("div", {
|
||||
className: "m02-struct__start",
|
||||
children: [
|
||||
el("p", { className: "m02-struct__empty", text: TEXT.blank }),
|
||||
el("div", { className: "m02-struct__start-row", children: [fresh, copy] }),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
const previewHost = el("div", { className: "m02-struct__preview" });
|
||||
const info = makeCard("structure", TEXT.drawingInfo, previewHost, () =>
|
||||
preview ? preview.write() : Promise.resolve(),
|
||||
@@ -340,7 +295,7 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle {
|
||||
: null;
|
||||
let laborEd: LaborDetailHandle | null = null;
|
||||
let laborVer = "";
|
||||
/** 일위대가 카드 — 그 열 문서를 읽어 카드 갈래로 붙임 · 빈 일위대가(로직 · 자재 없음)면 두 단추 */
|
||||
/** 일위대가 카드 — 그 열 문서를 읽어 카드 갈래로 붙임(빈 일위대가도 빈 몸) */
|
||||
const paintLabor = async (at: string, mine: number): Promise<void> => {
|
||||
if (!laborCard) return;
|
||||
laborEd?.destroy();
|
||||
@@ -359,11 +314,9 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle {
|
||||
}
|
||||
if (mine !== seq) return;
|
||||
laborVer = got.판 ?? "";
|
||||
const d = (got.문서 ?? {}) as { 일위대가번?: string; 담은로직?: unknown[]; 자재?: unknown[] };
|
||||
const d = (got.문서 ?? {}) as { 일위대가번?: string };
|
||||
laborCard.no.textContent =
|
||||
d.일위대가번 || numberOf("labor", at) || fill(TEXT.noFieldNumber, PART_FIELD.labor);
|
||||
if (blank(laborCard, !d.담은로직?.length && !d.자재?.length))
|
||||
return void laborHost.replaceChildren(startBox(laborCard, () => opts.labor?.()));
|
||||
laborEd = mod.mountLaborDetail(laborHost, got.문서, {
|
||||
layer,
|
||||
column: at,
|
||||
@@ -407,7 +360,8 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle {
|
||||
c.saver.button.hidden = c.help.hidden = !can;
|
||||
if (c.kind === "labor") c.help.hidden = true; // 일위대가 카드엔 단축키 상자 없음
|
||||
c.edit.hidden = !opts.editable();
|
||||
c.del.hidden = !opts.editable() || !opts.onClear; // 빈 카드는 그린 뒤 `blank` 가 다시 숨김
|
||||
c.take.hidden = !opts.editable() || !opts.onImport;
|
||||
c.del.hidden = !opts.editable() || !opts.onClear;
|
||||
}
|
||||
pills.hidden = !sheetEdit;
|
||||
};
|
||||
@@ -476,8 +430,6 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle {
|
||||
* 그 사이 열이 바뀌면 버림 */
|
||||
const paintSheet = async (c: SheetCard, mine: number): Promise<void> => {
|
||||
if (!c.doc) return;
|
||||
if (blank(c, emptyBook(c.doc)))
|
||||
return void c.host.replaceChildren(startBox(c, () => void paintSheet(c, seq)));
|
||||
c.host.replaceChildren();
|
||||
try {
|
||||
const { createSpreadsheet } = await loadSpreadsheet();
|
||||
@@ -502,12 +454,10 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle {
|
||||
paintDrawing();
|
||||
};
|
||||
|
||||
/** 도면 미리보기 — 빈 도면이면 두 단추(39-5) */
|
||||
/** 도면 미리보기(빈 도면도 빈 몸) */
|
||||
const paintDrawing = (): void => {
|
||||
if (!doc) return;
|
||||
previewHost.replaceChildren();
|
||||
if (blank(info, !doc.도면?.entities?.length))
|
||||
return void previewHost.append(startBox(info, paintDrawing));
|
||||
// 미리보기 — 고칠 수 있으면 메뉴 없이 단축키로 고치고 카드 [저장](31-4 · `DrawingCard`)
|
||||
preview = mountDrawingCard(previewHost, {
|
||||
doc,
|
||||
@@ -543,7 +493,6 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle {
|
||||
}
|
||||
root.hidden = false;
|
||||
paintPills(readStateRaw("m02-card-pills") === "true"); // 계정 취향을 늦게 받았어도 열 때마다 따름
|
||||
if (col.id !== column) started.clear();
|
||||
column = col.id;
|
||||
label = col.label;
|
||||
for (const c of cards) c.name.textContent = label;
|
||||
|
||||
@@ -225,9 +225,13 @@ export function buildTwoWay(opt: TwoWayOptions): HTMLElement {
|
||||
});
|
||||
}
|
||||
|
||||
/** 창으로 고름 — 줄 누름 → `ask` 확인(있으면) → 고른 것 · 닫으면 null */
|
||||
/** 창으로 고름 — 위 안내 한 줄(`note` · 있으면) · 줄 누름 → `ask` 확인(있으면) → 고른 것 · 닫으면 null */
|
||||
export function pickFrom(
|
||||
opt: Omit<PickerOptions, "onPick"> & { title: string; ask?: (picked: Picked) => string },
|
||||
opt: Omit<PickerOptions, "onPick"> & {
|
||||
title: string;
|
||||
note?: string;
|
||||
ask?: (picked: Picked) => string;
|
||||
},
|
||||
): Promise<Picked | null> {
|
||||
return new Promise((resolve) => {
|
||||
let done = false;
|
||||
@@ -250,6 +254,7 @@ export function pickFrom(
|
||||
close();
|
||||
})(),
|
||||
});
|
||||
if (opt.note) body.append(el("p", { className: "m02-itempick__note", text: opt.note }));
|
||||
body.append(picker.root);
|
||||
},
|
||||
});
|
||||
@@ -260,6 +265,7 @@ export function pickFrom(
|
||||
export async function pickHere(
|
||||
opt: Omit<PickerOptions, "onPick" | "user" | "company"> & {
|
||||
title: string;
|
||||
note?: string;
|
||||
ask?: (picked: Picked) => string;
|
||||
},
|
||||
): Promise<Picked | null> {
|
||||
|
||||
@@ -205,22 +205,6 @@
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* 빈 카드(39-5) — 안내 한 줄 + [새로 만들기] [복사 생성] */
|
||||
.m02-struct__start {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--spacing-8);
|
||||
padding-bottom: var(--spacing-16);
|
||||
}
|
||||
|
||||
.m02-struct__start-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-8);
|
||||
padding: 0 var(--spacing-16);
|
||||
}
|
||||
|
||||
/* 조회 후 선택 창 — 찾기 칸 + 목록(번호 · 이름) */
|
||||
.m02-pick__list {
|
||||
display: flex;
|
||||
|
||||
@@ -18,6 +18,7 @@ import { showConfirmDialog, showToast } from "@ui/ui_template_elements";
|
||||
import type { LinkOptions } from "../A00_Common/spreadsheet/spreadsheet_links";
|
||||
import {
|
||||
clearPart,
|
||||
copyLaborAreaFrom,
|
||||
copyPartFrom,
|
||||
fetchLaborPills,
|
||||
fetchStructureNumbers,
|
||||
@@ -50,6 +51,8 @@ const TEXT = {
|
||||
overwrite: "{from} 의 {what} 내용을 {to} 에 덮어씀 — 번호 · 열은 그대로",
|
||||
pickCopy: "{value} 복사해 가져오기",
|
||||
copied: "복사해 가져옴",
|
||||
pair: "일위대가 · 면적산출",
|
||||
pairNote: "일위대가 · 면적산출 함께 가져옴 — 알약 연결 포함 · 도면은 그대로",
|
||||
copyFailed: "복사 실패 — {value}",
|
||||
noPills: "이 열 일위대가에 단가산출 로직 · 자재가 없어 알약 없음 — 일위대가 상세에서 담음",
|
||||
pillsFailed: "일위대가 알약을 못 받음 — {value}",
|
||||
@@ -263,8 +266,10 @@ export function createStructurePanel(opts: StructurePanelOptions): StructurePane
|
||||
};
|
||||
|
||||
/** 다른 항목의 도면 · 면적산출(…)을 골라 내용만 복사(39-5) — 탭 = 읽을 수 있는 곳 · 덮어쓰기 확인 ·
|
||||
* 번호 · 열은 그대로 · 보고 있던 열이면 다시 읽음 · 집계표 줄 「조회 후 선택」 · 카드 「복사해 가져오기」가 씀 */
|
||||
* 번호 · 열은 그대로 · 보고 있던 열이면 다시 읽음 · 집계표 줄 「조회 후 선택」 · 카드 「가져오기」가 씀 ·
|
||||
* 일위대가 · 면적산출은 함께(43-9b · 알약 연결이 이어져 있음 · 실무문서면 서버가 일위대가만) · 도면은 그 칸만 */
|
||||
const pickCopy = async (kind: CardKind, to: StructureColumn | undefined): Promise<void> => {
|
||||
const pair = kind !== "structure";
|
||||
const word = PART_WORD[kind];
|
||||
const toNo = to && numbers[to.id]?.[PART_FIELD[kind]];
|
||||
if (!to || !toNo) return void showToast(TEXT.saveFirst, "error");
|
||||
@@ -276,22 +281,22 @@ export function createStructurePanel(opts: StructurePanelOptions): StructurePane
|
||||
const picked = await pickHere({
|
||||
title: `${fill(TEXT.pickCopy, word)} — ${target}`,
|
||||
kind,
|
||||
note: pair ? TEXT.pairNote : undefined,
|
||||
skip: (from, row) => same(from) && row.열 === to.id,
|
||||
ask: (p) =>
|
||||
TEXT.overwrite
|
||||
.replace("{from}", `${p.from.label} ${pickedName(p)}`)
|
||||
.replace("{what}", word)
|
||||
.replace("{what}", pair ? TEXT.pair : word)
|
||||
.replace("{to}", target),
|
||||
});
|
||||
if (!picked) return;
|
||||
const copy = (source: string, from: Parameters<typeof copyPartFrom>[3]) =>
|
||||
pair ? copyLaborAreaFrom(to.id, source, from) : copyPartFrom(kind, to.id, source, from);
|
||||
try {
|
||||
// 같은 곳 = 다른 열 · 다른 층 · 사람 = 원본 층 · 실무문서 = 조합 키 → 일위대가(39-1b)
|
||||
if ("combo" in picked)
|
||||
await copyPartFrom(kind, to.id, picked.combo.키, { layer: "practice", owner: null });
|
||||
if ("combo" in picked) await copy(picked.combo.키, { layer: "practice", owner: null });
|
||||
else
|
||||
await copyPartFrom(
|
||||
kind,
|
||||
to.id,
|
||||
await copy(
|
||||
picked.row.열,
|
||||
same(picked.from) ? null : { layer: picked.from.layer, owner: picked.from.owner },
|
||||
);
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/* 43-9b — 구조물 조합 카드 셋: 빈 카드 [새로 만들기] · [복사 생성] 없음 · 일위대가 [가져오기] = 일위대가 · 면적산출 함께
|
||||
* 시작 화면: M02 마스터 템플릿 · 마스터 층(#/m02-master-template?open=구조물조합&root=master) · 시스템 관리자 ·
|
||||
* 나무에 item1(SU-000001 · 담은 로직 있음 · 면적산출 있음)
|
||||
* 부르는 법: orca eval --expression "$(cat 이 파일)" (async 식 · JSON 글 · 20초 안)
|
||||
* 쓰는 것: 마스터 층에 새 항목 하나(「검증43 9b」) + 그 일위대가 · 면적산출 — 끝나면 resources/master_template 사본으로 되씀
|
||||
* 기대 수치:
|
||||
* - made = 새 열 id · twoButtons = 0(카드 안 「새로 만들기」 · 「복사 생성」) · tools = 카드 셋 모두 가져오기 · 수정 · 삭제
|
||||
* - note.labor = 「일위대가 · 면적산출 함께 가져옴 …」 · note.area = 같은 글 · note.drawing = null
|
||||
* - after.logic = src.logic(14) · after.links = src.links(item1 알약 연결 3) · after.areaSame = true(시트 통째 = 원본)
|
||||
* - drawing.before = drawing.after = 0(도면 그대로) · card.rows = src.logic(카드 다시 읽음)
|
||||
*/
|
||||
(async () => {
|
||||
const w = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
const until = async (fn, ms = 6000) => {
|
||||
for (let t = 0; t < ms; t += 150) {
|
||||
const v = fn();
|
||||
if (v) return v;
|
||||
await w(150);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const vis = (b) => b && !b.hidden && b.offsetParent !== null;
|
||||
const btns = (root, t) =>
|
||||
[...root.querySelectorAll("button")].filter((b) => vis(b) && b.innerText.trim() === t);
|
||||
const api = (kind, col) =>
|
||||
fetch(`/api/m02/layers/system/templates/${kind}/${col}`, { credentials: "include" }).then((r) =>
|
||||
r.json(),
|
||||
);
|
||||
const cells = (book) =>
|
||||
(book.시트 ?? []).flatMap((s) =>
|
||||
Object.entries(s.칸 ?? {}).filter(([, c]) => (c.값 ?? "") !== "" || c.식),
|
||||
);
|
||||
const links = (book) => (book.시트 ?? []).reduce((n, s) => n + (s.연결?.length ?? 0), 0);
|
||||
const sheets = (book) => JSON.stringify(book.시트 ?? []);
|
||||
const items = () =>
|
||||
[...document.querySelectorAll('[data-node^="i|"]')].map((n) => n.dataset.node);
|
||||
const cards = () => [...document.querySelectorAll(".m02-struct > *")];
|
||||
const out = {};
|
||||
|
||||
// ① 새 항목 — 제목 줄 「새 항목」 → [새로 만들기] → 종류 · 이름 → 확인
|
||||
const before = items();
|
||||
btns(document, "새 항목")[0].click();
|
||||
const two = await until(() => [...document.querySelectorAll(".m02-itempick__two")].find(vis));
|
||||
two.querySelector(".m02-itempick__buttons button").click();
|
||||
await w(500);
|
||||
const ins = [...document.querySelectorAll("input")].filter(vis);
|
||||
const set = (i, v) => {
|
||||
i.value = v;
|
||||
i.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
i.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
};
|
||||
set(ins[0], "검증43");
|
||||
set(ins[1], `9b-${Date.now() % 100000}`); // 같은 이름이면 안 만들어짐
|
||||
btns(document, "확인").pop().click();
|
||||
const node = await until(() => items().find((n) => !before.includes(n)));
|
||||
if (!node) return JSON.stringify({ error: "새 항목 안 생김" });
|
||||
const col = node.slice(2);
|
||||
out.made = col;
|
||||
await until(() => cards()[1]?.querySelector(".m02-labor__edit"), 8000);
|
||||
out.twoButtons = cards()
|
||||
.flatMap((c) => [...c.querySelectorAll("button")])
|
||||
.filter((b) => ["새로 만들기", "복사 생성"].includes(b.innerText.trim())).length;
|
||||
out.tools = cards().map((c) =>
|
||||
["가져오기", "수정", "삭제"].every((t) => btns(c, t).length === 1),
|
||||
);
|
||||
|
||||
// ② 가져오기 창 안내 — 도면(없음) · 면적산출(있음) 은 열어 보고 닫음
|
||||
const note = async (i) => {
|
||||
btns(cards()[i], "가져오기")[0].click();
|
||||
const d = await until(() => document.querySelector(".m02-itempick__dialog"));
|
||||
await w(300);
|
||||
const text = d.querySelector(".m02-itempick__note")?.innerText ?? null;
|
||||
return { d, text };
|
||||
};
|
||||
out.note = {};
|
||||
for (const [i, k] of [
|
||||
[0, "drawing"],
|
||||
[2, "area"],
|
||||
]) {
|
||||
const { d, text } = await note(i);
|
||||
out.note[k] = text;
|
||||
btns(d, "닫기")[0].click();
|
||||
await until(() => !document.querySelector(".m02-itempick__dialog"));
|
||||
}
|
||||
|
||||
// ③ 일위대가 카드 가져오기 ← item1(같은 마스터 층)
|
||||
const [srcLabor, srcArea, dstDrawing] = await Promise.all([
|
||||
api("labor", "item1"),
|
||||
api("area", "item1"),
|
||||
api("structure", col),
|
||||
]);
|
||||
out.src = {
|
||||
logic: srcLabor.문서.담은로직?.length ?? 0,
|
||||
links: links(srcArea.문서),
|
||||
areaCells: cells(srcArea.문서).length,
|
||||
};
|
||||
out.drawing = { before: dstDrawing.문서.도면?.entities?.length ?? 0 };
|
||||
const { d, text } = await note(1);
|
||||
out.note.labor = text;
|
||||
const row = await until(() =>
|
||||
[...d.querySelectorAll("tr")].find((r) => r.innerText.startsWith("SU-000001")),
|
||||
);
|
||||
row.click();
|
||||
await w(400);
|
||||
btns(document, "확인").pop().click();
|
||||
const rows = await until(() => {
|
||||
const ed = cards()[1]?.querySelector(".m02-labor__edit");
|
||||
const n = ed?.querySelectorAll("tbody tr[data-labor-logic]").length ?? 0;
|
||||
return n ? n : null;
|
||||
}, 8000);
|
||||
const [labor, area, drawing] = await Promise.all([
|
||||
api("labor", col),
|
||||
api("area", col),
|
||||
api("structure", col),
|
||||
]);
|
||||
out.after = {
|
||||
logic: labor.문서.담은로직?.length ?? 0,
|
||||
links: links(area.문서),
|
||||
areaCells: cells(area.문서).length,
|
||||
areaSame: sheets(area.문서) === sheets(srcArea.문서),
|
||||
};
|
||||
out.drawing.after = drawing.문서.도면?.entities?.length ?? 0;
|
||||
out.card = { rows };
|
||||
return JSON.stringify(out);
|
||||
})();
|
||||
Reference in New Issue
Block a user