Merge remote-tracking branch 'origin/main_laptop_1' into sub_laptop_6

This commit is contained in:
2026-09-30 19:13:48 +09:00
26 changed files with 120 additions and 37 deletions
@@ -186,7 +186,7 @@ export function createCondFmtPanel(ctx: SpreadsheetContext): PartHandle {
const val2 = createInputField({ label: "위(사이일 때만)" });
const fill = createInputField({ label: "채움색", type: "text", placeholder: "#RRGGBB" });
const fontColor = createInputField({ label: "글자색", type: "text", placeholder: "#RRGGBB" });
const addBtn = createButton({ label: "규칙 더하기" });
const addBtn = createButton({ label: "규칙 추가" });
addBtn.addEventListener("click", () => {
const 범위 = ctx.selection.범위;
+1 -1
View File
@@ -61,7 +61,7 @@ const TEXT = {
unlink: "연결 풀기",
remove: "목록에서 빼기",
removed: "목록에서 뺌 — 연결도 풀림",
add: "+ 자재 더하기",
add: "+ 자재 추가",
search: "자재 이름 · 규격 · 코드",
searching: "찾는 중…",
none: "맞는 자재 없음",
+1 -1
View File
@@ -142,7 +142,7 @@ export function createNameManagerPanel(ctx: SpreadsheetContext): PartHandle {
const root = el("div", { className: "ss-names" });
const list = el("ul", { className: "ss-names__list" });
const nameInput = createInputField({ label: "새 이름", placeholder: "단가" });
const addBtn = createButton({ label: "이름 더하기" });
const addBtn = createButton({ label: "이름 추가" });
let error: HTMLElement | null = null;
const showError = (msg: string | null): void => {
@@ -27,7 +27,7 @@ const TEXT = {
Column: ["열", "Column"],
Asc: ["오름차순", "Ascending"],
Desc: ["내림차순", "Descending"],
AddKey: ["기준 더하기", "Add level"],
AddKey: ["기준 추가", "Add level"],
RemoveKey: ["빼기", "Remove"],
Cancel: ["취소", "Cancel"],
Ok: ["정렬", "Sort"],
+1 -1
View File
@@ -67,7 +67,7 @@ const TEXT = {
"이름 골라 쓰거나 엑셀 형식 코드를 바로 침",
"Pick a preset or type an Excel format code",
],
SheetAdd: ["시트 더하기", "Add sheet"],
SheetAdd: ["시트 추가", "Add sheet"],
SheetRename: ["이름 바꾸기", "Rename"],
SheetDelete: ["지우기", "Delete"],
SheetDuplicate: ["복사", "Duplicate"],
@@ -146,7 +146,7 @@ export function createValidationPanel(ctx: SpreadsheetContext): PartHandle {
],
});
const message = createInputField({ label: "안내 글(생략 가능)" });
const addBtn = createButton({ label: "규칙 더하기" });
const addBtn = createButton({ label: "규칙 추가" });
addBtn.addEventListener("click", () => {
const 범위 = ctx.selection.범위;
+4 -3
View File
@@ -268,15 +268,16 @@
inset: 0;
z-index: var(--z-modal);
display: flex;
align-items: center;
/* 위에서 고정 거리 — 목록 길이가 바뀌어도 창 윗변 · 찾기 칸이 안 움직임 */
align-items: flex-start;
justify-content: center;
padding: var(--spacing-24);
padding: 10vh var(--spacing-24) var(--spacing-24);
background: rgba(38, 17, 74, 0.24);
}
.b01-dashboard__modal-panel {
width: min(560px, 100%);
max-height: calc(100vh - 2 * var(--spacing-24));
max-height: calc(90vh - var(--spacing-24));
overflow-y: auto;
/* 패널 끝까지 굴려도 뒤 대시보드로 넘어가지 않게 가둔다 (2026-09-13 사용자 지시). */
overscroll-behavior: contain;
@@ -16,7 +16,7 @@
.ui-modal.b05-routerec {
width: min(960px, 94vw);
max-height: 92vh;
--modal-max: 92vh;
font-size: var(--text-body-sm);
}
@@ -159,7 +159,7 @@
/* 복사해서 만들기(3-4) — 별칭 꼴 식 시트 */
.m01copy__sheet {
width: min(1040px, 94vw);
max-height: 90vh;
--modal-max: 90vh;
}
.m01copy__head {
@@ -94,7 +94,7 @@ const TEXT = {
Copy_Alias_Fork: ["같이 복사하기", "Copy along"],
Copy_Alias_Forked: ["복사됨", "Copied"],
Copy_Lines: ["식 줄 — 고치거나 더함", "Formula lines — edit or add"],
Copy_AddExtra: ["덧줄 더하기", "Add extra line"],
Copy_AddExtra: ["덧줄 추가", "Add extra line"],
Copy_ExtraName: ["새 덧줄", "New extra line"],
Copy_Save: ["저장(자체 로직)", "Save as mine"],
Copy_Cancel: ["취소", "Cancel"],
@@ -313,7 +313,7 @@ input.m01-logic__input {
inset: 0;
z-index: 1000;
display: flex;
align-items: center;
align-items: flex-start;
justify-content: center;
background-color: rgba(38, 17, 74, 0.35); /* 공용 창 틀과 같이 뒤를 가림 */
}
@@ -323,7 +323,9 @@ input.m01-logic__input {
flex-direction: column;
gap: var(--spacing-8);
width: min(720px, 92vw);
max-height: 80vh;
/* 위에서 고정 거리 — 공용 .ui-modal 과 같은 규칙(찾기 칸이 안 움직임) */
max-height: var(--modal-max, 80vh);
margin-top: calc((100vh - var(--modal-max, 80vh)) / 2);
padding: var(--spacing-16);
border-radius: var(--radius-xl);
border: 1px solid var(--color-border);
@@ -55,7 +55,7 @@ const TEXT = {
Extra_Title: ["덧줄", "Extra lines"],
Rounding: ["끝수", "Rounding"],
Formula: ["식", "Formula"],
AddRow: ["줄 더하기", "Add line"],
AddRow: ["줄 추가", "Add line"],
DeleteRow: ["줄 지우기", "Delete line"],
Calc_Title: ["시험 계산", "Test calculation"],
Calc_Run: ["계산", "Calculate"],
@@ -124,10 +124,10 @@ const TEXT = {
/* ── 상자에서 고치기 ── */
Edit_On: ["고치기", "Edit"],
Edit_Off: ["고치기 끝", "Done"],
Edit_AddHo: ["호표 줄 더하기", "Add unit-cost line"],
Edit_AddInput: ["설계 값 더하기", "Add design value"],
Edit_AddMiddle: ["표 찾기 더하기", "Add table lookup"],
Edit_AddExtra: ["덧줄 더하기", "Add extra line"],
Edit_AddHo: ["호표 줄 추가", "Add unit-cost line"],
Edit_AddInput: ["설계 값 추가", "Add design value"],
Edit_AddMiddle: ["표 찾기 추가", "Add table lookup"],
Edit_AddExtra: ["덧줄 추가", "Add extra line"],
Edit_Qty: ["수량 식", "Qty formula"],
/* ── 멈춘 까닭을 설계자 말로 ── */
Stop_Missing: [
+2 -1
View File
@@ -185,7 +185,7 @@
inset: 0;
z-index: 1000;
display: flex;
align-items: center;
align-items: flex-start;
justify-content: center;
background: rgba(0, 0, 0, 0.45);
}
@@ -195,6 +195,7 @@
gap: 8px;
width: min(640px, 92vw);
max-height: 80vh;
margin-top: 10vh; /* 위에서 고정 거리 — 찾기 칸이 안 움직임 */
padding: 16px;
border-radius: 8px;
background: var(--color-surface, #fff);
@@ -36,7 +36,7 @@ const TEXT = {
up: "위",
down: "아래",
remove: "빼기",
add: "로직 더하기",
add: "로직 추가",
apply: "적용",
close: "닫기",
empty: "담은 로직이 없음",
@@ -81,6 +81,7 @@
.m02-pop > .ui-modal {
position: absolute;
margin-top: 0;
width: 23rem;
max-width: calc(100vw - 16px);
box-shadow: 0 4px 16px rgb(0 0 0 / 25%);
@@ -8,7 +8,7 @@ import { currentLanguageIndex } from "@ui/ui_template_locale";
const TEXT = {
Title: ["일위대가 조합", "Unit-price combination"],
Library: ["일위대가 라이브러리", "Unit-price library"],
AddCombo: ["조합 더하기", "Add combination"],
AddCombo: ["조합 추가", "Add combination"],
ComboName: ["조합 이름", "Combination name"],
ReadOnly: ["읽기만 — 이 층은 고칠 수 없음", "Read only — this layer cannot be edited"],
Sub: ["구분", "Category"],
@@ -52,7 +52,7 @@ const TEXT = {
Up: ["올리기", "Up"],
Down: ["내리기", "Down"],
Remove: ["빼기", "Remove"],
AddLogic: ["로직 더하기", "Add logic"],
AddLogic: ["로직 추가", "Add logic"],
NoLogic: ["담은 로직 없음", "No logics yet"],
Open: ["열기", "Open"],
Pick_Title: ["로직 고르기", "Pick a logic"],
@@ -35,7 +35,7 @@ const TEXT = {
unit: "단위",
cell: "이은 칸",
remove: "빼기",
add: "자재 더하기",
add: "자재 추가",
apply: "적용",
undo: "되돌리기",
empty: "담은 자재가 없음",
@@ -51,7 +51,7 @@ const TEXT = {
askAlone:
"이 자재 조합은 모든 프로젝트가 같이 씀 — 고친 내용으로 새 자재 조합을 만들고 이 열만 새 조합을 가리킴 · 같은 내용이면 있는 조합을 씀",
failed: "자재 조합을 못 고침 — {value}",
pickTitle: "자재 더하기",
pickTitle: "자재 추가",
pickClose: "닫기",
search: "자재 이름 · 규격 · 코드",
searching: "찾는 중…",
@@ -36,7 +36,7 @@ const TEXT = {
Col_Spec: ["규격", "Spec"],
Col_Unit: ["단위", "Unit"],
Remove: ["빼기", "Remove"],
AddItem: ["자재 더하기", "Add material"],
AddItem: ["자재 추가", "Add material"],
NoItem: ["담은 자재 없음", "No materials yet"],
} as const satisfies Record<string, readonly [string, string]>;
@@ -2,11 +2,12 @@
"그룹": "일위대가조합",
"판": "2026",
"비고": ["조합 = 단가산출 로직을 골라 담은 묶음 · 수량·비율·계산은 두지 않음(프로젝트 몫)"],
"폴더": [["산림품셈"], ["실무 일위대가"]],
"줄": [
{
"키": "UA000001",
"이름": "견본 조합(지우셔도 됨)",
"구분": "산림품셈",
"구분": null,
"상세구분": null,
"단위": "식",
"담은로직": [{ "로직": "GF000219", "메모": "메쌓기 — 돌쌓기 인력" }, { "로직": "GF000119", "메모": "노체포설 — 굴착기 0.7㎥" }, { "로직": "GF000137", "메모": "운반 — 중기운반 트레일러(20ton)" }],
@@ -0,0 +1,74 @@
"""모달 위 고정 · 「더하기」 → 「추가」(PLAN 30-1) — 화면 판정은 ORCA.
· 찾기 있는 모달은 세로 가운데 대신 위에서 고정 거리(목록 길이가 바뀌어도 찾기 칸이 안 움직임)
· 보이는 글에 「더하기」 없음(주석 · 안쪽 명령 이름 `시트더하기` 는 뺌)
"""
from __future__ import annotations
import re
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
def _text(rel: str) -> str:
return (ROOT / rel).read_text(encoding="utf-8")
def _block(css: str, selector: str) -> str:
found = re.search(rf"^{re.escape(selector)} \{{([^}}]*)\}}", css, re.M)
assert found, selector
return found.group(1)
def test_공용_모달_위_고정():
css = _text("ui_template/ui_template_modal.css")
assert "align-items: flex-start;" in _block(css, ".ui-modal__backdrop")
modal = _block(css, ".ui-modal")
assert "max-height: var(--modal-max, 80vh);" in modal
assert "margin-top: calc((100vh - var(--modal-max, 80vh)) / 2);" in modal
# 큰 창은 max-height 대신 --modal-max(위 거리가 함께 맞음)
assert "--modal-max: 94vh;" in _block(
_text("ui_template/ui_template_item_picker.css"), ".ui-modal.ui-item-picker"
)
assert "--modal-max: 92vh;" in _block(
_text("B05_Profile/B05_Profile_UI_Style_RouteRecommend.css"), ".ui-modal.b05-routerec"
)
assert "margin-top: 0;" in _block(
_text("M02_MasterTemplete/M02_MasterTemplete_Drawing.css"), ".m02-pop > .ui-modal"
)
def test_따로_둔_모달도_위_고정():
logic = _text("M01_MasterData/M01_MasterData_UI_Logic_Style.css")
assert "align-items: flex-start;" in _block(logic, ".m01-logic__backdrop")
assert "margin-top: calc((100vh - var(--modal-max, 80vh)) / 2);" in _block(
logic, ".m01-logic__pick"
)
assert "--modal-max: 90vh;" in _block(
_text("M01_MasterData/M01_MasterData_UI_LogicLab_Style.css"), ".m01copy__sheet"
)
m01 = _text("M01_MasterData/M01_MasterData_UI_Style.css")
assert "align-items: flex-start;" in _block(m01, ".m01-procure")
assert "margin-top: 10vh;" in _block(m01, ".m01-procure__box")
b01 = _text("B01_Dashboard/B01_Dashboard_UI_Style.css")
assert "align-items: flex-start;" in _block(b01, ".b01-dashboard__modal")
def test_보이는_글에_더하기_없음():
files = subprocess.run(
["git", "ls-files", "*.ts"], cwd=ROOT, capture_output=True, text=True, encoding="utf-8"
).stdout.split()
left = []
for rel in files:
if rel.startswith(("tmp/", "docs/", "resources/")):
continue
for no, line in enumerate(_text(rel).splitlines(), 1):
if line.strip().startswith(("//", "*", "/*")):
continue
for m in re.finditer(r"([\"'`])((?:(?!\1).)*더하기(?:(?!\1).)*)\1", line):
if m.group(2) != "시트더하기": # 안쪽 명령 이름 — 화면에 안 나옴
left.append(f"{rel}:{no}: {m.group(2)}")
assert not left, left
+2 -2
View File
@@ -6,7 +6,7 @@
import { currentLanguageIndex } from "@ui/ui_template_locale";
const TEXT = {
Col_Add: ["열 더하기", "Add column"],
Col_Add: ["열 추가", "Add column"],
Col_Delete: ["열 지우기", "Delete column"],
Col_New: ["새 열", "New column"],
Row_Unit: ["단위", "Unit"],
@@ -14,7 +14,7 @@ const TEXT = {
Row_Drawing: ["도면", "Drawing"],
Row_Area: ["면적산출", "Area"],
Row_Material: ["재료산출", "Material"],
Add_Suffix: [" 더하기", " +"],
Add_Suffix: [" 추가", " +"],
New_Prefix: ["새 ", "New "],
Drawing_None: ["없음", "None"],
Page: ["쪽", "page"],
+1 -1
View File
@@ -2,7 +2,7 @@
/* 공용 모달(.ui-modal)의 폭 · 높이보다 앞서게 — 불러오는 차례에 따라 뒤집히지 않도록 두 클래스 */
.ui-modal.ui-item-picker {
width: min(1320px, 96vw);
max-height: 94vh;
--modal-max: 94vh;
}
.ui-item-picker .ui-modal__body {
+1 -1
View File
@@ -94,7 +94,7 @@ export const ui_locales_b3 = {
B09_Sheet_Edit: ["편집", "Edit"],
B09_Sheet_EditDone: ["편집 끝", "Done"],
B09_Sheet_RemoveRow: ["이 줄을 뺌(↺ 로 되돌림)", "Remove this row (undo with ↺)"],
B09_Sheet_AddRow: ["줄 더하기", "Add row"],
B09_Sheet_AddRow: ["줄 추가", "Add row"],
B09_Sheet_SearchTitle: [
"노임·자재·중기·일위대가 이름으로 찾기",
"Search labor, material, machine or unit price",
+1 -1
View File
@@ -17,7 +17,7 @@ export const ui_locales_m1 = {
M01_Page: ["쪽", "pages"],
M01_Rows: ["줄", "rows"],
M01_NoRows: ["줄 없음", "No rows"],
M01_RowAdd: ["줄 더하기", "Add row"],
M01_RowAdd: ["줄 추가", "Add row"],
M01_RowDelete: ["지우기", "Delete"],
M01_RowRestore: ["되살리기", "Restore"],
M01_RowRemove: ["빼기", "Remove"],
+3 -3
View File
@@ -111,11 +111,11 @@ export const ui_locales_m2 = {
"항목이 없음 — 빈 자리 오른쪽 클릭 또는 층 단추 옆 「+」 로 폴더 · 항목을 더함",
"No items — right-click an empty area or use + beside the layer buttons to add",
],
M02_TreeAddTop: ["맨 위에 폴더 · 항목 더하기", "Add folder or item at top"],
M02_TreeAddTop: ["맨 위에 폴더 · 항목 추가", "Add folder or item at top"],
M02_TreeEmpty: ["항목이 없음", "No items"],
M02_TreeNoHit: ["찾은 항목이 없음", "No matching item"],
M02_TreeAddFolder: ["폴더 더하기", "Add folder"],
M02_TreeAddItem: ["항목 더하기", "Add item"],
M02_TreeAddFolder: ["폴더 추가", "Add folder"],
M02_TreeAddItem: ["항목 추가", "Add item"],
M02_TreeRename: ["이름 바꾸기", "Rename"],
M02_TreeMove: ["옮기기", "Move"],
M02_TreeFolderName: ["폴더 이름", "Folder name"],
+5 -2
View File
@@ -3,17 +3,20 @@
inset: 0;
z-index: 1000;
display: flex;
align-items: center;
align-items: flex-start;
justify-content: center;
background-color: rgba(38, 17, 74, 0.35); /* 공용 확인 창(.ui-confirm)과 같이 뒤를 가림 */
}
/* 위에서 고정 거리 — 최대 높이일 때 가운데 자리에 윗변을 붙이고 아래로만 늘고 줆(찾기 칸이 안 움직임).
큰 창은 max-height 대신 --modal-max 를 줌. */
.ui-modal {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
width: min(720px, 92vw);
max-height: 80vh;
max-height: var(--modal-max, 80vh);
margin-top: calc((100vh - var(--modal-max, 80vh)) / 2);
padding: var(--spacing-16);
border-radius: var(--radius-xl);
border: 1px solid var(--color-border);