Merge remote-tracking branch 'origin/sub_laptop_1' into sub_laptop_7

This commit is contained in:
2026-10-04 22:38:44 +09:00
4 changed files with 47 additions and 5 deletions
@@ -69,6 +69,10 @@ const TEXT = {
pills: "알약",
pillsTip: "아래 알약 줄 보임 · 숨김",
take: "가져오기",
clear: "삭제",
clearTip: "이 항목의 {value} 을(를) 비움 — 번호 · 열은 그대로",
clearAsk: "「{name}」 의 {value} 을(를) 비울까? — 번호 · 열은 그대로 · 되돌릴 수 없음",
clearFailed: "비우지 못함 — {value}",
takeTip: "다른 항목의 {value} 을(를) 골라 이 칸에 덮어씀 — 번호 · 열은 그대로",
blank: "비어 있음 — 새로 만들거나 다른 항목에서 복사해 옴",
makeNew: "새로 만들기",
@@ -117,6 +121,9 @@ export interface ItemCardsOptions {
sheetEdit?: SheetEditOptions;
/** 「복사해 가져오기」 · 빈 카드 [복사 생성](39-5) — 다른 항목의 이 칸을 골라 덮어씀 · 없으면 단추 없음 */
onImport?: (kind: CardKind) => void;
/** 카드 [삭제](40-2) — 그 항목 비우기(번호 · 열 그대로 · 물음은 카드가 한 번) · 없으면 단추 없음 ·
* 끝나면 카드를 다시 읽음(빈 카드 = [새로 만들기] [복사 생성]) */
onClear?: (kind: CardKind) => Promise<void>;
/** 일위대가 카드(39-5 · 몸 = 일위대가 보기 갈래 `Labor_View`) — 도면 다음 · 지금 층에서 읽음 ·
* [일위대가 수정] · 빈 카드 [새로 만들기] = 이것(일위대가 상세) */
labor?: () => void;
@@ -174,6 +181,8 @@ interface Card {
edit: HTMLButtonElement;
/** 「복사해 가져오기」 — 찬 카드만 */
take: HTMLButtonElement;
/** [삭제] — 찬 카드만 · 쓸 수 있을 때만 */
del: HTMLButtonElement;
no: HTMLElement;
name: HTMLElement;
}
@@ -218,14 +227,35 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle {
const take = createButton({ label: TEXT.take, variant: "ghost", size: "sm" });
take.title = fill(TEXT.takeTip, PART_WORD[kind]);
take.addEventListener("click", () => opts.onImport?.(kind));
shell.tools.append(help, saver.button, take, edit);
return { kind, shell, saver, help, edit, take, no, name };
const del = createButton({ label: TEXT.clear, variant: "ghost", size: "sm" });
del.title = fill(TEXT.clearTip, PART_WORD[kind]);
del.addEventListener("click", () => void clearCard(kind));
shell.tools.append(help, saver.button, take, edit, del);
return { kind, shell, saver, help, edit, take, del, no, name };
};
/** 카드 [삭제](40-2) — 물음 한 번(저장 안 한 카드 고침도 함께 버려짐) → 비움 → 카드 다시 읽음(빈 카드가 결과) */
async function clearCard(kind: CardKind): Promise<void> {
if (!opts.onClear || !column) return;
const ask = fill(TEXT.clearAsk, PART_WORD[kind]).replace("{name}", label);
if (!(await showConfirmDialog(ask))) return;
try {
await opts.onClear(kind);
} catch (error) {
// 알림은 카드 틀 안(토스트 안 씀) — 까닭 + [다시 불러오기]
const card = cards.find((c) => c.kind === kind);
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;
};
/** 빈 카드 몸 — [새로 만들기] = 그 자리에서 빈 미리보기 편집 · [복사 생성] = 가져오기 창 */
@@ -307,7 +337,10 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle {
const paintLabor = async (at: string, mine: number): Promise<void> => {
if (!laborCard) return;
try {
laborView ??= (await import("./M02_MasterTemplete_Labor_View")).createLaborView();
// 카드 = 한 줄 표(항목 · 수량산출 · 단위 · 금액 넷 · 40-2) — 자세한 것은 [수정] 상세
laborView ??= (await import("./M02_MasterTemplete_Labor_View")).createLaborView(undefined, {
compact: true,
});
} catch (error) {
if (mine === seq) laborCard.shell.fail(fill(TEXT.noSheet, why(error)), reload);
return;
@@ -345,6 +378,7 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle {
const can = c.kind === "structure" ? drawEdit : c.kind !== "labor" && sheetEdit;
c.saver.button.hidden = c.help.hidden = !can;
c.edit.hidden = !opts.editable();
c.del.hidden = !opts.editable() || !opts.onClear; // 빈 카드는 그린 뒤 `blank` 가 다시 숨김
}
pills.hidden = !sheetEdit;
};
@@ -90,7 +90,10 @@
.m02-struct__card-tools {
display: inline-flex;
flex: none;
flex: 0 1 auto; /* 좁은 카드(1열 · 400px)에서 단추가 많으면 줄 바꿈 — 카드 밖으로 안 넘침(40-2) */
flex-wrap: wrap;
justify-content: flex-end;
min-width: 0;
align-items: center;
gap: var(--spacing-8);
margin-left: auto;
@@ -17,6 +17,7 @@
import { showConfirmDialog, showToast } from "@ui/ui_template_elements";
import type { LinkOptions } from "../A00_Common/spreadsheet/spreadsheet_links";
import {
clearPart,
copyPartFrom,
fetchLaborPills,
fetchStructureNumbers,
@@ -222,6 +223,10 @@ export function createStructurePanel(opts: StructurePanelOptions): StructurePane
warnOnFail: (id) => !!numbers[id],
// 「복사해 가져오기」 · 빈 카드 [복사 생성](39-5) — 집계표 줄 「조회 후 선택」 과 같은 길
onImport: (kind) => void pickCopy(kind, shown ?? undefined),
// 카드 [삭제](40-2) — 그 항목 비우기(서버 clear 길 · 번호 · 열 그대로 · 쓰기 권한 없으면 403)
onClear: async (kind) => {
if (column) await clearPart(kind, column);
},
labor: () => void openEdit("labor"), // 일위대가 카드(보기 갈래 · 39-3) — 도면 다음
// 면적산출 카드 미리보기 수정(31-3) — 전체 편집기와 같은 알약 · 저장 길
sheetEdit: {
+1 -1
View File
@@ -40,7 +40,7 @@ def test_저장_단추_알림_한_벌():
def test_네_카드가_틀을_씀_수정_단추_한_모양():
cards = _text("M02_MasterTemplete_ItemCards.ts")
assert "const shell = cardShell({ title, body, sub: [no, name] });" in cards
assert "shell.tools.append(help, saver.button, take, edit);" in cards # 가져오기(39-5 · 40-2)
assert "shell.tools.append(help, saver.button, take, edit, del);" in cards # 40-2
assert 'variant: "filled"' not in cards # [수정] 은 모두 빈 단추(ghost)
assert "c.saver.button.hidden = c.help.hidden = !can;" in cards # [저장] · ? 늘 보임
assert "DRAWING_KEYS" in cards and "SHEET_KEYS" in cards