Merge remote-tracking branch 'origin/sub_laptop_4' into sub_laptop_5
This commit is contained in:
@@ -52,8 +52,7 @@
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
.m02-cellpick__now,
|
||||
.m02-cellpick__hint {
|
||||
.m02-cellpick__now {
|
||||
margin: 0 0 var(--spacing-8);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ export const CARD_HELP: Record<"structure" | "labor" | "area", CardHelp> = {
|
||||
["수정", "일위대가 상세를 엶 — 로직 추가 · 순서 바꾸기 · 다이어그램은 여기"],
|
||||
clear("일위대가"),
|
||||
["로직 줄 누름", "넣을 값 창 — 그 로직의 넣을 값 고침"],
|
||||
["수량산출 칸", "손 입력 · 면적산출 칸 고르기"],
|
||||
["수량산출 칸", "수기 입력 · 면적산출 칸 고르기"],
|
||||
["별칭 칸", "줄에 따로 이름 붙임"],
|
||||
["줄 끝 단추", "그 줄을 뺌"],
|
||||
["자재 추가", "자재를 골라 자재목록에 넣음"],
|
||||
@@ -72,7 +72,7 @@ export const CARD_HELP: Record<"structure" | "labor" | "area", CardHelp> = {
|
||||
steps: [
|
||||
"왼쪽 표에서 구조물 항목을 고름",
|
||||
"로직 줄을 눌러 넣을 값을 고침",
|
||||
"수량산출 칸에서 손 입력 또는 면적산출 칸을 고름",
|
||||
"수량산출 칸에서 수기 입력 또는 면적산출 칸을 고름",
|
||||
"[저장] — 로직을 더하거나 순서를 바꿀 때는 [수정]",
|
||||
],
|
||||
},
|
||||
|
||||
@@ -319,7 +319,6 @@ export function createItemCards(opts: ItemCardsOptions): ItemCardsHandle {
|
||||
}
|
||||
: {}),
|
||||
onChanged: (dirty) => laborCard.saver.mark(dirty && laborWritable()),
|
||||
goArea: null, // 면적 칸 잇기는 수량 모달 안(43-1) — 카드에서 화면을 옮기지 않음
|
||||
card: true,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -301,13 +301,6 @@
|
||||
font-size: var(--text-caption);
|
||||
}
|
||||
|
||||
.m02-labin__foot {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--spacing-8);
|
||||
margin-top: var(--spacing-12);
|
||||
}
|
||||
|
||||
/* 보기 갈래(면적산출 오른쪽 패널 · 일위대가 카드) */
|
||||
.m02-labview {
|
||||
display: flex;
|
||||
|
||||
@@ -55,7 +55,7 @@ import {
|
||||
diagramTitle,
|
||||
renderComboDiagram,
|
||||
} from "./M02_MasterTemplete_Labor_Diagram";
|
||||
import { openLaborInput, type GoArea } from "./M02_MasterTemplete_Labor_Input";
|
||||
import { openLaborInput } from "./M02_MasterTemplete_Labor_Input";
|
||||
import { openQtyModal } from "./M02_MasterTemplete_Labor_QtyModal";
|
||||
import { openRefEdit } from "./M02_MasterTemplete_Labor_RefEdit";
|
||||
import { afterRowDrag } from "./M02_MasterTemplete_Labor_RowDrag";
|
||||
@@ -81,8 +81,6 @@ export interface LaborDetailOptions {
|
||||
readOnly: boolean;
|
||||
/** 저장 안 한 고침이 생기거나 사라짐 */
|
||||
onChanged: (dirty: boolean) => void;
|
||||
/** 「면적산출에서 잇기」 — 그 열 면적산출 상세로(옮겨 가면 참 · 취소 = 거짓 · 없으면 단추 없음) */
|
||||
goArea: GoArea | null;
|
||||
/** 구조물 조합 카드 갈래(43-4) — 단가산출 로직 표 + 자재목록만 · 위로 · 아래로 · 로직 추가 ·
|
||||
* 자재 추가 · 정보(단위 · 본뜬) · 다이어그램 없음 · 옵션 · 삭제 · 줄 · 수량 모달은 상세와 같음 */
|
||||
card?: boolean;
|
||||
@@ -328,7 +326,6 @@ export function mountLaborDetail(
|
||||
unit,
|
||||
readOnly: !edit,
|
||||
sheet: { doc: area?.doc ?? null, layer: opt.layer, column: opt.column, pill },
|
||||
goArea: opt.goArea,
|
||||
...(lib ? { handOnly: true } : {}),
|
||||
done: (r) => {
|
||||
if (r.kind === "keep") return;
|
||||
|
||||
@@ -16,26 +16,6 @@ import "./M02_MasterTemplete_Labor.css";
|
||||
|
||||
const fill = (text: string, value: string): string => text.replace("{value}", value);
|
||||
|
||||
/** 면적산출 상세로 감 — 옮겨 갔으면 참(물음에서 취소 = 거짓) */
|
||||
export type GoArea = () => Promise<boolean>;
|
||||
|
||||
function footer(
|
||||
readOnly: boolean,
|
||||
ok: () => void,
|
||||
close: () => void,
|
||||
okLabel = tc("In_Ok"),
|
||||
): HTMLElement {
|
||||
return el("div", {
|
||||
className: "m02-labin__foot",
|
||||
children: readOnly
|
||||
? [createButton({ label: tc("In_Close"), variant: "ghost", onClick: close })]
|
||||
: [
|
||||
createButton({ label: tc("In_Cancel"), variant: "ghost", onClick: close }),
|
||||
createButton({ label: okLabel, variant: "filled", onClick: ok }),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export interface LaborInputResult {
|
||||
입력값: LaborValues;
|
||||
메모: string | null;
|
||||
@@ -79,7 +59,7 @@ export function openLaborInput(opt: LaborInputOptions): void {
|
||||
title: fill(tc("In_Title"), opt.title),
|
||||
closeLabel: tc("In_Close"),
|
||||
dialogClass: "m02-modal m02-labin",
|
||||
mount: (body, close) => {
|
||||
mount: (body, close, head) => {
|
||||
const rows = inputs.map((input) => {
|
||||
const field = createValueControl(input, draft[input.이름] ?? null, (value, isBad) => {
|
||||
if (isBad) bad.add(input.이름);
|
||||
@@ -184,8 +164,12 @@ export function openLaborInput(opt: LaborInputOptions): void {
|
||||
}),
|
||||
],
|
||||
}),
|
||||
footer(opt.readOnly, ok, close, opt.okLabel),
|
||||
);
|
||||
// [확인] = 머리 [닫기] 왼쪽 · 아래 단추 없음(머리 [닫기] = 취소 · 147-3) · 읽기만 = [닫기]만
|
||||
if (!opt.readOnly)
|
||||
head.append(
|
||||
createButton({ label: opt.okLabel ?? tc("In_Ok"), variant: "filled", onClick: ok }),
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/* =============================================================================
|
||||
* M02_MasterTemplete_Labor_QtyModal.ts
|
||||
* 일위대가 수량산출 모달(43-1) — 수량산출 칸 누름(로직 줄 · 자재 줄 같음) = 모달 하나.
|
||||
* · 위 = 지금 수량(손 · 칸 · —) · 손 입력 칸 · [연결 풀기]
|
||||
* · 위 = 지금 수량(수기 · 칸 · —) · 수기 입력 칸 · [연결 풀기]
|
||||
* · 아래 = 그 열 면적산출 시트를 모달 안에 바로(읽기만 · 실제 값 · 이은 칸 색 · `mountAreaSheet`) ·
|
||||
* 칸 누름 = 그 칸을 고름(함께 잇기 · 바꾸기 물음 그대로)
|
||||
* · [확인] = 손 값이 있으면 손 · 아니면 고른 칸으로 잇기 · 풀기 · [닫기] = 그대로
|
||||
* · 머리 [확인] = 수기 값이 있으면 수기 · 아니면 고른 칸으로 잇기 · 풀기 · 머리 [닫기] = 그대로
|
||||
* 잇기 · 풀기는 화면 문서만(저장은 일위대가 [저장] 때 면적 문서와 함께 · `Labor_Detail`).
|
||||
* ========================================================================== */
|
||||
|
||||
@@ -13,7 +13,6 @@ import { openModal } from "@ui/ui_template_modal";
|
||||
import type { Workbook } from "../A00_Common/spreadsheet/spreadsheet";
|
||||
import type { Layer } from "./M02_MasterTemplete_Api_Fetch";
|
||||
import { mountAreaSheet, type AreaCellPick } from "./M02_MasterTemplete_AreaCellPick";
|
||||
import type { GoArea } from "./M02_MasterTemplete_Labor_Input";
|
||||
import { tc } from "./M02_MasterTemplete_Labor_Text";
|
||||
import "./M02_Labor_Values.css";
|
||||
import "./M02_MasterTemplete_Labor.css";
|
||||
@@ -29,20 +28,18 @@ export type QtyResult =
|
||||
export interface QtyModalOptions {
|
||||
/** 「GF000219 돌쌓기 메쌓기(인력)」 */
|
||||
title: string;
|
||||
/** 지금 수량 글(「손 5 ㎥」 · 「B6 = 12.5」 · 「—」) */
|
||||
/** 지금 수량 글(「수기 5 ㎥」 · 「B6 = 12.5」 · 「—」) */
|
||||
now: string;
|
||||
/** 지금 칸에 이어져 있나 — [연결 풀기] */
|
||||
linked: boolean;
|
||||
/** 지금 손 값(없으면 null) */
|
||||
hand: number | null;
|
||||
/** 손 입력 단위(분모 · 자재 단위) */
|
||||
/** 수기 입력 단위(분모 · 자재 단위) */
|
||||
unit: string;
|
||||
readOnly: boolean;
|
||||
/** 그 열 면적산출 — `pill` 이 없으면(계산 전) 시트 대신 안내 */
|
||||
sheet: { doc: Workbook | null; layer: Layer; column: string; pill: string | null };
|
||||
/** [면적산출 상세로] — 없으면 단추 없음 */
|
||||
goArea: GoArea | null;
|
||||
/** 손 입력만(서고 사전조합 줄 · 51-6) — 면적산출 시트 없음 */
|
||||
/** 수기 입력만(서고 사전조합 줄 · 51-6) — 면적산출 시트 없음 */
|
||||
handOnly?: boolean;
|
||||
done: (result: QtyResult) => void;
|
||||
}
|
||||
@@ -55,7 +52,7 @@ export function openQtyModal(opt: QtyModalOptions): void {
|
||||
closeLabel: tc("In_Close"),
|
||||
dialogClass: "m02-modal m02-labin m02-labqty",
|
||||
beforeClose: () => (sheet?.destroy(), true),
|
||||
mount: (body, closeNow) => {
|
||||
mount: (body, closeNow, head) => {
|
||||
const finish = (): void => {
|
||||
sheet?.destroy();
|
||||
closeNow();
|
||||
@@ -65,10 +62,6 @@ export function openQtyModal(opt: QtyModalOptions): void {
|
||||
text: opt.now,
|
||||
attrs: { "data-labor-qty-now": "" },
|
||||
});
|
||||
const state = el("small", {
|
||||
className: "m02-labin__muted",
|
||||
attrs: { "data-labor-qty-state": "" },
|
||||
});
|
||||
const hand = el("input", {
|
||||
className: "ui-input m02-inputval__control",
|
||||
attrs: {
|
||||
@@ -80,7 +73,8 @@ export function openQtyModal(opt: QtyModalOptions): void {
|
||||
}) as HTMLInputElement;
|
||||
hand.readOnly = opt.readOnly;
|
||||
if (opt.hand !== null) hand.value = String(opt.hand);
|
||||
const unlink =
|
||||
// 시트 위 상태 · 안내 글 없음(147-5) — [연결 풀기] 누르면 그 단추가 꺼짐 · 칸을 고르면 다시 켜짐
|
||||
const unlink: HTMLButtonElement | null =
|
||||
opt.linked && !opt.readOnly
|
||||
? createButton({
|
||||
label: tc("Labor_Unlink"),
|
||||
@@ -88,20 +82,10 @@ export function openQtyModal(opt: QtyModalOptions): void {
|
||||
onClick: () => {
|
||||
pending = { kind: "unlink" };
|
||||
hand.value = "";
|
||||
state.textContent = tc("Labor_UnlinkPending");
|
||||
if (unlink) unlink.disabled = true;
|
||||
},
|
||||
})
|
||||
: null;
|
||||
const go = opt.goArea
|
||||
? createButton({
|
||||
label: tc("In_QtyGoArea"),
|
||||
variant: "ghost",
|
||||
onClick: () =>
|
||||
void opt.goArea?.().then((moved) => {
|
||||
if (moved) finish();
|
||||
}),
|
||||
})
|
||||
: null;
|
||||
const host = el("div", {
|
||||
className: "m02-cellpick__sheet",
|
||||
attrs: { "data-labor-qty-sheet": "" },
|
||||
@@ -117,17 +101,14 @@ export function openQtyModal(opt: QtyModalOptions): void {
|
||||
onPick: (pick) => {
|
||||
if (opt.readOnly) return;
|
||||
pending = { kind: "cell", pick };
|
||||
hand.value = ""; // 칸을 고르면 손 값은 지움(칸을 따름)
|
||||
state.textContent = tc("Labor_CellPending").replace("{value}", pick.칸);
|
||||
hand.value = ""; // 칸을 고르면 수기 값은 지움(칸을 따름)
|
||||
if (unlink) unlink.disabled = false;
|
||||
},
|
||||
});
|
||||
} else
|
||||
host.replaceChildren(
|
||||
el("p", { className: "m02-labin__muted", text: tc("Labor_PickWait") }),
|
||||
);
|
||||
hand.addEventListener("input", () => {
|
||||
if (hand.value.trim() !== "") state.textContent = "";
|
||||
});
|
||||
const ok = (): void => {
|
||||
const raw = hand.value.trim();
|
||||
if (raw !== "") {
|
||||
@@ -153,23 +134,13 @@ export function openQtyModal(opt: QtyModalOptions): void {
|
||||
],
|
||||
}),
|
||||
...(unlink ? [unlink] : []),
|
||||
...(go ? [go] : []),
|
||||
],
|
||||
}),
|
||||
state,
|
||||
...(opt.handOnly
|
||||
? []
|
||||
: [el("p", { className: "m02-cellpick__hint", text: tc("Labor_SheetHint") }), host]),
|
||||
el("div", {
|
||||
className: "m02-labin__foot",
|
||||
children: opt.readOnly
|
||||
? [createButton({ label: tc("In_Close"), variant: "ghost", onClick: finish })]
|
||||
: [
|
||||
createButton({ label: tc("In_Close"), variant: "ghost", onClick: finish }),
|
||||
createButton({ label: tc("In_Ok"), variant: "filled", onClick: ok }),
|
||||
],
|
||||
}),
|
||||
...(opt.handOnly ? [] : [host]),
|
||||
);
|
||||
// [확인] = 머리 [닫기] 왼쪽 · 아래 [닫기] 없음(147-3) · 읽기만 = 머리 [닫기]만
|
||||
if (!opt.readOnly)
|
||||
head.append(createButton({ label: tc("In_Ok"), variant: "filled", onClick: ok }));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -59,26 +59,16 @@ export async function openRefEdit(opt: RefEditOptions): Promise<void> {
|
||||
finish();
|
||||
return true;
|
||||
},
|
||||
mount: (body, close) => {
|
||||
mount: (body, close, head) => {
|
||||
const host = el("div", { attrs: { "data-labor-ref": opt.ref.키 } });
|
||||
handle = mountLaborDetail(host, doc, {
|
||||
layer,
|
||||
column: opt.ref.키,
|
||||
readOnly: opt.readOnly,
|
||||
onChanged: (d) => (dirty = d && !opt.readOnly),
|
||||
goArea: null,
|
||||
mode: "precombo",
|
||||
owner: opt.ref.주인,
|
||||
});
|
||||
const cancel = createButton({
|
||||
label: tc(opt.readOnly ? "In_Close" : "In_Cancel"),
|
||||
variant: "ghost",
|
||||
onClick: async () => {
|
||||
if (dirty && !(await showConfirmDialog(tc("Ref_Discard")))) return;
|
||||
close();
|
||||
finish();
|
||||
},
|
||||
});
|
||||
const ok = createButton({
|
||||
label: tc("Save"),
|
||||
variant: "filled",
|
||||
@@ -98,11 +88,9 @@ export async function openRefEdit(opt: RefEditOptions): Promise<void> {
|
||||
attrs: { "data-labor-notice": "" },
|
||||
}),
|
||||
host,
|
||||
el("div", {
|
||||
className: "m02-labin__foot",
|
||||
children: opt.readOnly ? [cancel] : [cancel, ok],
|
||||
}),
|
||||
);
|
||||
// [저장] = 머리 [닫기] 왼쪽 · 아래 단추 없음(머리 [닫기] = 취소 · 고친 것 있으면 물음 · 147-3)
|
||||
if (!opt.readOnly) head.append(ok);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ const BOOK = /^(건설|산림)품셈/;
|
||||
const num = (v: number): string =>
|
||||
Number(v.toPrecision(12)).toLocaleString("ko-KR", { maximumFractionDigits: 6 });
|
||||
|
||||
/** 수량 글 — 「수량 손 3 개소」 · 「수량 12.5 ㎡(B6)」(칸 = 그 알약을 이은 면적산출 칸 · 44-6) ·
|
||||
/** 수량 글 — 「수량 수기 입력 3 개소」 · 「수량 12.5 ㎡(B6)」(칸 = 그 알약을 이은 면적산출 칸 · 44-6) ·
|
||||
* 이은 칸이 비었으면 「수량 빈 칸(A1)」(수량 없음 · 서버와 같음) · 아니면 빈 글 */
|
||||
export function qtyText(
|
||||
line: { 수량?: number | null; 수량출처?: string; 알약?: string | null } | undefined,
|
||||
@@ -105,7 +105,7 @@ export function baseQtyText(
|
||||
return `${num(line.배 || 1)} ${line.분모}`;
|
||||
}
|
||||
|
||||
/** 지금 수량 짧은 글 — 「손 5 ㎥」 · 「12.5(B6)」 · 「빈 칸(B6)」 · 없으면 「—」 */
|
||||
/** 지금 수량 짧은 글 — 「수기 5 ㎥」 · 「12.5(B6)」 · 「빈 칸(B6)」 · 없으면 「—」 */
|
||||
export function qtyShort(
|
||||
line: { 수량?: number | null; 수량출처?: string; 알약?: string | null } | undefined,
|
||||
unit: string,
|
||||
@@ -119,7 +119,7 @@ export function qtyShort(
|
||||
return `${num(line.수량)}(${at ?? tc("In_QtyCell")})`;
|
||||
}
|
||||
|
||||
/** 수량산출 칸(40-1) — 위 결과단위 기준 수량(「10 ㎡」) · 아래 지금 수량(「손 5 ㎥」 · 「12.5(B6)」 · 「—」) ·
|
||||
/** 수량산출 칸(40-1) — 위 결과단위 기준 수량(「10 ㎡」) · 아래 지금 수량(「수기 5 ㎥」 · 「12.5(B6)」 · 「—」) ·
|
||||
* `open` 이 있으면 단추(줄 누름과 따로) */
|
||||
function qtyCell(
|
||||
line: LaborCalcLine | undefined,
|
||||
@@ -206,7 +206,7 @@ export interface RowHandlers {
|
||||
option?: (i: number) => void;
|
||||
/** 참조 줄 단추(51-14) — 일위조합 [수정] = 서고 원본 모달 · 없으면 단추 없음 */
|
||||
ref?: (i: number) => void;
|
||||
/** 수량산출 칸 누름(손 입력 · 엑셀에서 고르기) — 없으면 줄 누름과 같음 */
|
||||
/** 수량산출 칸 누름(수기 입력 · 엑셀에서 고르기) — 없으면 줄 누름과 같음 */
|
||||
qty?: (i: number) => void;
|
||||
/** 줄 순서(85-3) — from 줄을 to 자리로(▲▼ = 옆 자리 · 줄 끌기 102-2 = 놓은 줄 자리) */
|
||||
move?: (from: number, to: number) => void;
|
||||
|
||||
@@ -70,19 +70,13 @@ const TEXT = {
|
||||
Labor_Qty: ["수량", "Quantity"],
|
||||
Labor_QtyCol: ["수량산출", "Quantity"],
|
||||
Labor_Item: ["항목", "Item"],
|
||||
Labor_Hand: ["손", "Manual"],
|
||||
Labor_QtyPick: ["누르면 손 입력 · 엑셀에서 고르기", "Manual or pick a cell"],
|
||||
Labor_Hand: ["수기", "Manual"],
|
||||
Labor_QtyPick: ["누르면 수기 입력 · 엑셀에서 고르기", "Manual or pick a cell"],
|
||||
Labor_PickWait: ["계산이 끝난 뒤 고름", "Wait for the calculation"],
|
||||
Labor_NoArea: ["이 열 면적산출을 못 읽음", "Area sheet not loaded"],
|
||||
Labor_BadCell: ["칸 주소가 틀림", "Invalid cell"],
|
||||
Labor_QtyNow: ["지금 수량", "Now"],
|
||||
Labor_Unlink: ["연결 풀기", "Unlink"],
|
||||
Labor_UnlinkPending: ["연결 풀기 — [확인]을 누르면 풂", "Unlink on OK"],
|
||||
Labor_CellPending: ["고른 칸 {value} — [확인]을 누르면 이음", "Cell {value} — link on OK"],
|
||||
Labor_SheetHint: [
|
||||
"면적산출 시트 — 칸을 누르면 그 칸을 고름 · 손 입력이 있으면 손이 앞섬",
|
||||
"Area sheet — click a cell to pick it · manual value wins",
|
||||
],
|
||||
Labor_Base: ["기준", "Base"],
|
||||
Labor_Applied: ["반영", "Applied"],
|
||||
Labor_BasePrice: ["기준 단가", "Base price"],
|
||||
@@ -141,12 +135,10 @@ const TEXT = {
|
||||
Labor_Layer: ["켜쌓기 {n}%", "Layered {n}%"],
|
||||
In_QtyCell: ["면적산출 칸", "Area sheet cell"],
|
||||
In_QtyEmpty: ["빈 칸", "empty cell"],
|
||||
In_QtyHand: ["손 입력", "Manual"],
|
||||
In_QtyGoArea: ["면적산출에서 잇기", "Link in area sheet"],
|
||||
In_QtyNeed: ["손 입력 수량을 수로 적음", "Enter a number"],
|
||||
In_QtyHand: ["수기 입력", "Manual"],
|
||||
In_QtyNeed: ["수기 입력 수량을 수로 적음", "Enter a number"],
|
||||
In_Bad: ["범위 밖 값이 있음 — 고친 뒤 확인", "Out-of-range values — fix first"],
|
||||
In_Ok: ["확인", "OK"],
|
||||
In_Cancel: ["취소", "Cancel"],
|
||||
In_Close: ["닫기", "Close"],
|
||||
Lib_Name: ["이름", "Name"],
|
||||
// 서고 목록표(51-4)
|
||||
|
||||
@@ -423,13 +423,6 @@ export function createMain(
|
||||
column: at.name,
|
||||
readOnly,
|
||||
onChanged: (d) => (dirty = d && !readOnly),
|
||||
goArea: mode
|
||||
? null
|
||||
: async () => {
|
||||
if (!(await confirmLeave())) return false; // 취소 — 입력 모달 그대로
|
||||
void open({ ...at, kind: "area" });
|
||||
return true;
|
||||
},
|
||||
...(mode ? { mode, owner: at.owner ?? null } : {}),
|
||||
});
|
||||
// [저장] = 엑셀에서 고른 칸 연결(면적 문서) 먼저 · 그다음 일위대가(서버가 새 칸 값으로 계산값)
|
||||
|
||||
Reference in New Issue
Block a user