feat(795-4): B09 사본 규칙 — 만든 때 그대로 · [단가 갱신] 값만 · [재생성] 따로 · 시점 섞임 고침
- 구조물 · 서고 사본 단가 묶음 = M02 일위대가 · SC · SK 로직 사본 줄 먼저(`collect(over)`) · M01 에서 지워진 키 = `사본_남은줄.json`
- 단가 책 새 줄([저장] · 첫 굳힘) = 연결 사본 `단가.json` 자원 줄(구조물 지정 때 값 · `Linked.frozen_rows`) · 없는 자원만 지금 M01 + 책 `now_keys` · 입력 `now` · 알림 한 줄
- POST …/estimation/prices 기본 = 단가 갱신(값만 · 사본 재적용 없음) · job 재생성 = 옛 [업데이트] · 미리 보기 단가 갱신 = 바뀐 자원 · 총괄 · 대가 합 전 → 후
- B09 화면 단추 [업데이트] → [단가 갱신] · 미리 보기 창 [재생성] → 재생성 미리 보기 → [적용] · 안내 글 둘 이름 맞춤
- 옮기기 도구(보기만) — 프로젝트 층 못셈 14 = 「못셈(옛 SP 참조)」 갈래 · B09 구조물 · 서고 사본 대조(그대로 · 재생성 금액)
- 새 시험 test_795_4_b09_copy(8) · 낡은 시험 고침: test_203_1 · test_296 = 단추 이름 「업데이트」 · 호버 글 · 몸 `{ user }` 를 박아 둔 것 → 795-4 이름 · job 덧칸으로
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
/* =============================================================================
|
||||
* B09_Estimation_UI_Check.ts
|
||||
* B09 좌측 「데이터 점검」 묶음(PLAN 203-1) — [업데이트] · 연결 점검(옛 「연결 점검」 탭 내용).
|
||||
* B09 좌측 「데이터 점검」 묶음(PLAN 203-1) — [단가 갱신] · 연결 점검(옛 「연결 점검」 탭 내용).
|
||||
*
|
||||
* [업데이트] = 단가를 지금 M01 값으로 새로 굳힘 · 서버가 낡았다고 보면(`payload.stale` 까닭 한 줄)
|
||||
* [단가 갱신](795-4 · 옛 [업데이트]) = 자원 단가만 지금 M01 값 · 서버가 낡았다고 보면(`payload.stale` 까닭 한 줄)
|
||||
* 단추에 점 배지(`is-pending`) · 굳힌 시각 · 까닭 = 공용 호버
|
||||
* 미리 보기 고리(703-3) = [업데이트] 누름을 먼저 잡아 POST …/estimation/prices/preview(화면 캐시) →
|
||||
* 「바뀌는 단가 줄 N · 다시 가져올 구조물 사본 K · 고친 칸 M 지킴 · 노임 판 전 → 후」 창 → [적용] =
|
||||
* 원래 단추 일(서버가 사본 재적용 + 단가 다시 굳힘 · 덮개 그대로) · 닫기 = 안 함
|
||||
* 미리 보기 고리 = 누름을 먼저 잡아 POST …/estimation/prices/preview(화면 캐시) →
|
||||
* 「총괄 금액 전 → 후 · 바뀐 자원」 창 → [적용] = 원래 단추 일(단가만 · 사본 · 덮개 그대로) ·
|
||||
* 창의 [재생성] = 재생성 미리 보기(703-3 · 다시 가져올 사본 · 고친 칸 · 노임 판) → [적용] =
|
||||
* `job: 재생성`(사본 재적용 + 단가) · 닫기 = 안 함
|
||||
* 연결 점검 = 「빈 줄 n」(설계내역서 금액 모르는 줄 · 안내 · 집계표와 같은 수 · 701-2) 목록 +
|
||||
* 그 밖 확인 목록(일위대가 · 단가산출 · 재료 등 · 탭 · 키마다 하나)(누르면 그 탭 그 줄로 · 까닭 = 공용 호버) + 서버 알림 · M01 에 없는 키
|
||||
* ========================================================================== */
|
||||
@@ -62,15 +63,15 @@ export function checkItems(
|
||||
return { items, others, notes };
|
||||
}
|
||||
|
||||
/** [업데이트] 호버 글 — 굳힌 시각 + 낡은 까닭 */
|
||||
/** [단가 갱신] 호버 글 — 굳힌 시각 + 낡은 까닭 */
|
||||
export function refreshTip(payload: Row | null): string {
|
||||
const made = payload?.book?.made;
|
||||
const when = made ? `단가 굳힘 ${dateTimeText(String(made))}` : "단가 아직 안 굳힘";
|
||||
const stale = payload?.stale;
|
||||
return `${when} · 누르면 미리 보기 → [적용] = 지금 M01 값으로 새로 굳힘 · 원본 바뀐 구조물 사본 재적용 · 고친 칸 그대로${stale ? ` · 업데이트 필요 — ${stale}` : ""}`;
|
||||
return `${when} · 누르면 미리 보기 → [적용] = 자원 단가만 지금 M01 값 · 사본(로직) · 고친 칸 그대로 · 로직까지 새로 = 미리 보기의 [재생성]${stale ? ` · 단가 갱신 가능 — ${stale}` : ""}`;
|
||||
}
|
||||
|
||||
/** [업데이트] 미리 보기 글 줄(703-3) */
|
||||
/** [재생성] 미리 보기 글 줄(703-3 옛 [업데이트]) */
|
||||
export function previewLines(got: Row): string[] {
|
||||
const copies: Row[] = got.사본 || [];
|
||||
const sks: Row[] = got.단가산출 || [];
|
||||
@@ -87,46 +88,91 @@ export function previewLines(got: Row): string[] {
|
||||
];
|
||||
}
|
||||
|
||||
/** [업데이트] 누름 앞 미리 보기 — [적용] 이면 원래 단추 일 · 닫기 = 안 함 */
|
||||
const won = (v: unknown): string => (v == null ? "—" : Number(v).toLocaleString("ko-KR"));
|
||||
|
||||
/** 자원 단가 값 — 노임 · 기계 = 수 · 자재 = 출처별 값 */
|
||||
function priceText(v: unknown): string {
|
||||
if (v == null) return "새 줄";
|
||||
if (typeof v !== "object") return won(v);
|
||||
const got = Object.entries(v as Row).filter(([, x]) => x != null);
|
||||
return got.length ? got.map(([k, x]) => `${k} ${won(x)}`).join(" / ") : "—";
|
||||
}
|
||||
|
||||
/** [단가 갱신] 미리 보기 글 줄(795-4) — 총괄 금액 전 → 후 · 바뀐 자원 */
|
||||
export function priceLines(got: Row): string[] {
|
||||
const rows: Row[] = got.바뀐자원 || [];
|
||||
const edition = got.노임판 || {};
|
||||
const shown = 30;
|
||||
return [
|
||||
`총괄 금액 ${won(got.전)} → ${won(got.후)} 원 · 대가 합 ${won(got.대가?.전)} → ${won(got.대가?.후)} 원`,
|
||||
`바뀐 자원 ${rows.length}${got.계수 ? " · 운전사 계수 바뀜" : ""}${got.환율 ? " · 환율 바뀜" : ""}`,
|
||||
...rows
|
||||
.slice(0, shown)
|
||||
.map((r) => `· ${r.이름 || r.키} ${priceText(r.전)} → ${priceText(r.후)}`),
|
||||
...(rows.length > shown ? [`… 외 ${rows.length - shown}`] : []),
|
||||
`고친 칸 ${got.고친칸 ?? 0} · 구조물 사본(로직) 그대로 — 로직까지 새로는 [재생성]`,
|
||||
`노임 판 ${edition.전 || "—"} → ${edition.후 || "—"}`,
|
||||
];
|
||||
}
|
||||
|
||||
const REGEN = "재생성";
|
||||
|
||||
/** [단가 갱신] 누름 앞 미리 보기 — [적용] 이면 원래 단추 일(`data-b09-job` = 할일) · 닫기 = 안 함 ·
|
||||
* [재생성] = 재생성 미리 보기 → [적용] = 사본 재적용 + 단가(795-4 · 사용자가 따로 누를 때만) */
|
||||
function attachPreview(refresh: HTMLElement, payload: () => Row | null): void {
|
||||
let pass = false;
|
||||
const open = (job?: string): void => {
|
||||
const projectId = currentProjectId();
|
||||
const user = readState<Row>("b09-estimation", projectId) ?? payload()?.user ?? {};
|
||||
void fetch(`${API_BASE_URL}/projects/${projectId}/estimation/prices/preview`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(job ? { user, job } : { user }),
|
||||
})
|
||||
.then(async (response) => {
|
||||
const got = (await response.json()) as Row;
|
||||
if (!response.ok) throw new Error(String(got?.detail ?? response.status));
|
||||
openModal({
|
||||
title: job ? "재생성 미리 보기" : "단가 갱신 미리 보기",
|
||||
closeLabel: "닫기",
|
||||
width: "40rem",
|
||||
mount: (body, close, head) => {
|
||||
const apply = createButton({
|
||||
label: "적용",
|
||||
variant: "filled",
|
||||
onClick: () => {
|
||||
close();
|
||||
pass = true;
|
||||
if (job) refresh.dataset.b09Job = job;
|
||||
refresh.click();
|
||||
delete refresh.dataset.b09Job;
|
||||
},
|
||||
});
|
||||
apply.dataset.b09RefreshApply = "";
|
||||
if (!job) {
|
||||
const regen = createButton({
|
||||
label: REGEN,
|
||||
variant: "ghost",
|
||||
onClick: () => (close(), open(REGEN)),
|
||||
});
|
||||
regen.dataset.b09Regen = "";
|
||||
head.append(regen);
|
||||
}
|
||||
head.append(apply);
|
||||
const lines = job ? previewLines(got) : priceLines(got);
|
||||
body.append(...lines.map((line) => createText(line)));
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((error) => showToast(`미리 보기 못 함 — ${String(error)}`, "error"));
|
||||
};
|
||||
refresh.addEventListener(
|
||||
"click",
|
||||
(event) => {
|
||||
if (pass) return void (pass = false);
|
||||
event.stopImmediatePropagation();
|
||||
const projectId = currentProjectId();
|
||||
const user = readState<Row>("b09-estimation", projectId) ?? payload()?.user ?? {};
|
||||
void fetch(`${API_BASE_URL}/projects/${projectId}/estimation/prices/preview`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ user }),
|
||||
})
|
||||
.then(async (response) => {
|
||||
const got = (await response.json()) as Row;
|
||||
if (!response.ok) throw new Error(String(got?.detail ?? response.status));
|
||||
openModal({
|
||||
title: "업데이트 미리 보기",
|
||||
closeLabel: "닫기",
|
||||
width: "40rem",
|
||||
mount: (body, close, head) => {
|
||||
const apply = createButton({
|
||||
label: "적용",
|
||||
variant: "filled",
|
||||
onClick: () => {
|
||||
close();
|
||||
pass = true;
|
||||
refresh.click();
|
||||
},
|
||||
});
|
||||
apply.dataset.b09RefreshApply = "";
|
||||
head.append(apply);
|
||||
body.append(...previewLines(got).map((line) => createText(line)));
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch((error) => showToast(`미리 보기 못 함 — ${String(error)}`, "error"));
|
||||
open();
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user