Merge remote-tracking branch 'origin/sub_laptop_2' into sub_laptop_3
This commit is contained in:
@@ -22,7 +22,7 @@ import "./M02_Labor_Values.css";
|
||||
const fill = (text: string, values: Record<string, string | number>): string =>
|
||||
text.replace(/\{(\w+)\}/g, (_, key: string) => String(values[key] ?? ""));
|
||||
|
||||
const CONTROL_TEXT = { auto: "자동값", outOfRange: "범위 {value} 밖" };
|
||||
const CONTROL_TEXT = { outOfRange: "범위 {value} 밖" };
|
||||
|
||||
const autoValue = (input: ItemInput): string | number | null => {
|
||||
const auto = input.자동값;
|
||||
@@ -60,17 +60,20 @@ export function createValueControl(
|
||||
},
|
||||
})
|
||||
) as HTMLInputElement | HTMLSelectElement;
|
||||
// 고르기 칸 — 「자동값 xx」 빈 선택지는 두지 않음(40-1 ③ · 자동값 = 늘 첫 선택지) · 값이 없으면 자동값을
|
||||
// 고른 채 흐리게 보이고 저장은 사용자가 바꿀 때만 · 자동값도 없으면 「—」 빈 선택지 하나
|
||||
if (control instanceof HTMLSelectElement) {
|
||||
control.append(
|
||||
el("option", {
|
||||
attrs: { value: "" },
|
||||
text: auto === null ? "—" : `${CONTROL_TEXT.auto} ${String(auto)}`,
|
||||
}),
|
||||
...(auto === null ? [el("option", { attrs: { value: "" }, text: "—" })] : []),
|
||||
...choices.map((c) => el("option", { attrs: { value: c.값 }, text: c.이름 })),
|
||||
);
|
||||
}
|
||||
const empty = value === null || value === "";
|
||||
control.value = empty ? "" : String(value);
|
||||
control.value = empty
|
||||
? control instanceof HTMLSelectElement && auto !== null
|
||||
? String(auto)
|
||||
: ""
|
||||
: String(value);
|
||||
control.classList.toggle("is-auto", empty);
|
||||
const error = el("span", { className: "m02-inputval__error" });
|
||||
const out: ValueControl = { control, error, wasBad: false };
|
||||
@@ -81,7 +84,7 @@ export function createValueControl(
|
||||
error.textContent = bad
|
||||
? fill(CONTROL_TEXT.outOfRange, { value: `${range?.min ?? ""}~${range?.max ?? ""}` })
|
||||
: "";
|
||||
control.classList.toggle("is-auto", raw === "");
|
||||
control.classList.toggle("is-auto", raw === ""); // 고르기 칸은 고르면 늘 저장 값
|
||||
onChange(storedOf(input, raw), bad);
|
||||
});
|
||||
return out;
|
||||
|
||||
@@ -181,6 +181,23 @@
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 수량산출 칸 — 윗줄에(차례 · 단위와 한 줄) · 칸 이름 없이 */
|
||||
.m02-labrow:not(.m02-labline) tr:has(.m02-labrow__qty) .m02-labrow__no,
|
||||
.m02-labrow tr:has(.m02-labrow__qty) .m02-labrow__unit:not([data-label]) {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.m02-labrow .m02-labrow__qty {
|
||||
order: 0;
|
||||
grid-column: span 2;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.m02-labrow .m02-labrow__qty::before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.m02-labrow__sum .m02-labrow__qty,
|
||||
.m02-labrow__sum .m02-labrow__no,
|
||||
.m02-labrow__sum .m02-labrow__unit {
|
||||
display: none;
|
||||
@@ -264,3 +281,27 @@
|
||||
padding: var(--spacing-4) 3px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 수량산출 칸(40-1) — 위 결과단위 기준 수량 · 아래 지금 수량 · 누르면 손 입력 · 엑셀에서 고르기 */
|
||||
.m02-labrow__qty {
|
||||
white-space: nowrap;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.m02-labrow__qtybtn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 2px var(--spacing-4);
|
||||
border: 1px dashed var(--color-border);
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.m02-labrow__qtybtn:hover {
|
||||
border-color: var(--color-primary);
|
||||
background: var(--color-mist-violet);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,15 @@ import { createButton, createInputField, el, showToast } from "@ui/ui_template_e
|
||||
import { ROUTES } from "@config/config_frontend";
|
||||
import { navigateTo } from "../A00_Common/router";
|
||||
import { setPendingLogicKey } from "../M01_MasterData/M01_MasterData_UI_Side";
|
||||
import { readTemplate, type ItemLogic, type Layer } from "./M02_MasterTemplete_Api_Fetch";
|
||||
import type { Workbook } from "../A00_Common/spreadsheet/spreadsheet";
|
||||
import { recalcWorkbook } from "../A00_Common/spreadsheet/spreadsheet_recalc";
|
||||
import {
|
||||
readTemplate,
|
||||
saveTemplate,
|
||||
type ItemLogic,
|
||||
type Layer,
|
||||
} from "./M02_MasterTemplete_Api_Fetch";
|
||||
import { linkAreaCell, pickAreaCell } from "./M02_MasterTemplete_AreaCellPick";
|
||||
import { settle } from "./M02_MasterTemplete_Labor_Calc";
|
||||
import {
|
||||
areaLinks,
|
||||
@@ -50,6 +58,9 @@ export interface LaborDetailOptions {
|
||||
|
||||
export interface LaborDetailHandle {
|
||||
getDoc: () => LaborDoc;
|
||||
/** [엑셀에서 고르기]로 바뀐 면적산출 문서를 저장(일위대가 [저장] 앞) · 저장했으면 참(없으면 거짓) —
|
||||
* 서버가 면적 저장 때 그 열 일위대가 계산값을 다시 적어 일위대가 판이 바뀜 → 부르는 쪽이 판을 다시 읽음 */
|
||||
saveArea: () => Promise<boolean>;
|
||||
destroy: () => void;
|
||||
}
|
||||
|
||||
@@ -72,6 +83,12 @@ export function mountLaborDetail(
|
||||
const cells = (): Map<string, string> => new Map([...links].map(([k, v]) => [k, v.칸]));
|
||||
const given = (): Record<string, number> =>
|
||||
Object.fromEntries([...links].flatMap(([k, v]) => (v.값 === null ? [] : [[k, v.값]])));
|
||||
/** 그 열 면적산출 문서 · 판 — [엑셀에서 고르기]로 이으면 바뀜(일위대가 [저장] 때 함께 저장) */
|
||||
let area: { doc: Workbook; 판: string } | null = null;
|
||||
let areaDirty = false;
|
||||
/** 알약 키 → 이름 · 단위(calc 답 `알약`) — 칸에 이을 때 연결 정보 */
|
||||
let pills = new Map<string, { 이름?: string; 단위?: string }>();
|
||||
const dirty = (): boolean => areaDirty || JSON.stringify(cur) !== saved;
|
||||
|
||||
const logicHost = el("div");
|
||||
const matHost = el("div");
|
||||
@@ -112,7 +129,7 @@ export function mountLaborDetail(
|
||||
logicTable(
|
||||
{ rows: cur.담은로직, logics, calc, fail, cells: cells() },
|
||||
// 읽기만이면 순서 · 빼기 없음(줄 누름 = 보기 모달은 둠)
|
||||
edit ? { open: openRow, move, remove: removeRow } : { open: openRow },
|
||||
edit ? { open: openRow, qty: openQty, move, remove: removeRow } : { open: openRow },
|
||||
),
|
||||
);
|
||||
matHost.replaceChildren(
|
||||
@@ -133,9 +150,13 @@ export function mountLaborDetail(
|
||||
fail = null;
|
||||
draw();
|
||||
try {
|
||||
const got = await calcLabor(opt.layer, opt.column, cur);
|
||||
// 저장 안 한 칸 연결이 있으면 그 칸 값으로(없으면 서버가 저장된 면적산출을 읽음)
|
||||
const got = await calcLabor(opt.layer, opt.column, cur, areaDirty ? given() : undefined);
|
||||
if (!alive || mine !== seq) return;
|
||||
calc = got;
|
||||
pills = new Map(
|
||||
(got.알약 as { 키: string; 이름?: string; 단위?: string }[]).map((p) => [p.키, p]),
|
||||
);
|
||||
} catch (error) {
|
||||
if (!alive || mine !== seq) return;
|
||||
fail = error instanceof Error ? error.message : tc("Labor_Calc_Fail");
|
||||
@@ -146,7 +167,7 @@ export function mountLaborDetail(
|
||||
|
||||
/** `qtyOnly` = 수량만 바뀜 — 기준은 그대로라 짝 셈(`Labor_Calc.settle`)으로 바로 곱함(서버 안 부름) */
|
||||
const changed = (qtyOnly = false): void => {
|
||||
opt.onChanged(JSON.stringify(cur) !== saved);
|
||||
opt.onChanged(dirty());
|
||||
if (qtyOnly && calc) {
|
||||
calc = settle(calc, cur, given());
|
||||
draw();
|
||||
@@ -160,6 +181,36 @@ export function mountLaborDetail(
|
||||
return `${at.칸} = ${at.값 === null ? "—" : at.값.toLocaleString("ko-KR")}${unit ? ` ${unit}` : ""}`;
|
||||
};
|
||||
|
||||
/** [엑셀에서 고르기] — 그 열 면적산출 칸을 골라 그 알약을 이음(화면 문서 · 저장은 [저장]) · 이었으면 참 */
|
||||
const pickCell = async (pill: string | null | undefined, label: string): Promise<boolean> => {
|
||||
if (!pill || !area) {
|
||||
showToast(tc(area ? "Labor_PickWait" : "Labor_NoArea"), "error");
|
||||
return false;
|
||||
}
|
||||
const got = await pickAreaCell({
|
||||
doc: area.doc,
|
||||
layer: opt.layer,
|
||||
column: opt.column,
|
||||
pill,
|
||||
label,
|
||||
});
|
||||
if (!got || !alive) return false;
|
||||
const info = pills.get(pill);
|
||||
const next = linkAreaCell(area.doc, pill, got.칸, {
|
||||
...(info?.이름 ? { 이름: info.이름 } : {}),
|
||||
...(info?.단위 ? { 단위: info.단위 } : {}),
|
||||
});
|
||||
if (!next) {
|
||||
showToast(tc("Labor_BadCell"), "error");
|
||||
return false;
|
||||
}
|
||||
next.계산값 = recalcWorkbook(next); // 식 칸 값도 바로 — 저장 때 서버가 다시 셈
|
||||
area.doc = next;
|
||||
links = areaLinks(next);
|
||||
areaDirty = true;
|
||||
return true;
|
||||
};
|
||||
|
||||
/** 아직 안 받은 로직 입력 목록만 받음 */
|
||||
const loadLogics = async (): Promise<void> => {
|
||||
const want = cur.담은로직.map((r) => r.로직).filter((k) => !logics.has(k));
|
||||
@@ -200,6 +251,7 @@ export function mountLaborDetail(
|
||||
qty: row.수량,
|
||||
linked: linkedText(line?.알약, line?.분모 ?? ""),
|
||||
unit: line?.분모 ?? "",
|
||||
pick: () => pickRow(i),
|
||||
},
|
||||
readOnly: !edit,
|
||||
goArea: opt.goArea,
|
||||
@@ -214,6 +266,37 @@ export function mountLaborDetail(
|
||||
});
|
||||
}
|
||||
|
||||
/** 그 줄 알약을 칸에 이음 — 이으면 손 수량은 지움(칸을 따름) */
|
||||
async function pickRow(i: number): Promise<boolean> {
|
||||
const row = cur.담은로직[i];
|
||||
const logic = logics.get(row.로직);
|
||||
if (!(await pickCell(lineOf(row.로직)?.알약, logic?.이름 ?? row.로직))) return false;
|
||||
row.수량 = null;
|
||||
changed(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 수량산출 칸 누름 — 손 입력 · 엑셀에서 고르기 */
|
||||
function openQty(i: number): void {
|
||||
const row = cur.담은로직[i];
|
||||
const line = lineOf(row.로직);
|
||||
openQtyModal(
|
||||
`${row.로직} ${logics.get(row.로직)?.이름 ?? ""}`.trim(),
|
||||
{
|
||||
qty: row.수량,
|
||||
linked: linkedText(line?.알약, line?.분모 ?? ""),
|
||||
unit: line?.분모 ?? "",
|
||||
pick: () => pickRow(i),
|
||||
},
|
||||
!edit,
|
||||
opt.goArea,
|
||||
(qty) => {
|
||||
row.수량 = qty;
|
||||
changed(true);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function openMat(i: number): void {
|
||||
const mat = cur.자재[i];
|
||||
const line = matOf(mat.코드);
|
||||
@@ -223,6 +306,12 @@ export function mountLaborDetail(
|
||||
qty: mat.수량,
|
||||
linked: linkedText(line?.알약, mat.단위 ?? ""),
|
||||
unit: mat.단위 ?? "",
|
||||
pick: async () => {
|
||||
if (!(await pickCell(line?.알약, mat.이름 ?? mat.코드))) return false;
|
||||
mat.수량 = null;
|
||||
changed(true);
|
||||
return true;
|
||||
},
|
||||
},
|
||||
!edit,
|
||||
opt.goArea,
|
||||
@@ -236,7 +325,6 @@ export function mountLaborDetail(
|
||||
const addLogic = createButton({
|
||||
label: tc("AddLogic"),
|
||||
variant: "filled",
|
||||
size: "sm",
|
||||
onClick: () =>
|
||||
openLogicPicker((x) => {
|
||||
if (cur.담은로직.some((r) => r.로직 === x.키))
|
||||
@@ -248,7 +336,6 @@ export function mountLaborDetail(
|
||||
const addMat = createButton({
|
||||
label: tc("Labor_AddMat"),
|
||||
variant: "filled",
|
||||
size: "sm",
|
||||
onClick: () =>
|
||||
pickMaterial((m) => {
|
||||
if (cur.자재.some((x) => x.코드 === m.코드))
|
||||
@@ -268,7 +355,7 @@ export function mountLaborDetail(
|
||||
unit.input.setAttribute("data-labor-field", "단위");
|
||||
unit.input.addEventListener("input", () => {
|
||||
cur.단위 = unit.input.value.trim() || null;
|
||||
opt.onChanged(JSON.stringify(cur) !== saved);
|
||||
opt.onChanged(dirty());
|
||||
});
|
||||
const origin = createInputField({ type: "text", label: tc("Labor_Origin") });
|
||||
origin.input.value = cur.본뜬?.키 ? `${cur.본뜬.층} ${cur.본뜬.키}` : tc("Origin_Self");
|
||||
@@ -306,7 +393,10 @@ export function mountLaborDetail(
|
||||
void Promise.all([
|
||||
loadLogics(),
|
||||
readTemplate(opt.layer, "area", opt.column, null).then(
|
||||
(got) => (links = areaLinks(got.문서)),
|
||||
(got) => {
|
||||
area = { doc: got.문서 as Workbook, 판: got.판 ?? "" };
|
||||
links = areaLinks(got.문서);
|
||||
},
|
||||
() => undefined,
|
||||
),
|
||||
]).then(() => {
|
||||
@@ -315,6 +405,13 @@ export function mountLaborDetail(
|
||||
|
||||
return {
|
||||
getDoc: () => cur,
|
||||
saveArea: async () => {
|
||||
if (!area || !areaDirty) return false;
|
||||
const info = await saveTemplate(opt.layer, "area", opt.column, null, area.판, area.doc);
|
||||
area.판 = info.판;
|
||||
areaDirty = false;
|
||||
return true;
|
||||
},
|
||||
destroy: () => {
|
||||
alive = false;
|
||||
window.clearTimeout(timer);
|
||||
|
||||
@@ -33,6 +33,8 @@ export interface QtySeat {
|
||||
linked: string | null;
|
||||
/** 손 입력 단위 표기(분모 · 자재 단위) */
|
||||
unit: string;
|
||||
/** [엑셀에서 고르기](40-1) — 그 열 면적산출 칸을 골라 이음 · 이었으면 참(모달 닫음) · 없으면 단추 없음 */
|
||||
pick?: (() => Promise<boolean>) | null;
|
||||
}
|
||||
|
||||
interface QtyPart {
|
||||
@@ -42,7 +44,12 @@ interface QtyPart {
|
||||
}
|
||||
|
||||
/** 수량 칸 — 「면적산출 칸」 / 「손 입력」 · 손이 칸보다 앞섬 */
|
||||
function qtyPart(seat: QtySeat, readOnly: boolean, goArea: (() => void) | null): QtyPart {
|
||||
function qtyPart(
|
||||
seat: QtySeat,
|
||||
readOnly: boolean,
|
||||
goArea: (() => void) | null,
|
||||
close: () => void,
|
||||
): QtyPart {
|
||||
const name = `m02-labin-qty-${Math.random().toString(36).slice(2)}`;
|
||||
const radio = (value: "cell" | "hand"): HTMLInputElement => {
|
||||
const input = el("input", { attrs: { type: "radio", name, value } }) as HTMLInputElement;
|
||||
@@ -67,6 +74,16 @@ function qtyPart(seat: QtySeat, readOnly: boolean, goArea: (() => void) | null):
|
||||
const go = goArea
|
||||
? createButton({ label: tc("In_QtyGoArea"), variant: "ghost", size: "sm", onClick: goArea })
|
||||
: null;
|
||||
const pickFn = seat.pick;
|
||||
const pick =
|
||||
pickFn && !readOnly
|
||||
? createButton({
|
||||
label: tc("In_QtyPick"),
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
onClick: () => void pickFn().then((linked) => linked && close()),
|
||||
})
|
||||
: null;
|
||||
const line = (input: HTMLInputElement, label: string, ...rest: HTMLElement[]): HTMLElement =>
|
||||
el("label", {
|
||||
className: "m02-labin__qty-line",
|
||||
@@ -83,6 +100,7 @@ function qtyPart(seat: QtySeat, readOnly: boolean, goArea: (() => void) | null):
|
||||
className: "m02-labin__muted",
|
||||
text: seat.linked ? fill(tc("In_QtyLinked"), seat.linked) : tc("In_QtyNoLink"),
|
||||
}),
|
||||
...(pick ? [pick] : []),
|
||||
...(go ? [go] : []),
|
||||
),
|
||||
line(hand, tc("In_QtyHand"), number, el("small", { text: seat.unit })),
|
||||
@@ -174,7 +192,7 @@ export function openLaborInput(opt: LaborInputOptions): void {
|
||||
}) as HTMLInputElement;
|
||||
memo.value = opt.memo ?? "";
|
||||
memo.readOnly = opt.readOnly;
|
||||
const qty = qtyPart(opt.seat, opt.readOnly, opt.goArea && leaveTo(opt.goArea, close));
|
||||
const qty = qtyPart(opt.seat, opt.readOnly, opt.goArea && leaveTo(opt.goArea, close), close);
|
||||
const ok = (): void => {
|
||||
if (bad.size) return showToast(tc("In_Bad"), "error");
|
||||
const 수량 = qty.read();
|
||||
@@ -226,7 +244,7 @@ export function openQtyModal(
|
||||
closeLabel: tc("In_Close"),
|
||||
dialogClass: "m02-modal m02-labin",
|
||||
mount: (body, close) => {
|
||||
const qty = qtyPart(seat, readOnly, goArea && leaveTo(goArea, close));
|
||||
const qty = qtyPart(seat, readOnly, goArea && leaveTo(goArea, close), close);
|
||||
const ok = (): void => {
|
||||
const 수량 = qty.read();
|
||||
if (수량 === undefined) return showToast(tc("In_QtyNeed"), "error");
|
||||
|
||||
@@ -73,6 +73,36 @@ export function qtyShort(
|
||||
return `${at ?? tc("In_QtyCell")} = ${num(line.수량)}`;
|
||||
}
|
||||
|
||||
/** 수량산출 칸(40-1) — 위 결과단위 기준 수량(「10 ㎡」) · 아래 지금 수량(「손 5 ㎥」 · 「B6 = 12.5」 · 「—」) ·
|
||||
* `open` 이 있으면 단추(줄 누름과 따로) */
|
||||
function qtyCell(
|
||||
line: LaborCalcLine | undefined,
|
||||
cells: ReadonlyMap<string, string> | undefined,
|
||||
open: (() => void) | null,
|
||||
): HTMLElement {
|
||||
const parts = [
|
||||
el("small", { className: "m02-labrow__base", text: baseQtyText(line) || "—" }),
|
||||
el("span", { className: "m02-labrow__applied", text: qtyShort(line, line?.분모 ?? "", cells) }),
|
||||
];
|
||||
const body = open
|
||||
? el("button", {
|
||||
className: "m02-labrow__qtybtn",
|
||||
attrs: { type: "button", title: tc("Labor_QtyPick"), "data-labor-qty-open": "" },
|
||||
children: parts,
|
||||
})
|
||||
: el("span", { children: parts });
|
||||
if (open)
|
||||
body.addEventListener("click", (e) => {
|
||||
e.stopPropagation(); // 줄 누름(입력 모달)과 따로
|
||||
open();
|
||||
});
|
||||
return el("td", {
|
||||
className: "m02-labrow__qty",
|
||||
attrs: { "data-label": tc("Labor_QtyCol") },
|
||||
children: [body],
|
||||
});
|
||||
}
|
||||
|
||||
/** 금액 칸 하나 — 위 기준(작게) · 아래 반영 · `label` = 좁은 칸에서 칸 이름(머리 줄 대신) */
|
||||
function moneyCell(
|
||||
base: number | null | undefined,
|
||||
@@ -120,6 +150,8 @@ const table = (heads: string[], rows: HTMLElement[], foot: HTMLElement[]): HTMLE
|
||||
/** 줄 누름 · 순서 · 빼기 — 순서 · 빼기가 없으면 그 칸 없음 */
|
||||
export interface RowHandlers {
|
||||
open: (i: number) => void;
|
||||
/** 수량산출 칸 누름(손 입력 · 엑셀에서 고르기) — 없으면 줄 누름과 같음 */
|
||||
qty?: (i: number) => void;
|
||||
move?: (i: number, d: number) => void;
|
||||
remove?: (i: number) => void;
|
||||
}
|
||||
@@ -146,14 +178,12 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
const line = lineOf(row.로직);
|
||||
const unit = logic?.결과단위 ?? "";
|
||||
const values = valueLine(logic?.입력 ?? [], row.입력값);
|
||||
const qty = qtyText(line, line?.분모 ?? "", data.cells);
|
||||
const note = [values.text, qty].filter(Boolean).join(" · ");
|
||||
const note = values.text;
|
||||
const why = line?.까닭 ?? (data.fail && i === 0 ? data.fail : null);
|
||||
const small = (label: string, run: () => void): HTMLElement =>
|
||||
createButton({
|
||||
label,
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
onClick: (e) => {
|
||||
e.stopPropagation(); // 줄 누름(입력 모달)과 따로
|
||||
run();
|
||||
@@ -189,6 +219,7 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
: []),
|
||||
],
|
||||
}),
|
||||
qtyCell(line, data.cells, on?.qty ? () => on.qty?.(i) : null),
|
||||
el("td", { className: "m02-labrow__unit", text: unit }),
|
||||
...COSTS.map((c) => moneyCell(line?.기준?.[c], line?.반영?.[c], wait, tc(HEAD[c]))),
|
||||
...(move && remove
|
||||
@@ -217,6 +248,7 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
children: [
|
||||
el("td", { className: "m02-labrow__no", text: "" }),
|
||||
el("td", { className: "m02-labrow__name", text: tc("Prev_Sum") }),
|
||||
el("td", { className: "m02-labrow__qty", text: "" }),
|
||||
el("td", { className: "m02-labrow__unit", text: "" }),
|
||||
...COSTS.map((c) =>
|
||||
moneyCell(sum(data.calc?.기준합, c), sum(data.calc?.반영합, c), wait, tc(HEAD[c])),
|
||||
@@ -229,6 +261,7 @@ export function logicTable(data: LogicTableData, on?: RowHandlers): HTMLElement
|
||||
const heads = [
|
||||
tc("Col_Order"),
|
||||
tc("Col_Name"),
|
||||
tc("Labor_QtyCol"),
|
||||
tc("Col_Unit"),
|
||||
...COSTS.map((c) => tc(HEAD[c])),
|
||||
...(tools ? [tc("Col_Move")] : []),
|
||||
@@ -300,7 +333,6 @@ export function matTable(data: MatTableData, on?: Omit<RowHandlers, "move">): HT
|
||||
createButton({
|
||||
label: tc("Remove"),
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
onClick: (e) => {
|
||||
e.stopPropagation();
|
||||
remove(i);
|
||||
|
||||
@@ -58,6 +58,11 @@ const TEXT = {
|
||||
Labor_Qty: ["수량", "Quantity"],
|
||||
Labor_QtyCol: ["수량산출", "Quantity"],
|
||||
Labor_Hand: ["손", "Manual"],
|
||||
Labor_QtyPick: ["누르면 손 입력 · 엑셀에서 고르기", "Manual or pick a cell"],
|
||||
In_QtyPick: ["엑셀에서 고르기", "Pick in sheet"],
|
||||
Labor_PickWait: ["계산이 끝난 뒤 고름", "Wait for the calculation"],
|
||||
Labor_NoArea: ["이 열 면적산출을 못 읽음", "Area sheet not loaded"],
|
||||
Labor_BadCell: ["칸 주소가 틀림", "Invalid cell"],
|
||||
Labor_Base: ["기준", "Base"],
|
||||
Labor_Applied: ["반영", "Applied"],
|
||||
Labor_BasePrice: ["기준 단가", "Base price"],
|
||||
|
||||
@@ -365,7 +365,7 @@ export function createMain(
|
||||
if (at.kind === "labor") {
|
||||
// 일위대가 상세(39-3) — 구조물 열마다 하나 · 고침은 이 화면 [저장]으로
|
||||
const { mountLaborDetail } = await import("./M02_MasterTemplete_Labor_Detail");
|
||||
editor = mountLaborDetail(host, doc, {
|
||||
const labor = mountLaborDetail(host, doc, {
|
||||
layer: at.layer,
|
||||
column: at.name,
|
||||
readOnly,
|
||||
@@ -376,6 +376,16 @@ export function createMain(
|
||||
return true;
|
||||
},
|
||||
});
|
||||
// [저장] = 엑셀에서 고른 칸 연결(면적 문서) 먼저 · 그다음 일위대가(서버가 새 칸 값으로 계산값)
|
||||
editor = {
|
||||
getDoc: async () => {
|
||||
// 면적 저장이 일위대가 계산값을 다시 적어 판이 바뀜 — 판을 다시 읽어 409 를 막음
|
||||
if (await labor.saveArea())
|
||||
version = (await readTemplate(at.layer, "labor", at.name, at.projectId)).판 ?? "";
|
||||
return labor.getDoc();
|
||||
},
|
||||
destroy: labor.destroy,
|
||||
};
|
||||
return;
|
||||
}
|
||||
if (at.kind === "area") {
|
||||
|
||||
Reference in New Issue
Block a user