refactor(M02): 항목 카드 넷 공용 틀 CardShell (PLAN 31-7)
- 공용 틀 — 제목 줄(제목 · 저장 안 한 점 ● · 오른쪽 단추 자리) + 한 줄 부제(번호 · 이름 · 말줄임) · 상태(불러오는 중 · 실패 + 다시 불러오기 · 비었음) · [저장](늘 보임 · 고쳐야 켜짐 · 카드 안 Ctrl+S · 알림 글 한 벌) · ?(단축키 상자) - 도면 정보 · 면적산출 · 재료산출(ItemCards) · 선택 입력(관리 화면 · 고르기 모달 값 카드)이 틀에 내용만 넣음 - 「〇〇 수정」 빈 단추 한 모양 · 엑셀 카드에도 ? · 불러오는 중 네 카드 · 실패는 카드 안 글(토스트 안 씀) · 물음 한 번(어느 카드든) - 저장 길(saveTemplate · 도면 write · sheetEdit.save · 위 [저장] saveInputs) · 단축키는 그대로 · 고르기 모달은 보기만 - 시험 test_m02_card_shell 새로 · 옛 모양 시험 맞춤 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,7 @@ import {
|
||||
type SeatMap,
|
||||
type ValueDoc,
|
||||
} from "./M02_MasterTemplete_Inputs_Model";
|
||||
import { cardNote, cardShell } from "./M02_MasterTemplete_CardShell";
|
||||
import "./M02_MasterTemplete_Inputs.css";
|
||||
|
||||
const TEXT = {
|
||||
@@ -54,7 +55,6 @@ const TEXT = {
|
||||
noLogic: "이 일위대가에 담긴 로직이 없음",
|
||||
missing: "빠진 로직 {value}",
|
||||
failed: "입력 목록을 못 받음 — {value}",
|
||||
loading: "입력 목록을 받는 중…",
|
||||
seat: "자리",
|
||||
};
|
||||
|
||||
@@ -98,12 +98,9 @@ export function createInputsCard(opts: InputsCardOptions): InputsCardHandle {
|
||||
|
||||
const summary = el("div", { className: "m02-inputs__summary" });
|
||||
const body = el("div", { className: "m02-inputs__body" });
|
||||
const root = el("section", {
|
||||
className: "m02-struct__card m02-inputs ui-collapsible ui-sidebar-section ui-sidebar-card",
|
||||
children: [el("h3", { className: "ui-collapsible__title", text: TEXT.title }), summary, body],
|
||||
});
|
||||
|
||||
const note = (text: string): HTMLElement => el("p", { className: "m02-struct__empty", text });
|
||||
// 공용 카드 틀(31-7) — 부제 = 요약 · [저장]은 없음(집계표 [저장]과 함께) · 점(●) = 고른 것 저장 전
|
||||
const shell = cardShell({ title: TEXT.title, className: "m02-inputs", sub: [summary], body });
|
||||
const root = shell.root;
|
||||
const clear = (): void => {
|
||||
summary.replaceChildren();
|
||||
body.replaceChildren();
|
||||
@@ -163,6 +160,7 @@ export function createInputsCard(opts: InputsCardOptions): InputsCardHandle {
|
||||
else draft.seats.delete(key);
|
||||
draft.dirty = true;
|
||||
paintSummary(draft);
|
||||
shell.setDirty(true);
|
||||
opts.onDirty();
|
||||
});
|
||||
const hint = valueHint(input);
|
||||
@@ -199,31 +197,32 @@ export function createInputsCard(opts: InputsCardOptions): InputsCardHandle {
|
||||
);
|
||||
const missing = draft.list.빠진로직;
|
||||
body.replaceChildren(
|
||||
...(groups.length ? groups : [note(TEXT.noLogic)]),
|
||||
...(missing.length ? [note(fill(TEXT.missing, { value: missing.join(", ") }))] : []),
|
||||
...(groups.length ? groups : [cardNote(TEXT.noLogic)]),
|
||||
...(missing.length ? [cardNote(fill(TEXT.missing, { value: missing.join(", ") }))] : []),
|
||||
);
|
||||
}
|
||||
|
||||
async function show(colId: string | null, combo: string | null): Promise<void> {
|
||||
const mine = ++seq;
|
||||
clear();
|
||||
shell.setDirty(!!colId && !!drafts.get(colId)?.dirty);
|
||||
if (!colId) return;
|
||||
const kept = drafts.get(colId);
|
||||
if (kept) return paint(kept);
|
||||
body.replaceChildren(note(TEXT.loading));
|
||||
const stop = shell.loading(); // 0.3초 넘으면 「불러오는 중」(31-7 · 네 카드 같게)
|
||||
try {
|
||||
const layer: Layer = getActiveLayer();
|
||||
const row = (await itemRows()).find((r) => r.열 === colId);
|
||||
if (mine !== seq) return;
|
||||
if (!row) return void body.replaceChildren(note(combo ? TEXT.unsavedCombo : TEXT.noItem));
|
||||
if (!row.일위대가)
|
||||
return void body.replaceChildren(note(combo ? TEXT.unsavedCombo : TEXT.noCombo));
|
||||
stop();
|
||||
if (!row) return shell.note(combo ? TEXT.unsavedCombo : TEXT.noItem);
|
||||
if (!row.일위대가) return shell.note(combo ? TEXT.unsavedCombo : TEXT.noCombo);
|
||||
const [list, links] = await Promise.all([
|
||||
fetchItemInputs(layer, row.key),
|
||||
readInputLinks(layer, row.key),
|
||||
]);
|
||||
if (mine !== seq) return;
|
||||
if (!list.일위대가) return void body.replaceChildren(note(TEXT.noCombo));
|
||||
if (!list.일위대가) return shell.note(TEXT.noCombo);
|
||||
const draft: Draft = {
|
||||
key: row.key,
|
||||
판: links.판,
|
||||
@@ -234,7 +233,11 @@ export function createInputsCard(opts: InputsCardOptions): InputsCardHandle {
|
||||
drafts.set(colId, draft);
|
||||
paint(draft);
|
||||
} catch (error) {
|
||||
if (mine === seq) body.replaceChildren(note(fill(TEXT.failed, { value: why(error) })));
|
||||
// 실패 = 카드 안 까닭 + [다시 불러오기](31-7)
|
||||
if (mine === seq)
|
||||
shell.fail(fill(TEXT.failed, { value: why(error) }), () => void show(colId, combo));
|
||||
} finally {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,6 +258,7 @@ export function createInputsCard(opts: InputsCardOptions): InputsCardHandle {
|
||||
draft.판 = got.판;
|
||||
draft.dirty = false;
|
||||
}
|
||||
shell.setDirty(false);
|
||||
},
|
||||
invalidate: () => {
|
||||
rows = null;
|
||||
@@ -307,7 +311,9 @@ export interface InputsValueOptions {
|
||||
export interface InputsValueHandle {
|
||||
root: HTMLElement;
|
||||
/** 자료를 바꿔 끼움 — `null` 은 못 읽은 경우(까닭 글 `note`) */
|
||||
set: (data: InputsValueData | null, note?: string) => void;
|
||||
set: (data: InputsValueData | null, note?: string, retry?: () => void) => void;
|
||||
/** 목록을 받는 동안 0.3초 넘으면 「불러오는 중」(31-7) — 돌려받은 함수 = 끝 */
|
||||
loading: () => () => void;
|
||||
/** 지금 값(확인한 것 · 값이 든 것만) */
|
||||
getValues: () => ValueDoc;
|
||||
/** 범위 밖 값이 없나 */
|
||||
@@ -342,14 +348,15 @@ export function createInputsValueCard(opts: InputsValueOptions): InputsValueHand
|
||||
if (opts.onEdit) edit.addEventListener("click", opts.onEdit);
|
||||
const places = el("p", { className: "m02-inputval__places" });
|
||||
const body = el("div", { className: "m02-struct__sheet m02-inputval" });
|
||||
const root = el("section", {
|
||||
className: "m02-struct__card ui-collapsible ui-sidebar-section ui-sidebar-card",
|
||||
children: [
|
||||
el("h3", { className: "ui-collapsible__title", text: TEXT.title }),
|
||||
el("div", { className: "m02-struct__subhead", children: [summary, edit] }),
|
||||
el("div", { className: "m02-inputval__wrap", children: [places, body] }),
|
||||
],
|
||||
// 공용 카드 틀(31-7) — 부제 = 확인 n/m · 제목 줄 오른쪽 = [선택 입력 수정] · 상태 글은 목록 칸에
|
||||
const shell = cardShell({
|
||||
title: TEXT.title,
|
||||
sub: [summary],
|
||||
body: el("div", { className: "m02-inputval__wrap", children: [places, body] }),
|
||||
stateHost: body,
|
||||
});
|
||||
shell.tools.append(edit);
|
||||
const root = shell.root;
|
||||
|
||||
const groups = (): ReturnType<typeof pickedGroups> =>
|
||||
data ? pickedGroups(data.list, data.link) : [];
|
||||
@@ -485,18 +492,13 @@ export function createInputsValueCard(opts: InputsValueOptions): InputsValueHand
|
||||
return el("div", { className: "m02-inputval__cell", children: [control, box, state, error] });
|
||||
};
|
||||
|
||||
const paint = (note?: string): void => {
|
||||
const paint = (note?: string, retry?: () => void): void => {
|
||||
invalid = 0;
|
||||
paintSummary();
|
||||
if (!data) {
|
||||
places.textContent = "";
|
||||
body.replaceChildren(
|
||||
el("p", {
|
||||
className: "m02-struct__empty",
|
||||
text: note ? fill(VALUE_TEXT.noList, { value: note }) : VALUE_TEXT.none,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
if (note && retry) return shell.fail(fill(VALUE_TEXT.noList, { value: note }), retry);
|
||||
return shell.note(note ? fill(VALUE_TEXT.noList, { value: note }) : VALUE_TEXT.none);
|
||||
}
|
||||
const seats = [...seatsOf(data.link).values()];
|
||||
const area = seats.filter((seat) => seat === "면적").length;
|
||||
@@ -512,10 +514,7 @@ export function createInputsValueCard(opts: InputsValueOptions): InputsValueHand
|
||||
]
|
||||
: [];
|
||||
if (!shown.length) {
|
||||
body.replaceChildren(
|
||||
el("p", { className: "m02-struct__empty", text: VALUE_TEXT.none }),
|
||||
...missing,
|
||||
);
|
||||
body.replaceChildren(cardNote(VALUE_TEXT.none), ...missing);
|
||||
return;
|
||||
}
|
||||
body.replaceChildren(
|
||||
@@ -552,11 +551,12 @@ export function createInputsValueCard(opts: InputsValueOptions): InputsValueHand
|
||||
|
||||
return {
|
||||
root,
|
||||
set: (next, note) => {
|
||||
set: (next, note, retry) => {
|
||||
data = next;
|
||||
values = next ? structuredClone(next.values) : {};
|
||||
paint(note);
|
||||
paint(note, retry);
|
||||
},
|
||||
loading: () => shell.loading(),
|
||||
getValues: () => {
|
||||
// 확인한 것 · 값이 든 것만 — 입력 목록에서 없어진 입력은 그대로 둠(다음 가져오기가 정함)
|
||||
const out: ValueDoc = {};
|
||||
|
||||
Reference in New Issue
Block a user