feat(M02): 단가산출 로직 줄 순서 — ▲▼ 작은 단추 + 손잡이 끌기 · 구조물 조합 카드도(85-3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W4uKaV4ZBhsJN4GHG7yRj4
This commit is contained in:
@@ -90,7 +90,33 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 순서 칸 2행(51-14) — 위 [위로] [아래로] / 아래 [옵션]·[수정] [삭제] · 줄 사이도 단추 묶음 간격 */
|
||||
/* 순서(85-3) — 손잡이 + ▲▼ 작은 단추 · 끌어 놓을 줄 위 선 */
|
||||
.m02-labrow__order {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.m02-labrow__arrow.ui-btn {
|
||||
min-width: 0;
|
||||
height: 20px;
|
||||
padding: 0 5px;
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.m02-labrow__grip {
|
||||
padding: 0 4px;
|
||||
color: var(--color-text-secondary);
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.m02-labrow__row.is-dragover td {
|
||||
box-shadow: inset 0 2px 0 var(--color-primary);
|
||||
}
|
||||
|
||||
/* 순서 칸 2행(51-14) — 위 손잡이 ▲▼ / 아래 [옵션]·[수정] [삭제] · 줄 사이도 단추 묶음 간격 */
|
||||
.m02-labrow__tools {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -186,7 +186,7 @@ export function mountLaborDetail(
|
||||
option: openRow,
|
||||
ref: openRef,
|
||||
qty: openQty,
|
||||
...(opt.card ? {} : { move }),
|
||||
move: reorder,
|
||||
remove: removeRow,
|
||||
alias: setAlias,
|
||||
}
|
||||
@@ -310,10 +310,10 @@ export function mountLaborDetail(
|
||||
}
|
||||
};
|
||||
|
||||
function move(i: number, d: number): void {
|
||||
const j = i + d;
|
||||
if (j < 0 || j >= cur.담은로직.length) return;
|
||||
[cur.담은로직[i], cur.담은로직[j]] = [cur.담은로직[j], cur.담은로직[i]];
|
||||
/** 줄 순서(85-3) — from 줄을 to 자리로(▲▼ = 옆 자리 · 끌기 = 놓은 줄 자리) */
|
||||
function reorder(from: number, to: number): void {
|
||||
if (from === to || to < 0 || to >= cur.담은로직.length) return;
|
||||
cur.담은로직.splice(to, 0, ...cur.담은로직.splice(from, 1));
|
||||
changed();
|
||||
}
|
||||
/** 줄 고름(47-5) — 하이라이트 + 하위 행(51-2 · 표만 다시 그림 · 다이어그램 안 다시 그림) + 원문 패널(47-4) ·
|
||||
|
||||
@@ -148,6 +148,9 @@ function moneyCell(
|
||||
}
|
||||
|
||||
/** 이름 칸 글마다 title = 전체 글(한 줄 말줄임 · 45-7) */
|
||||
/** 끌고 있는 줄 차례(85-3) — 손잡이 dragstart ~ drop */
|
||||
let dragFrom: number | null = null;
|
||||
|
||||
const titled = (td: HTMLElement): HTMLElement => {
|
||||
for (const c of td.children) c.setAttribute("title", c.textContent ?? "");
|
||||
return td;
|
||||
@@ -179,7 +182,8 @@ export interface RowHandlers {
|
||||
ref?: (i: number) => void;
|
||||
/** 수량산출 칸 누름(손 입력 · 엑셀에서 고르기) — 없으면 줄 누름과 같음 */
|
||||
qty?: (i: number) => void;
|
||||
move?: (i: number, d: number) => void;
|
||||
/** 줄 순서(85-3) — from 줄을 to 자리로(▲▼ = 옆 자리 · 손잡이 끌기 = 놓은 줄 자리) */
|
||||
move?: (from: number, to: number) => void;
|
||||
remove?: (i: number) => void;
|
||||
/** 별칭 칸 고침(44-6 · 글자마다 · 빈 글 = null) — 없으면 글만 보임 */
|
||||
alias?: (i: number, value: string | null) => void;
|
||||
@@ -324,6 +328,32 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
run();
|
||||
},
|
||||
});
|
||||
// 순서(85-3) — ▲▼ 작은 단추(글은 title · aria) + 손잡이 끌기(줄 전체는 안 끎 · 별칭 칸 글 고르기)
|
||||
const arrow = (mark: string, label: string, to: number): HTMLElement => {
|
||||
const b = small(mark, () => move?.(i, to));
|
||||
b.classList.add("m02-labrow__arrow");
|
||||
b.title = label;
|
||||
b.setAttribute("aria-label", label);
|
||||
if (to < 0 || to >= data.rows.length) (b as HTMLButtonElement).disabled = true;
|
||||
return b;
|
||||
};
|
||||
const grip = (): HTMLElement => {
|
||||
const g = el("span", {
|
||||
className: "m02-labrow__grip",
|
||||
text: "⠿",
|
||||
attrs: { draggable: "true", title: tc("Labor_Drag"), "data-labor-grip": String(i) },
|
||||
});
|
||||
g.addEventListener("click", (e) => e.stopPropagation());
|
||||
g.addEventListener("dragstart", (ev) => {
|
||||
dragFrom = i;
|
||||
ev.dataTransfer?.setData("text/plain", String(i));
|
||||
if (ev.dataTransfer) ev.dataTransfer.effectAllowed = "move";
|
||||
});
|
||||
g.addEventListener("dragend", () => {
|
||||
dragFrom = null;
|
||||
});
|
||||
return g;
|
||||
};
|
||||
const sel = data.selected === id ? " is-selected" : "";
|
||||
const tr = el("tr", {
|
||||
className: (on ? "m02-labrow__row is-open" : "m02-labrow__row") + sel,
|
||||
@@ -373,10 +403,14 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
children: [
|
||||
...(move
|
||||
? [
|
||||
group([
|
||||
small(tc("Up"), () => move(i, -1)),
|
||||
small(tc("Down"), () => move(i, 1)),
|
||||
]),
|
||||
el("div", {
|
||||
className: "m02-labrow__order",
|
||||
children: [
|
||||
grip(),
|
||||
arrow("▲", tc("Up"), i - 1),
|
||||
arrow("▼", tc("Down"), i + 1),
|
||||
],
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
group([
|
||||
@@ -403,6 +437,21 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
],
|
||||
});
|
||||
if (on) tr.addEventListener("click", () => on.open(i));
|
||||
if (move) {
|
||||
tr.addEventListener("dragover", (ev) => {
|
||||
if (dragFrom === null || dragFrom === i) return;
|
||||
ev.preventDefault();
|
||||
tr.classList.add("is-dragover");
|
||||
});
|
||||
tr.addEventListener("dragleave", () => tr.classList.remove("is-dragover"));
|
||||
tr.addEventListener("drop", (ev) => {
|
||||
ev.preventDefault();
|
||||
tr.classList.remove("is-dragover");
|
||||
const from = dragFrom;
|
||||
dragFrom = null;
|
||||
if (from !== null && from !== i) move(from, i);
|
||||
});
|
||||
}
|
||||
const subs = sel ? subRows(line, wait, tools) : [];
|
||||
if (!note) return [tr, ...subs];
|
||||
// 옵션 값 줄(44-6) — 이름 ~ 합계 칸에 걸친 한 줄(말줄임 · 전체 글은 title) · 행 높이 안 커짐
|
||||
|
||||
@@ -33,6 +33,7 @@ const TEXT = {
|
||||
Col_Move: ["순서", "Move"],
|
||||
Up: ["위로", "Up"],
|
||||
Down: ["아래로", "Down"],
|
||||
Labor_Drag: ["끌어 순서 바꿈", "Drag to reorder"],
|
||||
Remove: ["삭제", "Delete"],
|
||||
Labor_Option: ["옵션", "Options"],
|
||||
Labor_Edit: ["수정", "Edit"],
|
||||
|
||||
@@ -78,7 +78,7 @@ def test_일위대가_카드는_상세_카드_갈래():
|
||||
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 "move: reorder," in detail # 85-3 — 카드도 순서(▲▼ · 끌기) · 빼기는 그대로
|
||||
# 서고 모드(50-3 · lib)도 더하기 · 다이어그램 없음
|
||||
# 51-11 사전조합 = 일위대가 상세와 같은 [로직 추가] · 같은 목록(걸러냄 없음)
|
||||
assert "opt.card || promote ? null : addLogic" in detail
|
||||
@@ -134,7 +134,10 @@ def test_일위대가_줄_고름과_옵션_단추():
|
||||
) # 읽기만도 고름 · [옵션] = 보기 모달
|
||||
assert "selected: picked" in detail
|
||||
rows = _text("M02_MasterTemplete_Labor_Rows.ts")
|
||||
up, down = rows.index('small(tc("Up")'), rows.index('small(tc("Down")')
|
||||
# 85-3 — ▲▼ 작은 단추(글은 title · aria) + 손잡이 끌기
|
||||
up, down = rows.index('arrow("▲", tc("Up")'), rows.index('arrow("▼", tc("Down")')
|
||||
assert rows.index("grip(),") < up and 'b.setAttribute("aria-label", label)' in rows
|
||||
assert "move: reorder," in detail and "opt.card ? {} : { move }" not in detail # 카드도 순서
|
||||
opt, rm = (
|
||||
rows.index('small(tc("Labor_Option")'),
|
||||
rows.index('small(tc("Remove"), () => remove(i), "danger")'),
|
||||
|
||||
Reference in New Issue
Block a user