diff --git a/M01_MasterData/M01_MasterData_UI_Combo.ts b/M01_MasterData/M01_MasterData_UI_Combo.ts index d4bf0346..fa94820a 100644 --- a/M01_MasterData/M01_MasterData_UI_Combo.ts +++ b/M01_MasterData/M01_MasterData_UI_Combo.ts @@ -65,21 +65,47 @@ export function mountM01Combo( let items: ComboSummary[] = []; let logics = new Map(); let pick = { sub: "", detail: "" }; + /** 화면을 바꾼 횟수 — 늦게 온 답은 이 값이 달라졌으면 버림 */ + let nav = 0; + /** 열어 둔 상세 — 다른 화면에 갔다 와도 그대로 돌아오게 기억 */ + let current: { combo: Combo; version: string } | null = null; + let logicsReady: Promise = Promise.resolve(); const filterHost = el("div"); side.comboHost.replaceChildren(filterHost); const view = el("div", { className: "m01-logic__editor" }); host.replaceChildren(view); - const load = async (): Promise => { + /** 목록만 받음 — 로직 이름표(1300여 개)는 따로 받아 목록을 붙잡지 않음 */ + const load = async (redraw = true): Promise => { + const my = nav; try { - const [combos, all] = await Promise.all([fetchCombos(), fetchLogics()]); - items = combos; - logics = new Map(all.map((x) => [x.키, x])); + items = await fetchCombos(); } catch (error) { showToast(error instanceof Error ? error.message : tc("Load_Failed"), "error"); } drawFilter(); - drawList(); + if (redraw && my === nav) drawList(); + }; + + const loadLogics = (): void => { + logicsReady = fetchLogics() + .then((all) => { + logics = new Map(all.map((x) => [x.키, x])); + }) + .catch((error) => + showToast(error instanceof Error ? error.message : tc("Load_Failed"), "error"), + ); + }; + + const show = async (): Promise => { + if (!logics.size) loadLogics(); + if (!items.length && !current) { + view.replaceChildren(el("p", { className: "m01-logic__muted", text: tc("Loading") })); + } + await load(false); + if (!current) drawList(); + await logicsReady; + if (current) draw(current.combo, current.version); }; /* --- 왼쪽 거름 --- */ @@ -124,6 +150,8 @@ export function mountM01Combo( }); const drawList = (): void => { + nav++; + current = null; const shown = items.filter( (x) => (!pick.sub || x.구분 === pick.sub) && (!pick.detail || x.상세구분 === pick.detail), ); @@ -169,7 +197,10 @@ export function mountM01Combo( const open = async (key: string): Promise => { try { + const my = ++nav; const one = await fetchCombo(key); + await logicsReady; + if (my !== nav) return; draw(one.combo, one.version); } catch (error) { showToast(error instanceof Error ? error.message : tc("Load_Failed"), "error"); @@ -178,6 +209,9 @@ export function mountM01Combo( /* --- 상세 --- */ const draw = (combo: Combo, version: string): void => { + nav++; + current = { combo, version }; + const saved = JSON.stringify(combo); const field = (label: string, name: "이름" | "구분" | "상세구분" | "단위" | "비고") => { const f = createInputField({ type: "text", label }); f.input.value = combo[name] ?? ""; @@ -208,7 +242,10 @@ export function mountM01Combo( text: info?.이름 ?? row.로직, attrs: { type: "button", title: tc("Open") }, }); - name.addEventListener("click", () => onOpenLogic(row.로직)); + name.addEventListener("click", async () => { + if (JSON.stringify(combo) !== saved && !(await showConfirmDialog(tc("LeaveAsk")))) return; + onOpenLogic(row.로직); + }); const btns = el("div", { className: "m01-logic__chips", children: [ @@ -274,11 +311,16 @@ export function mountM01Combo( variant: "filled", onClick: async () => { if (!combo.이름.trim()) return showToast(tc("NeedName"), "error"); + const my = nav; try { const key = combo.키 ? combo.키 : (await newCombo(combo)).key; if (combo.키) await editCombo(combo.키, version, combo); showToast(tc("Saved"), "success"); - await load(); + await load(false); + if (my !== nav) { + if (current?.combo === combo) current = null; // 저장 사이에 나감 — 옛 판본을 붙들지 않음 + return; + } await open(key); } catch (error) { showToast(reason(error), "error"); @@ -335,5 +377,5 @@ export function mountM01Combo( drawTable(); }; - return { show: load }; + return { show }; } diff --git a/M01_MasterData/M01_MasterData_UI_Combo_Text.ts b/M01_MasterData/M01_MasterData_UI_Combo_Text.ts index 60ca1532..4456b20f 100644 --- a/M01_MasterData/M01_MasterData_UI_Combo_Text.ts +++ b/M01_MasterData/M01_MasterData_UI_Combo_Text.ts @@ -18,6 +18,11 @@ const TEXT = { Saved: ["저장함", "Saved"], Deleted: ["지움", "Deleted"], Stale: ["그 사이 조합 파일이 바뀜 — 다시 열어 주세요", "The file changed — reopen it"], + Loading: ["불러오는 중…", "Loading…"], + LeaveAsk: [ + "저장 안 한 고침이 있음 — 로직 상세로 가면 이 화면에서는 그대로 두고 옮겨 감. 갈까요?", + "Unsaved edits — go to the logic anyway?", + ], NeedName: ["이름을 적어야 저장됨", "A name is required"], Load_Failed: ["불러오지 못함", "Load failed"], Head_Key: ["키", "Key"], diff --git a/resources/master_data/ref/_검증_조합_화면.md b/resources/master_data/ref/_검증_조합_화면.md index 8589d345..6b07023a 100644 --- a/resources/master_data/ref/_검증_조합_화면.md +++ b/resources/master_data/ref/_검증_조합_화면.md @@ -21,3 +21,7 @@ ORCA · 13-4-1 + 12-4 + 12-17-1 + 9-16-1 로 견본 조합을 끝까지 씀. 흔 - 「돌아오기」 = 컨테이너 제목을 접었다 펴야 함 · 그때 목록을 새로 받아 열어 둔 상세가 목록으로 바뀜(저장 안 한 고침은 사라짐). - 로직 상세로 갈 때 저장 안 한 조합 고침을 묻지 않음. - PLAN.md 에 4-3·4-4 줄이 이 창 판에 없어 체크·검증완료를 적지 못함. + +## 고친 뒤 + +- 넷 모두 고침 · ORCA 재확인: 펼치자마자 「불러오는 중」(0.1초) · 목록은 로직 이름표를 기다리지 않음 · 저장 직후 나가면 늦은 답 버림 · 다녀오면 열어 둔 상세(저장 안 한 고침 포함) 그대로 · 로직 상세로 갈 때 저장 안 한 고침이 있으면 확인창.