feat(dev): 확정 없이 다음으로 — 등록 + 좌측 단추 (2/2)
앞 커밋 618b6cf4 의 신규 파일을 등록하고 화면 단추를 붙임.
두 걸음 규칙대로 파일이 먼저 push 된 뒤 등록 줄이 감.
문은 앞뒤 둘
- 서버(정본): ENVIRONMENT 가 개발이 아니면 세 입구 모두 403.
라우터는 다른 것과 같은 보호(로그인·회사·프로젝트 접근)를 받고
그 위에 환경을 한 번 더 봄
- 화면(보조): import.meta.env.DEV 일 때만 단추가 그려짐
눌렀을 때 바뀌는 것 — project_workflow_stages.state 한 칸뿐.
계산은 안 돌고 정본 값도 안 만듦. 실측(b269ea34):
우회 전 [] → 우회 [3 STALE · 4 NOT_STARTED · 5 NOT_STARTED] → 되돌림 → []
이미 COMPLETE 인 0·1·2 는 안 건드림 (진짜 확정은 보존)
되돌리기 — 푼 단계의 옛 상태를 message 에 DEV_UNLOCK:<옛상태> 로 적어 두고
[원래대로 되돌리기]가 그대로 복원. 그 표시가 없으면 진짜 확정이라 안 만짐.
드러남 — 우회 중이면 좌측에 「지금 확정을 건너뛴 상태입니다 — 값이 비어
보이는 것은 정상입니다. 건너뛴 단계 (3, 4, 5)」가 뜸.
⚠ 화면에서 잡은 것 — 단추를 패널 아래에 두니 overflow:hidden 에 잘려
화면 밖(y=772, 패널 740)이라 눌리지 않았음. 맨 위로 옮김(상태 경고이기도 함).
시험: 677 passed · 24 skipped (B05 코리도 1건 기존 깨짐, 무관) · tsc 오류 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -233,6 +233,72 @@ interface DraftSettings {
|
|||||||
dirty: boolean;
|
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` 는 저장 뒤 표를 다시 그리는 손잡이다 — 조건이 바뀌면 집계·운반 값이 달라진다. */
|
* `reload` 는 저장 뒤 표를 다시 그리는 손잡이다 — 조건이 바뀌면 집계·운반 값이 달라진다. */
|
||||||
function buildQuantitySidePanel(
|
function buildQuantitySidePanel(
|
||||||
@@ -244,6 +310,18 @@ function buildQuantitySidePanel(
|
|||||||
const panel = document.createElement("div");
|
const panel = document.createElement("div");
|
||||||
panel.className = "b08-quantity__panel";
|
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")));
|
panel.append(field(L("B08_Quantity_Side_Method"), L("B08_Quantity_Side_Method_Value")));
|
||||||
const entries = Object.entries(table?.conversion_factors ?? {});
|
const entries = Object.entries(table?.conversion_factors ?? {});
|
||||||
|
|||||||
@@ -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 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 B09_Estimation.B09_Estimation_Router import router as b09_estimation_router
|
||||||
from common_util.common_util_audit import note_api_call, record_call_burst
|
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 (
|
from common_util.common_util_auth import (
|
||||||
require_company,
|
require_company,
|
||||||
require_project_access,
|
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_earthwork_router, dependencies=protected_with_company)
|
||||||
app.include_router(b08_material_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(b09_estimation_router, dependencies=protected_with_company)
|
||||||
|
# 개발 전용 잠금 해제 — 다른 라우터와 **같은 보호**를 받는다(로그인·회사·프로젝트 접근).
|
||||||
|
# 그 위에 서버가 환경까지 한 번 더 본다.
|
||||||
|
app.include_router(dev_unlock_router, dependencies=protected_with_company)
|
||||||
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -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.",
|
"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: ["표토제거", "Topsoil Removal"],
|
||||||
B08_Quantity_Side_Topsoil_Label: ["표토 두께(m)", "Topsoil thickness (m)"],
|
B08_Quantity_Side_Topsoil_Label: ["표토 두께(m)", "Topsoil thickness (m)"],
|
||||||
B08_Quantity_Unset_Placeholder: ["안 정함", "Not set"],
|
B08_Quantity_Unset_Placeholder: ["안 정함", "Not set"],
|
||||||
|
|||||||
Reference in New Issue
Block a user