feat(M02): 구조물 조합 일위대가 카드 = 상세 그대로 — 단가산출 로직 2행 표 + 자재목록(Labor_Detail 카드 갈래 · 순서 · 로직 추가 · 다이어그램 · 정보 칸 없음 · 빼기 · 자재 추가 그대로) · 카드 [저장] · Ctrl+S
43-4 — 줄 누름 = 넣을 값 모달 · 수량산출 칸 = 수량 모달(상세와 같은 함수) · 저장 차례 = 면적 먼저 → 일위대가 · 표 감싸개 안 가로 스크롤 · 40-2 한 줄 표(compactTable · Labor_View compact · m02-labline css) 지움 · 빼기만 있는 줄도 끝 칸 · 화면 시나리오 ui_checks/43-4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
This commit is contained in:
@@ -61,3 +61,22 @@ def test_실패는_카드_안_글과_다시_불러오기():
|
||||
assert "const retry = (): void => void paintLabor(row, seq);" in picker
|
||||
assert "inputs.set(null, why(error), retry);" in picker
|
||||
assert "const stops = [labor.loading(), inputs.loading()];" in picker
|
||||
|
||||
|
||||
def test_일위대가_카드는_상세_카드_갈래():
|
||||
"""43-4 — 일위대가 카드 몸 = Labor_Detail 카드 갈래(단가산출 로직 2행 표 + 자재목록 · 순서 ·
|
||||
로직 추가 · 다이어그램 없음 · 빼기 그대로) · 카드 [저장] = 면적 먼저 → 일위대가 ·
|
||||
한 줄 표 지움."""
|
||||
cards = _text("M02_MasterTemplete_ItemCards.ts")
|
||||
assert "mod.mountLaborDetail(laborHost, got.문서, {" in cards and "card: true," in cards
|
||||
assert 'makeCard("labor", PART_WORD.labor, laborHost, () => saveLabor())' in cards
|
||||
save = cards[cards.index("async function saveLabor()") :]
|
||||
assert save.index("laborEd.saveArea()") < save.index('saveTemplate(layer, "labor"')
|
||||
assert "onChanged: (dirty) => laborCard.saver.mark(dirty && opts.editable())," in cards
|
||||
assert "Labor_View" not in cards # 한 줄 표(40-2) 안 씀
|
||||
detail = _text("M02_MasterTemplete_Labor_Detail.ts")
|
||||
assert "...(opt.card ? {} : { move })" in detail # 순서 단추 없음 · 빼기는 그대로
|
||||
assert "opt.card ? null : addLogic" in detail and "...(opt.card ? [] : [dgBox])," in detail
|
||||
rows = _text("M02_MasterTemplete_Labor_Rows.ts")
|
||||
assert "const tools = !!(move || remove);" in rows and "compactTable" not in rows
|
||||
assert "compact" not in _text("M02_MasterTemplete_Labor_View.ts").split("*/", 1)[1]
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/* 43-4 — 구조물 조합 일위대가 카드 = 일위대가 상세 카드 갈래(단가산출 로직 2행 표 + 자재목록)
|
||||
* 시작 화면: M02 마스터 템플릿 · 마스터 층(#/m02-master-template?open=구조물조합&root=master) · 시스템 관리자 ·
|
||||
* 나무에 item1(일위대가 담은 로직 있는 열)이 있음
|
||||
* 부르는 법: orca eval --expression "$(cat 이 파일)" (async 식 · JSON 글을 돌려줌 · 20초 안 — 길면 ORCA 가 끊음)
|
||||
* 기대 수치:
|
||||
* - sections = ["단가산출 로직", "자재목록"] · diagram = 0 · info = 0(단위 · 본뜬 칸 없음)
|
||||
* - addLogic = 0 · up = down = 0 · remove = 로직 줄 수 · addMat = 1 · rows = 담은 로직 수(item1 = 14)
|
||||
* - money2 = true(금액 칸이 두 줄 — 위 기준 · 아래 반영)
|
||||
* - scroll.overflowX = "auto" · scroll.wide = 참(item1 표 ≈ 870px > 카드 ≈ 310px · 표 감싸개 안 가로 스크롤)
|
||||
* - rowModal = 「넣을 값 — …」 · qtyModal = 모달 뜸(수량 · 43-1) · 둘 다 닫기 뒤 dirty = false
|
||||
* - 빼기 하나 → dirty = true · saveEnabled = true · 다른 항목(item3) → 버림 물음 「확인」 → item1 다시(되돌림)
|
||||
* → rowsAfter = rows · dirtyAfter = false
|
||||
*/
|
||||
(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 pick = async () => {
|
||||
document.querySelector('[data-node="i|item1"]').click();
|
||||
return until(() => {
|
||||
const ed = document.querySelector(".m02-struct__labor .m02-labor__edit");
|
||||
return ed && ed.querySelector("tbody tr[data-labor-logic]") ? ed : null;
|
||||
});
|
||||
};
|
||||
const ed = await pick();
|
||||
if (!ed) return JSON.stringify({ error: "카드 몸 없음" });
|
||||
const card = ed.closest(".m02-struct > *");
|
||||
const btn = (t) =>
|
||||
[...card.querySelectorAll("button")].filter((b) => vis(b) && b.textContent === t);
|
||||
const save = [...card.querySelectorAll(".ui-collapsible__title button")].find(
|
||||
(b) => b.textContent === "저장",
|
||||
);
|
||||
const rows = () => ed.querySelectorAll("tbody tr[data-labor-logic]").length;
|
||||
const money = ed.querySelector("tbody tr[data-labor-logic] td:nth-child(5)");
|
||||
const out = {
|
||||
sections: [...ed.querySelectorAll(".m01-logic__section-head h3")].map((h) => h.textContent),
|
||||
diagram: ed.querySelectorAll(".m02-labor__dg").length,
|
||||
info: ed.querySelectorAll("[data-labor-field]").length,
|
||||
addLogic: btn("로직 추가").length,
|
||||
up: btn("올리기").length,
|
||||
down: btn("내리기").length,
|
||||
remove: btn("빼기").length,
|
||||
addMat: btn("자재 추가").length,
|
||||
rows: rows(),
|
||||
money2: !!money && money.children.length >= 2,
|
||||
// 표마다 공용 감싸개(ui-general-block__table-wrap)가 가로 스크롤 — 카드보다 넓으면 그 안에서
|
||||
scroll: (() => {
|
||||
const wrap = ed.querySelector("table")?.parentElement;
|
||||
return wrap
|
||||
? {
|
||||
overflowX: getComputedStyle(wrap).overflowX,
|
||||
wide: wrap.scrollWidth > wrap.clientWidth,
|
||||
sw: wrap.scrollWidth,
|
||||
cw: wrap.clientWidth,
|
||||
}
|
||||
: null;
|
||||
})(),
|
||||
};
|
||||
// 줄 누름 = 넣을 값 모달 · 수량산출 칸 = 수량 모달 — 닫기만(값 안 바꿈)
|
||||
const modal = async (open) => {
|
||||
open();
|
||||
const m = await until(() => document.querySelector(".ui-modal"), 2000);
|
||||
const t = m?.querySelector("h3")?.textContent ?? null;
|
||||
[...(m?.querySelectorAll("button") ?? [])].find((b) => b.textContent === "닫기")?.click();
|
||||
await until(() => !document.querySelector(".ui-modal"), 1500);
|
||||
return t;
|
||||
};
|
||||
const first = () => ed.querySelector("tbody tr[data-labor-logic]");
|
||||
out.rowModal = await modal(() => first().querySelector(".m02-labrow__name").click());
|
||||
out.qtyModal = await modal(() =>
|
||||
(
|
||||
first().querySelector("[data-labor-qty-open]") ?? first().querySelector(".m02-labrow__qty")
|
||||
).click(),
|
||||
);
|
||||
out.dirtyAfterModals = !save.disabled;
|
||||
// 빼기 하나 → 고침 표시 · 다른 항목으로 갔다 버림 확인 → item1 다시(서버 판으로 되돌림)
|
||||
btn("빼기").pop().click();
|
||||
await w(300);
|
||||
out.dirty = card.classList.contains("is-dirty") || !save.disabled;
|
||||
out.saveEnabled = !save.disabled;
|
||||
document.querySelector('[data-node="i|item3"]').click();
|
||||
const ask = await until(() => document.querySelector(".ui-confirm"), 2000);
|
||||
[...(ask?.querySelectorAll("button") ?? [])].find((b) => b.textContent === "확인")?.click();
|
||||
await w(600);
|
||||
const again = await pick();
|
||||
out.rowsAfter = again ? again.querySelectorAll("tbody tr[data-labor-logic]").length : null;
|
||||
out.dirtyAfter = !save.disabled;
|
||||
return JSON.stringify(out);
|
||||
})();
|
||||
Reference in New Issue
Block a user