feat(z01): 서버가 주는 거르기 상자 — 표에 filters 가 오면 찾기 칸 옆에 고르기 상자를 세우고 고른 값만 key=value 로 보냄

- 이름·선택지·줄 수·기본값도, 어느 줄을 거를지 판정도 서버 것(화면은 판정 안 함 · 2026-09-17 브레인)
- 안 골랐으면 서버 default 를 보임 — 첫 선택지를 보이면 「전부」로 보이며 걸러진 목록이 뜸
- 고르면 1쪽부터 · 다른 표로 가면 고른 것 비움 · filters 없는 표(산림 등)는 상자 없음
- 화면 확인: 진짜 건설 공종 축 위에 가짜 filters 얹어 기본 「임도가 쓰는 것」 → 「전부」 전환 · 산림 상자 없음
- 시험: 기본값 = 서버 default(첫 선택지 아님) · 고르면 고른 값

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
This commit is contained in:
2026-09-17 13:42:00 +09:00
co-authored by Claude Opus 5
parent 0d56e727cb
commit b890fba04a
4 changed files with 69 additions and 3 deletions
+35 -2
View File
@@ -10,6 +10,7 @@
import {
createButton,
createInputField,
createSelectField,
el,
showConfirmDialog,
showToast,
@@ -21,6 +22,7 @@ import {
cellInfo,
clampPage,
columnTitle,
filterValue,
latestOnly,
nextSort,
noticeLines,
@@ -90,7 +92,12 @@ function stateText(info: CellInfo, column: MasterColumn): string {
export function buildRowsView(): RowsView {
let source: RowsSource | null = null;
const state = { page: 1, q: "", sort: null as SortOrder | null };
const state = {
page: 1,
q: "",
sort: null as SortOrder | null,
filters: {} as Record<string, string>,
};
let showHidden = false;
let last: MasterRows | null = null;
let selected: { index: number; key: string } | null = null;
@@ -137,9 +144,10 @@ export function buildRowsView(): RowsView {
const prev = pagerButton("Z01_MasterData_Prev", () => goTo(state.page - 1));
const next = pagerButton("Z01_MasterData_Next", () => goTo(state.page + 1));
const end = pagerButton("Z01_MasterData_Last", () => goTo(Infinity));
const filterBox = el("div", { className: "z01-master__toolbar" });
const toolbar = el("div", {
className: "z01-master__toolbar",
children: [search.root, hiddenLabel],
children: [search.root, filterBox, hiddenLabel],
});
const pager = el("div", {
className: "z01-master__pager",
@@ -180,6 +188,7 @@ export function buildRowsView(): RowsView {
size: PAGE_SIZE,
q: state.q,
sort: state.sort,
filters: state.filters,
});
} catch (error) {
grid.classList.remove("is-loading");
@@ -250,6 +259,28 @@ export function buildRowsView(): RowsView {
const outdated = outdatedBanner(meta.source);
banner.replaceChildren(...outdated.map((text) => el("p", { text: `⚠ ${text}` })));
banner.hidden = outdated.length === 0;
// 거르기 — 선택지·이름·줄 수·기본값도, 어느 줄을 거를지 판정도 서버 것(화면은 고른 값만 보냄).
filterBox.replaceChildren(
...(meta.filters ?? []).map(
(filter) =>
createSelectField({
label: filter.label,
options: filter.options.map((option) => ({
value: option.value,
text:
option.rows == null
? option.label
: `${option.label} (${option.rows.toLocaleString("ko-KR")})`,
})),
value: filterValue(filter, state.filters),
onChange: (value) => {
state.filters[filter.key] = value;
state.page = 1;
void load();
},
}).root,
),
);
const columns = shownColumns(meta.columns, showHidden);
const headRow = el("tr", { children: columns.map((column) => headCell(meta, column)) });
const body = el("tbody");
@@ -429,6 +460,8 @@ export function buildRowsView(): RowsView {
source = picked;
state.page = 1;
state.sort = null;
state.filters = {};
filterBox.replaceChildren();
title.textContent = picked.title;
titleKey.textContent = picked.key;
notice.hidden = true;