feat(804-7): 줄 쌓기 「수식」 탭 수정 2차(요구 687 · 688)
- 로직 삭제 = 서버 정본에서 바로(로직 줄 + 수식 문서 · `/logic/remove`) → 로직 목록 · 부르는 곳(다른 수식 문서 · 일위대가 조합 · M02 단가산출 바탕)이 있으면 확인 창 전에 막고 까닭(`/logic/users`) - 머리 단추 다섯 — [신규 로직] · [로직 복사/생성] · [로직 삭제] · [되돌림] · [저장] · [저장] 한 번 = 기본정보 + 수식 · [되돌림] = 둘 다 저장 뒤 상태 · 줄 쌓기 [고침 버림] · [수식 저장]은 M01 에서 걷음(M02 사본은 그대로) - 합계 상자 화면 아래 고정 · 원문 손잡이 브라우저 오른쪽 끝 - 왼쪽: 그룹 머리 한 줄(접어도 [+] · X 자리 그대로) · 「비우면 번호 없음」 · 가나다 체크 걷음 · 금액 줄 한 줄 배치 · 이름 단추 = 그 이름 찾은 찾기 창 · 수량 기호 = 값 + 출처 꼬리표 · [+] 메뉴(단가산출 · 율 · 식 · 근거 글 · 틀 찾기 걷음) · 식 글 눌러 그 자리 고침([식 고치기] 걷음) · 단위 = 공용 단위 고르기(친 글도 씀) · 만약 안 탄 갈래 숨김 - 오른쪽 기계 줄: 비목 수 링크 걷음 · 제목 줄 누름 = 시간당 경비 펼침 · 펼친 제목 보통 글 - 요소 키가 기호 이름으로 샌 것(`k_EQ0006`) — 변환기가 자원 이름으로(`k_하베스터`) · 수식 문서 다시 빌드(10 파일 · 78 기호 · 금액 같음) - 시험 페이지 `tools/line-stack-test` · main.py 마운트 · vite 프록시 · M01 왼쪽 「시험: 줄 쌓기」 걷음 - 단위 표준 표 읽기 = 로그인만(M02 일반 사용자 단위 목록) - 시험: test_804_7 새로 · 804_2 · 804_4 이음 글 고침(createStackScreen 줄 나뉨) · 552_3 여는 단추 기본 ⋯ 꼴로 고침(부르는 쪽이 [+] 로 바꿀 수 있음) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,9 @@
|
||||
*
|
||||
* 데이터 흐름(CLAUDE.md 5장) — 고친 것은 캐시(sessionStorage)에 쌓고 [저장] 한 번에 `POST /save`.
|
||||
* 409 = 그 사이 파일이 바뀜 · 422 = 검사 걸림(아무것도 안 씀) — 까닭은 서버 글 그대로 보임.
|
||||
* 머리 단추(804-7 · 요구 687-2) = [신규 로직] · [로직 복사/생성] · [로직 삭제] · [되돌림] · [저장]
|
||||
* [저장] = 기본정보 · 별칭(로직 줄 `/save`) + 수식(줄 쌓기 `/logic/doc/save`) 한 번에 · [되돌림] = 둘 다 저장 뒤 상태로
|
||||
* [로직 삭제] = 확인 → 서버 정본에서 바로(`/logic/remove` — 로직 줄 + 수식 문서 · 부르는 곳 있으면 막고 까닭) → 로직 목록
|
||||
* ========================================================================== */
|
||||
|
||||
import { t as L } from "@ui/ui_template_locale";
|
||||
@@ -23,6 +26,8 @@ import {
|
||||
fetchLogicFiles,
|
||||
fetchLogics,
|
||||
fetchLogicSubs,
|
||||
fetchLogicUsers,
|
||||
removeLogic,
|
||||
saveFiles,
|
||||
type CalcLine,
|
||||
type TextAnswer,
|
||||
@@ -108,7 +113,12 @@ export async function mountM01LogicLab(
|
||||
let recalc: number | undefined;
|
||||
const drive = createDrive();
|
||||
// 수식 = 줄 쌓기(804-2 · 804-6 옛 수식 문서 탭 걷음) — 수식 문서 · 캐시 m01_doc_drafts
|
||||
const stack = createStackScreen({ base: "/m01/logic/doc", admin, cache: "m01_doc_drafts" });
|
||||
const stack = createStackScreen({
|
||||
base: "/m01/logic/doc",
|
||||
admin,
|
||||
cache: "m01_doc_drafts",
|
||||
page: () => persist(),
|
||||
});
|
||||
const leave = (): Promise<boolean> => stack.canLeave();
|
||||
const editor = el("div", { className: "m01-logic__editor" });
|
||||
// 로직 상세 — 왼쪽 지금 내용 · 오른쪽 접는 패널 「원문」
|
||||
@@ -166,8 +176,7 @@ export async function mountM01LogicLab(
|
||||
list.setMarks(marks, extra, aliases);
|
||||
const n = Object.keys(drafts).length;
|
||||
pending.textContent = n ? tx("Bar_Pending", { n }) : "";
|
||||
saveButton.disabled = discardButton.disabled = n === 0;
|
||||
saveButton.hidden = discardButton.hidden = n === 0; // 저장 안 한 고친 것이 없으면 숨김
|
||||
saveButton.disabled = discardButton.disabled = n === 0 && !stack.dirty(); // 로직 줄 · 수식 둘 다 봄
|
||||
};
|
||||
|
||||
const touch = (): void => {
|
||||
@@ -307,6 +316,7 @@ export async function mountM01LogicLab(
|
||||
};
|
||||
|
||||
let allLogics: LogicSummary[] = [];
|
||||
let saving = false; // [저장] 뒤 목록 다시 읽는 중 — 거름 되살림이 연 로직을 떠나지 않게(저장 뒤 다시 엶)
|
||||
|
||||
const reload = async (): Promise<void> => {
|
||||
const [logics, subs] = await Promise.all([fetchLogics(), fetchLogicSubs()]);
|
||||
@@ -324,7 +334,7 @@ export async function mountM01LogicLab(
|
||||
restore: true,
|
||||
onPick: (sub, detail) => {
|
||||
list.setPick(sub, detail);
|
||||
if (opened) void leave().then((ok) => ok && show(null));
|
||||
if (opened && !saving) void leave().then((ok) => ok && show(null));
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -351,8 +361,21 @@ export async function mountM01LogicLab(
|
||||
}
|
||||
};
|
||||
|
||||
/** 부르는 곳 까닭 띠 */
|
||||
const usedBy = (used: string[]): void => {
|
||||
errors.replaceChildren(
|
||||
el("strong", { text: tx("Delete_Used") }),
|
||||
...used.map((x) => el("div", { text: x })),
|
||||
);
|
||||
errors.hidden = false;
|
||||
};
|
||||
|
||||
const onDelete = async (): Promise<void> => {
|
||||
if (!opened?.row) return;
|
||||
if (opened.origKey) {
|
||||
const used = (await fetchLogicUsers(opened.origKey).catch(() => null))?.쓰는곳 ?? [];
|
||||
if (used.length) return usedBy(used); // 확인 창 전에 막음
|
||||
}
|
||||
if (
|
||||
!(await showConfirmDialog(
|
||||
tx("Confirm_Delete", { v: opened.row.원문번호 || opened.row.키 }),
|
||||
@@ -360,21 +383,38 @@ export async function mountM01LogicLab(
|
||||
))
|
||||
)
|
||||
return;
|
||||
if (opened.origKey === null) {
|
||||
delete drafts[opened.id];
|
||||
const was = opened;
|
||||
if (was.origKey === null) {
|
||||
delete drafts[was.id];
|
||||
persist();
|
||||
show(null);
|
||||
return;
|
||||
}
|
||||
drafts[opened.id] = pick(opened, null);
|
||||
opened.row = null;
|
||||
persist();
|
||||
show(opened);
|
||||
const key = was.origKey;
|
||||
showLoadingOverlay();
|
||||
try {
|
||||
const one = await fetchLogic(key); // 지금 판(캐시에 옛 판이 남았어도)
|
||||
const done = await saveRevised((ok) => removeLogic(key, one.file, one.version, ok));
|
||||
if (!done) return; // 개정 확인 취소
|
||||
delete drafts[was.id];
|
||||
await stack.discard(); // 그 로직 수식 캐시도 비움
|
||||
await reload();
|
||||
show(null); // 로직 목록
|
||||
showToast(tx("Delete_Done", { v: was.row?.원문번호 || key }), "success");
|
||||
} catch (error) {
|
||||
const used =
|
||||
error instanceof ApiError ? (error.detail as { 쓰는곳?: string[] })?.쓰는곳 : null;
|
||||
if (used?.length) usedBy(used);
|
||||
else failed(error);
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
}
|
||||
};
|
||||
|
||||
const onDiscard = async (): Promise<void> => {
|
||||
if (!(await showConfirmDialog(tx("Confirm_Discard"), tx("Bar_Discard")))) return;
|
||||
drafts = {};
|
||||
await stack.discard();
|
||||
persist();
|
||||
const was = opened;
|
||||
if (was && was.origKey !== null)
|
||||
@@ -393,7 +433,10 @@ export async function mountM01LogicLab(
|
||||
byFile.set(d.file, part);
|
||||
}
|
||||
if (!byFile.size) {
|
||||
showToast(tx("Save_Nothing"), "info");
|
||||
// 수식만 고침 — 줄 쌓기 정본 저장
|
||||
if (!stack.dirty()) return void showToast(tx("Save_Nothing"), "info");
|
||||
if (await stack.save()) showToast(tx("Save_Done"), "success");
|
||||
persist();
|
||||
return;
|
||||
}
|
||||
showLoadingOverlay();
|
||||
@@ -403,13 +446,16 @@ export async function mountM01LogicLab(
|
||||
if (!saved) return; // 개정 확인 취소 = 캐시 그대로
|
||||
const was = opened;
|
||||
drafts = {};
|
||||
await reload();
|
||||
saving = true;
|
||||
await reload().finally(() => (saving = false));
|
||||
showToast(tx("Save_Done"), "success");
|
||||
const newKey = was?.row && !was.row.키 ? saved.added?.at(-1)?.key : undefined;
|
||||
const made = newKey ? allLogics.find((x) => x.키 === newKey) : undefined;
|
||||
if (made) await open(logicId(made.구분, made.키), made.구분, made.키);
|
||||
else if (was?.row?.키) await open(logicId(was.sub, was.row.키), was.sub, was.row.키);
|
||||
else show(null);
|
||||
if (opened) await stack.save(); // 수식 고침도 같은 [저장]에(여는 중이면 다 읽고 · 새 로직 = 새 키로 이어 받은 뒤 · 고침 없으면 그냥)
|
||||
persist();
|
||||
} catch (error) {
|
||||
failed(error);
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user