main_laptop_1 -> main byeonghap (4 hwangyeong 585 commits) #12

Merged
eomsangdon merged 585 commits from main_laptop_1 into main 2026-09-08 17:26:30 +09:00
3 changed files with 98 additions and 0 deletions
Showing only changes of commit 6e114ac990 - Show all commits
+78
View File
@@ -233,6 +233,72 @@ interface DraftSettings {
dirty: boolean;
}
/** 개발 전용 「확정 없이 다음으로」 한 줄 — 단추 둘 + 지금 상태 안내.
*
* ⚠ **조용히 넘어가지 않는다.** 우회로 열린 상태면 「확정을 건너뛴 상태입니다」를 띄운다 —
* 안 그러면 다음 사람이 「왜 값이 없나」로 헤맨다.
* ⚠ **되돌리는 단추를 같은 줄에 둔다.** 되돌릴 길이 안 보이면 검증용 프로젝트가 굳는다.
*/
function devUnlockRow(projectId: string, reload: () => void): HTMLElement {
const row = document.createElement("div");
row.className = "b08-quantity__dev";
const title = document.createElement("p");
title.className = "b08-quantity__note";
title.textContent = L("B08_Quantity_Dev_Title");
row.append(title);
const state = document.createElement("p");
state.className = "b08-quantity__note";
row.append(state);
const paint = (): void => {
fetch(`/api/projects/${projectId}/dev/unlock`, { credentials: "include" })
.then((response) => (response.ok ? response.json() : null))
.then((body: { bypassed_stages?: number[] } | null) => {
const stages = body?.bypassed_stages ?? [];
state.textContent = stages.length
? `${L("B08_Quantity_Dev_Bypassed")} (${stages.join(", ")})`
: L("B08_Quantity_Dev_Normal");
})
.catch(() => {
state.textContent = L("B08_Quantity_Dev_Normal");
});
};
paint();
const call = (method: "POST" | "DELETE", button: HTMLButtonElement): void => {
button.disabled = true;
fetch(`/api/projects/${projectId}/dev/unlock`, { method, credentials: "include" })
.then((response) => {
if (!response.ok) throw new Error(String(response.status));
showToast(L("B08_Quantity_Dev_Done"), "success");
paint();
reload();
})
.catch(() => showToast(L("B08_Quantity_Dev_Failed"), "error"))
.finally(() => {
button.disabled = false;
});
};
const unlock = createButton({
label: L("B08_Quantity_Dev_Unlock"),
variant: "ghost",
onClick: () => call("POST", unlock),
});
const relock = createButton({
label: L("B08_Quantity_Dev_Relock"),
variant: "ghost",
onClick: () => call("DELETE", relock),
});
const buttons = document.createElement("div");
buttons.className = "b08-quantity__actions ui-sidebar-actions";
buttons.append(unlock, relock);
row.append(buttons);
return row;
}
/** 좌측 패널: 산출 조건 + 하단 [저장]·[확정] 액션 행.
* `reload` 는 저장 뒤 표를 다시 그리는 손잡이다 — 조건이 바뀌면 집계·운반 값이 달라진다. */
function buildQuantitySidePanel(
@@ -244,6 +310,18 @@ function buildQuantitySidePanel(
const panel = document.createElement("div");
panel.className = "b08-quantity__panel";
// ── 개발 전용 「확정 없이 다음으로」 ────────────────────────────────────────
// ⚠ **맨 위에 둔다.** 패널이 `overflow: hidden` 이라 아래에 붙이면 화면 밖으로
// 밀려 **눌리지 않는다**(2026-09-08 화면에서 실제로 그랬다 — 단추 y=772,
// 패널 높이 740). 상태 경고이기도 하니 자리도 여기가 맞다.
// ⚠ **이것은 보조 문일 뿐이다.** 진짜 문은 서버가 `ENVIRONMENT` 로 막는다
// (`common_util_dev_unlock`). 화면만 숨기면 API 는 그대로 뚫려 있다.
// ⚠ **계산을 대신 돌리지 않는다** — 워크플로 잠금만 푼다. 값이 비어 보이는 것은
// 정상이고, 그것을 「미확보」로 보이는 것이 이 화면이 이미 하는 일이다.
if (import.meta.env.DEV && projectId) {
panel.append(devUnlockRow(projectId, reload));
}
// 계수는 서버 상수가 유일한 정의처다 — 화면은 보여 주기만 하고 값을 다시 적지 않는다.
panel.append(field(L("B08_Quantity_Side_Method"), L("B08_Quantity_Side_Method_Value")));
const entries = Object.entries(table?.conversion_factors ?? {});
+7
View File
@@ -63,6 +63,10 @@ from B08_Quantity.B08_Quantity_Router_Earthwork import router as b08_earthwork_r
from B08_Quantity.B08_Quantity_Router_Material import router as b08_material_router
from B09_Estimation.B09_Estimation_Router import router as b09_estimation_router
from common_util.common_util_audit import note_api_call, record_call_burst
# 개발환경 전용 — 「확정 없이 다음으로」. **문은 서버가 정본이다** — `ENVIRONMENT` 가
# 개발이 아니면 세 입구 모두 403 으로 거절한다(화면 단추 숨김은 보조).
from common_util.common_util_dev_unlock_router import router as dev_unlock_router
from common_util.common_util_auth import (
require_company,
require_project_access,
@@ -541,6 +545,9 @@ app.include_router(b08_quantity_router, dependencies=protected_with_company)
app.include_router(b08_earthwork_router, dependencies=protected_with_company)
app.include_router(b08_material_router, dependencies=protected_with_company)
app.include_router(b09_estimation_router, dependencies=protected_with_company)
# 개발 전용 잠금 해제 — 다른 라우터와 **같은 보호**를 받는다(로그인·회사·프로젝트 접근).
# 그 위에 서버가 환경까지 한 번 더 본다.
app.include_router(dev_unlock_router, dependencies=protected_with_company)
# ─────────────────────────────────────────────────────────────────────────
+13
View File
@@ -632,6 +632,19 @@ export const ui_locales_b2 = {
"콘크리트 물량이 「콘크리트 타설」 공종으로 견적에 넘어갑니다.",
"The placing method changes this work item's unit price — concrete volume is handed over as a placing work item.",
],
B08_Quantity_Dev_Title: [
"⚠ 개발 전용 — 확정을 건너뛰고 다음 단계를 엽니다(계산은 돌지 않습니다).",
"⚠ Dev only — unlocks the next stage without confirming (no recalculation).",
],
B08_Quantity_Dev_Unlock: ["확정 없이 다음으로", "Skip confirm"],
B08_Quantity_Dev_Relock: ["원래대로 되돌리기", "Undo skip"],
B08_Quantity_Dev_Bypassed: [
"지금 확정을 건너뛴 상태입니다 — 값이 비어 보이는 것은 정상입니다. 건너뛴 단계",
"Currently skipping confirmation — empty values are expected. Skipped stages",
],
B08_Quantity_Dev_Normal: ["건너뛴 단계 없음 (정상 상태)", "No skipped stages"],
B08_Quantity_Dev_Done: ["단계 잠금을 바꿨습니다.", "Stage lock updated."],
B08_Quantity_Dev_Failed: ["단계 잠금을 바꾸지 못했습니다.", "Failed to update stage lock."],
B08_Quantity_Side_Topsoil: ["표토제거", "Topsoil Removal"],
B08_Quantity_Side_Topsoil_Label: ["표토 두께(m)", "Topsoil thickness (m)"],
B08_Quantity_Unset_Placeholder: ["안 정함", "Not set"],