feat(748-1): 단가산출 상세 줄 모달 — 제목 오른쪽 연결 키 · [적용] [삭제] [닫기] 차례 · 요소 단가 정보 칸 걷음
- 제목 오른쪽 (연결 키 · 예 보통인부 (LB000002)) — 요소 · 고른 품목 · 부른 로직 키 - [적용] · [삭제](옛 빼기 이름 바꿈)를 모달 머리 [닫기] 왼쪽으로 · [기초 빼기] · 별도계상은 고침 칸 아래 그대로 - 고침 칸이 있는 모달은 요소 단가 정보 칸(이름 · 키 · 값 · 출처 · 마스터 보기) 걷음 — 자재품목 물가 넷 · 후보 목록 · 한 줄 풀이는 그대로 - M01 단가산출 기초 상세 모달도 같은 꼴([적용] [닫기]) - 낡은 시험 고침: test_722 모달 edit 인자(close, head) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -93,6 +93,8 @@ export interface CoverFormOptions {
|
||||
/** 「m02」 = 덮개 보관 안내 */
|
||||
scope?: "m01" | "m02";
|
||||
onApply: (cover: LineCover | null) => void;
|
||||
/** 모달 머리 단추 자리(닫기 왼쪽 · 748-1) — 주면 [적용] 이 거기로 */
|
||||
head?: HTMLElement;
|
||||
}
|
||||
|
||||
const num = (raw: string): number | null => {
|
||||
@@ -140,10 +142,17 @@ export function coverForm(o: CoverFormOptions): HTMLElement {
|
||||
error.hidden = !bad;
|
||||
if (!bad) o.onApply(Object.keys(next).length ? next : null);
|
||||
};
|
||||
const applyButton = createButton({
|
||||
label: tc("Apply"),
|
||||
variant: "filled",
|
||||
size: 32,
|
||||
onClick: apply,
|
||||
});
|
||||
o.head?.append(applyButton);
|
||||
const actions = el("div", {
|
||||
className: "m01lab-cover__actions",
|
||||
children: [
|
||||
createButton({ label: tc("Apply"), variant: "filled", size: 32, onClick: apply }),
|
||||
...(o.head ? [] : [applyButton]),
|
||||
...(o.cover
|
||||
? [
|
||||
createButton({
|
||||
@@ -156,6 +165,7 @@ export function coverForm(o: CoverFormOptions): HTMLElement {
|
||||
: []),
|
||||
],
|
||||
});
|
||||
actions.hidden = !actions.childElementCount;
|
||||
return el("section", {
|
||||
className: "m01lab-cover",
|
||||
attrs: { "data-cover-form": "" },
|
||||
|
||||
@@ -122,8 +122,8 @@ export interface HoEdit {
|
||||
covers: Record<number, LineCover>;
|
||||
set: (idx: number, cover: LineCover | null) => void;
|
||||
scope?: "m01" | "m02";
|
||||
/** (M02) 모달 고침 칸 아래 덧 동작(줄 빼기 · 별도계상 등) */
|
||||
more?: (idx: number, close: () => void) => HTMLElement[];
|
||||
/** (M02) 모달 덧 동작 — `head` = 닫기 왼쪽 단추(삭제) · `body` = 고침 칸 아래(기초 빼기 · 별도계상 등) */
|
||||
more?: (idx: number, close: () => void) => { head: HTMLElement[]; body: HTMLElement[] };
|
||||
}
|
||||
|
||||
/** 고친 줄 꼬리표 — M01 = 시험 고침 · M02 = 변경 항목 · 고침 없음 = "" */
|
||||
@@ -279,8 +279,9 @@ function modalOf(e: Entry, ctx: HoContext, extra: { cond?: PickCond; item?: HoLi
|
||||
// 덧줄(%) = M02 덮개만 율 고침(M01 시험 값은 호표 줄만)
|
||||
...(edit && (e.extra ? edit.scope === "m02" : item)
|
||||
? {
|
||||
edit: (close: () => void) => {
|
||||
edit: (close: () => void, head: HTMLElement) => {
|
||||
const form = coverForm({
|
||||
head,
|
||||
qty: line ? pctView(line).qty : null,
|
||||
prices: e.extra
|
||||
? {}
|
||||
@@ -295,8 +296,9 @@ function modalOf(e: Entry, ctx: HoContext, extra: { cond?: PickCond; item?: HoLi
|
||||
scope: edit.scope,
|
||||
onApply: (c) => (close(), edit.set(e.idx, c)),
|
||||
});
|
||||
const more = edit.more?.(e.idx, close) ?? [];
|
||||
return more.length ? el("div", { children: [form, ...more] }) : form;
|
||||
const more = edit.more?.(e.idx, close);
|
||||
head.append(...(more?.head ?? []));
|
||||
return more?.body.length ? el("div", { children: [form, ...more.body] }) : form;
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
|
||||
@@ -92,8 +92,8 @@ export interface ModalTarget {
|
||||
usedSlot?: string;
|
||||
/** 있으면 후보 줄을 눌러 고름(시험 입력 · 저장 안 함) — 고르면 모달 닫힘 */
|
||||
onPick?: (item: { ref: string; label: string }) => void;
|
||||
/** 722-2 그 자리 고침 칸(맨 위) — 받는 것 = 모달 닫기 */
|
||||
edit?: (close: () => void) => HTMLElement;
|
||||
/** 722-2 그 자리 고침 칸(맨 위) — 받는 것 = 모달 닫기 · 머리 단추 자리(닫기 왼쪽 · 748-1) */
|
||||
edit?: (close: () => void, head: HTMLElement) => HTMLElement;
|
||||
}
|
||||
|
||||
/** 수량 칸 표 알약 하나가 여는 자료 — 서버 조각 `참조`(키 · 행 · 열)가 곧 찾은 자리 */
|
||||
@@ -287,7 +287,10 @@ function priceView(ref: string, brief: ElementBrief, usedSlot?: string): HTMLEle
|
||||
}
|
||||
|
||||
/** 모달 겉틀(배경·닫기·포커스) — 몸은 `mount` 이 채움(닫기 함수도 받음) */
|
||||
function openDialog(title: string, mount: (body: HTMLElement, close: () => void) => void): void {
|
||||
function openDialog(
|
||||
title: string,
|
||||
mount: (body: HTMLElement, close: () => void, head: HTMLElement) => void,
|
||||
): void {
|
||||
openModal({
|
||||
title,
|
||||
closeLabel: tl("Modal_Close"),
|
||||
@@ -428,7 +431,7 @@ function pickedOf(cond: PickCond, values: Record<string, string>, fallback: stri
|
||||
/** 줄을 눌렀을 때 — 그 줄의 단가(재료·인력·기계 또는 다른 로직)를 바로 보임(알약 없이) ·
|
||||
* 재료 고르기 줄(`cond`)은 잡힌 품목 단가 + 후보 목록 */
|
||||
export function openMaterialModal(target: ModalTarget): void {
|
||||
openDialog(target.title, (body, close) => {
|
||||
openDialog(target.title, (body, close, head) => {
|
||||
const cond = target.cond;
|
||||
const element = target.element ?? "";
|
||||
const base = element.split(".")[0];
|
||||
@@ -445,6 +448,15 @@ export function openMaterialModal(target: ModalTarget): void {
|
||||
? target.unitRef || pickedOf(cond, target.values, known?.ref ?? "")
|
||||
: element;
|
||||
const logicKey = /^로직\((\w+)/.exec(element)?.[1] ?? "";
|
||||
// 748-1 연결 키 = 제목 오른쪽 (괄호) · 고침 칸이 있으면 요소 단가 정보 칸은 없음(키 · 값은 제목 · 단가 칸에)
|
||||
const key = logicKey || (cond ? priceRef : element.includes("{") ? target.unitRef : base);
|
||||
if (key)
|
||||
body
|
||||
.closest(".ui-modal")
|
||||
?.querySelector("h3")
|
||||
?.append(el("span", { className: "m01lab__modalkey", text: ` (${key})` }));
|
||||
const info = (ref: string, brief: ElementBrief, slot?: string): HTMLElement =>
|
||||
target.edit && !ref.startsWith("MT") ? el("span") : priceView(ref, brief, slot);
|
||||
const sum = el("p", { className: "m01lab__sum" });
|
||||
const setSum = (price: ElementBrief | null | undefined): void => {
|
||||
const unit = price ? `${tl("Modal_UnitPrice")} ${formatPrice(price.값)} × ` : "";
|
||||
@@ -457,9 +469,7 @@ export function openMaterialModal(target: ModalTarget): void {
|
||||
/** `prices` 에 있으면 그대로 · 없으면 키로 찾아 옴 — 못 찾으면 「관리자 값 미확정」 */
|
||||
const lookup = (ref: string, label: string): void => {
|
||||
const show = (brief: ElementBrief | null): void => {
|
||||
rate.replaceChildren(
|
||||
brief ? priceView(ref, brief) : muted(`${label} — ${tl("Pill_NoValue")}`),
|
||||
);
|
||||
rate.replaceChildren(brief ? info(ref, brief) : muted(`${label} — ${tl("Pill_NoValue")}`));
|
||||
setSum(brief);
|
||||
};
|
||||
rate.append(muted("…"));
|
||||
@@ -471,7 +481,7 @@ export function openMaterialModal(target: ModalTarget): void {
|
||||
.catch(() => show(null));
|
||||
};
|
||||
if (known && priceRef) {
|
||||
rate.append(priceView(priceRef, known, target.usedSlot));
|
||||
rate.append(info(priceRef, known, target.usedSlot));
|
||||
} else if (logicKey) {
|
||||
rate.append(
|
||||
el("p", {
|
||||
@@ -505,7 +515,7 @@ export function openMaterialModal(target: ModalTarget): void {
|
||||
lookup(element, element); // 더한 줄(M02) 등 `prices` 에 없는 요소 — 키로 찾아 옴(725-4)
|
||||
}
|
||||
body.replaceChildren(
|
||||
...(target.edit ? [target.edit(close)] : []),
|
||||
...(target.edit ? [target.edit(close, head)] : []),
|
||||
rate,
|
||||
...(cond || target.kind === "재료" ? [candidateView(target, priceRef, close)] : []),
|
||||
muted(tl("Modal_Sum")),
|
||||
|
||||
@@ -121,6 +121,13 @@
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 748-1 모달 제목 오른쪽 연결 키 — 제목보다 가볍게 */
|
||||
.m01lab__modalkey {
|
||||
font-size: var(--text-body);
|
||||
font-weight: var(--font-weight-regular, 400);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.m01lab__modal-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -399,11 +399,11 @@ export function mountSangeunDetail(
|
||||
},
|
||||
more: (idx, close) => {
|
||||
const r = at(idx);
|
||||
if (!r) return [];
|
||||
if (!r) return { head: [], body: [] };
|
||||
const drop = createButton({
|
||||
label: L("M02_SangeunDrop"),
|
||||
variant: "danger-ghost",
|
||||
size: 28,
|
||||
size: 32,
|
||||
onClick: () => (close(), dropLine(doc, r), touch()),
|
||||
});
|
||||
drop.dataset.skDrop = "";
|
||||
@@ -432,7 +432,11 @@ export function mountSangeunDetail(
|
||||
}).root,
|
||||
]
|
||||
: [];
|
||||
return [el("div", { className: "m02-sk__tags", children: [...apart, drop, ...whole] })];
|
||||
const rest = [...apart, ...whole];
|
||||
return {
|
||||
head: [drop],
|
||||
body: rest.length ? [el("div", { className: "m02-sk__tags", children: rest })] : [],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ def test_시험_고침은_캐시_밖():
|
||||
cover = _text(COVER)
|
||||
assert "drafts" not in cover and "touch" not in cover and "/save" not in cover
|
||||
modal = _text(M01 / "M01_MasterData_UI_LogicLab_Modal.ts")
|
||||
assert "...(target.edit ? [target.edit(close)] : [])" in modal
|
||||
assert "...(target.edit ? [target.edit(close, head)] : [])" in modal
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
|
||||
|
||||
@@ -180,7 +180,7 @@ export const ui_locales_m2 = {
|
||||
M02_SangeunSwapWeight: ["단위중량", "Unit weight"],
|
||||
M02_SangeunSwapPick: ["고르세요", "Choose"],
|
||||
M02_SangeunAddLine: ["항목 추가", "Add item"],
|
||||
M02_SangeunDrop: ["빼기", "Remove"],
|
||||
M02_SangeunDrop: ["삭제", "Delete"],
|
||||
M02_SangeunDropBase: ["기초 빼기", "Remove base"],
|
||||
M02_SangeunBases: ["더한 기초", "Added bases"],
|
||||
M02_SangeunBaseQty: ["수량(기준단위 몇 개)", "Quantity (base units)"],
|
||||
|
||||
Reference in New Issue
Block a user