diff --git a/tools/est-test/PLAN.md b/tools/est-test/PLAN.md index d8e7085c5..57d79da07 100644 --- a/tools/est-test/PLAN.md +++ b/tools/est-test/PLAN.md @@ -487,6 +487,8 @@ ative-standard.js에 표준시장단가 원천(SilJukDat)과 내역 행(EstMainD - 남음: 구성비·요율 전환(CalcBase), 약어 표시(SName), 제비율 기준 문서 보기(원본 한글 파일). - [x] 수량산출서 검색·전체보기(원본 TFRMMULLANG btnSerch·btnAllDisplay — 빈칸·|·괄호 무시 포함 검색) — 고르기 줄을 `native-quantity-sheets.js` 로 옮겨 700줄 제한 유지. - 검증완료: `test_est_quantity_sheets.py` 검색 시험 포함 수량 시험 22개 통과. ORCA 브라우저에서 「집수」 검색 → 항목에 집수정이 있는 「배수」만·선택 이동, 전체보기 → 3개 복귀, 샘플 복구. + - [x] 셀복사·셀붙여넣기(원본 ActionCellCopy·ActionCellPaste) — 마지막으로 누른 칸의 열을 고른 행들(없으면 그 행)만큼 복사(시스템 클립보드에도 글자로), 다른 칸을 누르고 붙이면 그 행부터 아래로 차례로 넣음(열 달라도 됨, 수치 칸은 수치만, 코드·결과비목 칸 거부). + - 검증완료: 바인딩 시험 통과. ORCA 브라우저에서 자재 품명 2칸 복사 → 6번째 행 규격부터 붙여넣기(경유·휘발유), 코드 칸 거부, 샘플 복구. - [x] 산출 줄 잘라내기·복사·붙여넣기(원본 TFRMMULWORD popCut·popCopy·popPaste) — 산출 줄마다 복사·잘라내기·아래 붙여넣기. 붙일 때 수량 지정 표지를 떼고 글만(같은 항목이 두 번 잡히지 않게), 수량 지정된 줄은 잘라내기 거부. - 검증완료: 산출서·수량 흐름·바인딩 시험 22개 통과. ORCA 브라우저에서 표지 있는 줄 복사 → 아래 붙여넣기(표지 없음), 표지 줄 잘라내기 거부, 샘플 복구. - [x] 수량산출서 엑셀형식 전환(원본 TFRMMULLANG popSetMode — DataKind 로 편집기 교체) — 산출서 화면 「엑셀형식으로」: 엑셀 시트가 없으면 빈 시트를 만들어 엑셀형으로, 「일반형식으로」는 엑셀 내용을 보관한 채 끄고 셀 연결 수량을 쓰지 않음. 바꾼 산출서는 ESTX 저장 거부(JSON). diff --git a/tools/est-test/index.html b/tools/est-test/index.html index 3cb7a0323..c80e18952 100644 --- a/tools/est-test/index.html +++ b/tools/est-test/index.html @@ -59,6 +59,7 @@ + @@ -135,7 +136,9 @@ > + >
diff --git a/tools/est-test/native-business-actions.js b/tools/est-test/native-business-actions.js index 2edcd466c..3b3a696b1 100644 --- a/tools/est-test/native-business-actions.js +++ b/tools/est-test/native-business-actions.js @@ -630,7 +630,8 @@ const EstBusinessActions = (() => { (typeof EstDocEditor !== "undefined" && EstDocEditor.handle(name, state, node)) || (typeof EstDocForms !== "undefined" && EstDocForms.handle(name, state, node)) || (typeof EstBlockFile !== "undefined" && EstBlockFile.handle(name, state)) || - (typeof EstDisplay !== "undefined" && EstDisplay.handle(name, state)) + (typeof EstDisplay !== "undefined" && EstDisplay.handle(name, state)) || + (typeof EstCellClip !== "undefined" && EstCellClip.handle(name, state)) ) { EstApp.render(); } else if (name === "insert-annual") { diff --git a/tools/est-test/native-cell-clip.js b/tools/est-test/native-cell-clip.js new file mode 100644 index 000000000..cfa17ce0d --- /dev/null +++ b/tools/est-test/native-cell-clip.js @@ -0,0 +1,49 @@ +// 원본 셀복사·셀붙여넣기(TfrmEstMain ActionCellCopy·ActionCellPaste) — 마지막으로 누른 칸의 열을, 고른 행들(없으면 그 행)만큼 복사하고 +// 다른 칸을 누른 뒤 그 행부터 아래로 차례로 붙여넣는다(열이 달라도 됨). 코드·결과비목 칸은 연결 처리가 따로 있어 붙여넣기 거부. +const EstCellClip = (() => { + let last = null; + function copy(state) { + if (!last) throw Error("복사할 칸을 먼저 누르세요."); + const rows = state.project.tables[last.table] || [], + picked = [...state.selection].filter((i) => rows[i]).sort((a, b) => a - b), + list = picked.includes(last.row) ? picked : [last.row]; + state.cellClip = { field: last.field, values: list.map((i) => rows[i][last.field] ?? null) }; + const text = state.cellClip.values + .map((v) => (last.field === "LineData" ? EstText.display(v ?? "") : (v ?? ""))) + .join("\n"); + navigator.clipboard?.writeText(text).catch(() => {}); + state.notice = `${list.length}칸을 복사했습니다.`; + } + function paste(state) { + if (!last) throw Error("붙여넣을 칸을 먼저 누르세요."); + if (!state.cellClip?.values.length) throw Error("복사한 칸이 없습니다."); + if (["Code", "SCheck"].includes(last.field)) + throw Error("코드·결과비목 칸은 셀붙여넣기를 쓸 수 없습니다. 칸에서 직접 고치세요."); + const rows = state.project.tables[last.table] || [], + numeric = EstModel.numeric.has(last.field); + const values = state.cellClip.values.map((v) => { + if (!numeric) return v == null ? "" : String(v); + const n = v === "" || v == null ? null : Number(v); + if (n !== null && !Number.isFinite(n)) + throw Error("수치 칸에는 수치만 붙여넣을 수 있습니다."); + return n; + }); + if (last.row + values.length > rows.length) throw Error("붙여넣을 행이 모자랍니다."); + EstApp.change((project) => { + const table = project.tables[last.table]; + values.forEach((v, i) => (table[last.row + i][last.field] = v)); + }); + } + if (typeof document !== "undefined" && document.addEventListener) + document.addEventListener("focusin", (event) => { + const t = event.target.closest?.("[data-table][data-field][data-row]"); + if (t) last = { table: t.dataset.table, row: Number(t.dataset.row), field: t.dataset.field }; + }); + function handle(name, state) { + if (name === "cell-copy") copy(state); + else if (name === "cell-paste") paste(state); + else return false; + return true; + } + return { handle }; +})(); diff --git a/tools/est-test/server.cjs b/tools/est-test/server.cjs index ab5fb1db0..62924800f 100644 --- a/tools/est-test/server.cjs +++ b/tools/est-test/server.cjs @@ -80,6 +80,7 @@ const allowed = new Set([ "native-doc-forms.js", "native-block-file.js", "native-display.js", + "native-cell-clip.js", "native-wonga-tools.js", "native-estimate-print.js", "native-xml-check.js",