@wipco/sdui 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE +27 -0
- package/dist/chunk-4SU2KYPU.js +59 -0
- package/dist/chunk-56KKWLNI.js +186 -0
- package/dist/chunk-7Y4J7EOK.js +115 -0
- package/dist/chunk-APCXOCAO.js +110 -0
- package/dist/chunk-AROOQ5W6.js +119 -0
- package/dist/chunk-DGPRX4MU.js +251 -0
- package/dist/chunk-IL2KS4WS.js +25 -0
- package/dist/chunk-KADFN76X.js +541 -0
- package/dist/chunk-KBHI3IUR.js +40 -0
- package/dist/chunk-LZJTKVMO.js +767 -0
- package/dist/chunk-MOIU7MUA.js +282 -0
- package/dist/chunk-MSAV4J4I.js +77 -0
- package/dist/chunk-OOHHW4K4.js +159 -0
- package/dist/chunk-Q6SBH63R.js +190 -0
- package/dist/chunk-QZQDHSMD.js +78 -0
- package/dist/chunk-RH7ESUE6.js +309 -0
- package/dist/chunk-SEBPBUZR.js +260 -0
- package/dist/chunk-USKRM22W.js +39 -0
- package/dist/chunk-WNYWTFGW.js +61 -0
- package/dist/chunk-WVRCIVAD.js +72 -0
- package/dist/chunk-YJMD6MOE.js +404 -0
- package/dist/chunk-ZNITJ4KK.js +433 -0
- package/dist/chunk-ZQR3ULVH.js +193 -0
- package/dist/{chunk-QJHD6HVQ.js → chunk-ZUCI34QY.js} +106 -390
- package/dist/index.d.ts +306 -53
- package/dist/index.js +276 -720
- package/dist/meta.d.ts +8 -0
- package/dist/meta.js +147 -8
- package/dist/rich.js +3 -2
- package/dist/styles.css +246 -0
- package/dist/widgets/AutocompleteField.d.ts +96 -0
- package/dist/widgets/AutocompleteField.js +3 -0
- package/dist/widgets/ColumnBoard.d.ts +47 -0
- package/dist/widgets/ColumnBoard.js +2 -0
- package/dist/widgets/DiffView.d.ts +30 -0
- package/dist/widgets/DiffView.js +2 -0
- package/dist/widgets/Disclosure.d.ts +20 -0
- package/dist/widgets/Disclosure.js +2 -0
- package/dist/widgets/EventCalendar.d.ts +59 -0
- package/dist/widgets/EventCalendar.js +2 -0
- package/dist/widgets/ImageLightbox.d.ts +25 -0
- package/dist/widgets/ImageLightbox.js +2 -0
- package/dist/widgets/InlineEdit.d.ts +23 -0
- package/dist/widgets/InlineEdit.js +2 -0
- package/dist/widgets/JsonField.d.ts +24 -0
- package/dist/widgets/JsonField.js +2 -0
- package/dist/widgets/LiveClock.d.ts +21 -0
- package/dist/widgets/LiveClock.js +1 -0
- package/dist/widgets/OrgChart.d.ts +32 -0
- package/dist/widgets/OrgChart.js +2 -0
- package/dist/widgets/PeriodFilter.d.ts +48 -0
- package/dist/widgets/PeriodFilter.js +2 -0
- package/dist/widgets/PrintSheet.d.ts +23 -0
- package/dist/widgets/PrintSheet.js +2 -0
- package/dist/widgets/SearchableMultiPicker.d.ts +29 -0
- package/dist/widgets/SearchableMultiPicker.js +3 -0
- package/dist/widgets/SecureScreen.d.ts +25 -0
- package/dist/widgets/SecureScreen.js +2 -0
- package/dist/widgets/StackedMeter.d.ts +26 -0
- package/dist/widgets/StackedMeter.js +1 -0
- package/dist/widgets/Strip.d.ts +20 -0
- package/dist/widgets/Strip.js +1 -0
- package/dist/widgets/SummaryTable.d.ts +31 -0
- package/dist/widgets/SummaryTable.js +1 -0
- package/dist/widgets/TagMultiSelect.d.ts +25 -0
- package/dist/widgets/TagMultiSelect.js +2 -0
- package/dist/widgets/Timeline.d.ts +30 -0
- package/dist/widgets/Timeline.js +3 -0
- package/dist/widgets/TreePicker.d.ts +21 -0
- package/dist/widgets/TreePicker.js +3 -0
- package/dist/widgets/TreeView.d.ts +26 -0
- package/dist/widgets/TreeView.js +2 -0
- package/dist/widgets/index.d.ts +76 -0
- package/dist/widgets/index.js +69 -0
- package/package.json +93 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { DateRangePicker, DatePicker, HoistedStyle } from './chunk-YJMD6MOE.js';
|
|
2
|
+
export { DatePicker, DateRangePicker } from './chunk-YJMD6MOE.js';
|
|
3
|
+
import { searchValuesFromParams, splitMultiValue, includesLooseSearch, cascadingValuePaths, SearchToolbar, searchFieldKeys, Popover } from './chunk-KADFN76X.js';
|
|
4
|
+
export { SearchToolbar, cascadingValuePaths, cleanSearchValues, includesLooseSearch, joinMultiValue, normalizeSearchText, searchFieldKeys, searchValuesFromParams, splitMultiValue } from './chunk-KADFN76X.js';
|
|
5
|
+
import { Toast, Textarea, Tag, Tabs, Table, Switch, StatCard, Spinner, Select, ProgressBar, Pagination, Input, IconButton, Icon, Field, EmptyState, Checkbox, Card, Button, Badge, Alert, SegmentedControl, Dialog } from './chunk-ZUCI34QY.js';
|
|
6
|
+
export { Alert, Avatar, Badge, Breadcrumb, Button, Card, Checkbox, Dialog, EmptyState, Field, Icon, IconButton, Input, Pagination, ProgressBar, Radio, SegmentedControl, Select, SidebarNav, Spinner, StatCard, Switch, Table, Tabs, Tag, Textarea, Toast, Tooltip } from './chunk-ZUCI34QY.js';
|
|
3
7
|
import React, { createContext, useContext, useMemo, useState, useRef, useEffect, useCallback } from 'react';
|
|
4
8
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
5
9
|
|
|
@@ -229,6 +233,110 @@ function Show({ when, children }) {
|
|
|
229
233
|
function Divider({ style }) {
|
|
230
234
|
return /* @__PURE__ */ jsx("hr", { className: "wp-divider", style });
|
|
231
235
|
}
|
|
236
|
+
function toNumber(value) {
|
|
237
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
238
|
+
if (typeof value === "string") {
|
|
239
|
+
const plain = value.replace(/,/g, "").trim();
|
|
240
|
+
if (plain !== "" && /^-?\d+(\.\d+)?$/.test(plain)) return Number(plain);
|
|
241
|
+
}
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
function fillTemplate(template, row) {
|
|
245
|
+
return template.replace(
|
|
246
|
+
/\{([\w.-]+)\}/g,
|
|
247
|
+
(_match, key) => encodeURIComponent(String(row[key] ?? ""))
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
function formatDate(raw, format) {
|
|
251
|
+
const date = new Date(raw);
|
|
252
|
+
if (Number.isNaN(date.getTime())) return raw;
|
|
253
|
+
if (format === "datetime") return date.toLocaleString("ko-KR");
|
|
254
|
+
if (format === "time") return date.toLocaleTimeString("ko-KR");
|
|
255
|
+
return date.toLocaleDateString("ko-KR");
|
|
256
|
+
}
|
|
257
|
+
function cellRenderer(cell, key, dispatch) {
|
|
258
|
+
if (!cell || typeof cell !== "object" || typeof cell.type !== "string") return void 0;
|
|
259
|
+
switch (cell.type) {
|
|
260
|
+
case "badge":
|
|
261
|
+
return (row) => {
|
|
262
|
+
const value = String(row[key] ?? "");
|
|
263
|
+
if (!value) return null;
|
|
264
|
+
return /* @__PURE__ */ jsx(Badge, { tone: cell.toneMap?.[value] ?? "neutral", children: value });
|
|
265
|
+
};
|
|
266
|
+
case "tag":
|
|
267
|
+
return (row) => {
|
|
268
|
+
const value = String(row[key] ?? "");
|
|
269
|
+
if (!value) return null;
|
|
270
|
+
return /* @__PURE__ */ jsx(Tag, { color: cell.colorMap?.[value], filled: cell.filled, children: value });
|
|
271
|
+
};
|
|
272
|
+
case "link":
|
|
273
|
+
return (row) => {
|
|
274
|
+
const label = String(row[cell.labelKey ?? key] ?? "");
|
|
275
|
+
if (!label) return null;
|
|
276
|
+
const to = fillTemplate(cell.to ?? "", row);
|
|
277
|
+
return (
|
|
278
|
+
// Button이 아니라 <a>인 이유: 링크는 마우스 오른쪽 클릭·새 탭이 되어야 하는데
|
|
279
|
+
// ButtonProps에 href가 없다. 클래스는 DS의 링크 버튼(.wp-btn--link)을 그대로 쓴다.
|
|
280
|
+
/* @__PURE__ */ jsxs(
|
|
281
|
+
"a",
|
|
282
|
+
{
|
|
283
|
+
className: "wp-btn wp-btn--link",
|
|
284
|
+
href: to,
|
|
285
|
+
onClick: (e) => {
|
|
286
|
+
e.preventDefault();
|
|
287
|
+
e.stopPropagation();
|
|
288
|
+
dispatch({ action: "navigate", to });
|
|
289
|
+
},
|
|
290
|
+
children: [
|
|
291
|
+
cell.icon ? /* @__PURE__ */ jsx(Icon, { name: cell.icon, size: 14 }) : null,
|
|
292
|
+
label
|
|
293
|
+
]
|
|
294
|
+
}
|
|
295
|
+
)
|
|
296
|
+
);
|
|
297
|
+
};
|
|
298
|
+
case "progress":
|
|
299
|
+
return (row) => {
|
|
300
|
+
const value = toNumber(row[key]);
|
|
301
|
+
if (value === null) return String(row[key] ?? "");
|
|
302
|
+
const max = (cell.maxKey ? toNumber(row[cell.maxKey]) : null) ?? cell.max ?? 100;
|
|
303
|
+
return /* @__PURE__ */ jsx(ProgressBar, { value, max: max || 100, tone: cell.tone, showValue: cell.showValue, size: "sm" });
|
|
304
|
+
};
|
|
305
|
+
case "icon":
|
|
306
|
+
return (row) => {
|
|
307
|
+
const value = String(row[key] ?? "");
|
|
308
|
+
const name = cell.iconMap?.[value] ?? cell.icon;
|
|
309
|
+
if (!name) return value;
|
|
310
|
+
return (
|
|
311
|
+
// .wp-row 기본 간격(space-2)은 아이콘+글자 한 덩어리로 보이기엔 넓어 한 단계 좁힌다
|
|
312
|
+
/* @__PURE__ */ jsxs("span", { className: "wp-row", style: { gap: "var(--wp-space-1)" }, children: [
|
|
313
|
+
/* @__PURE__ */ jsx(Icon, { name, size: 14, title: cell.withLabel ? void 0 : value }),
|
|
314
|
+
cell.withLabel ? value : null
|
|
315
|
+
] })
|
|
316
|
+
);
|
|
317
|
+
};
|
|
318
|
+
case "number":
|
|
319
|
+
return (row) => {
|
|
320
|
+
const value = toNumber(row[key]);
|
|
321
|
+
if (value === null) return String(row[key] ?? "");
|
|
322
|
+
const digits = Number.isFinite(cell.digits) ? Math.max(0, Math.min(6, Number(cell.digits))) : 0;
|
|
323
|
+
const text = new Intl.NumberFormat("ko-KR", {
|
|
324
|
+
minimumFractionDigits: digits,
|
|
325
|
+
maximumFractionDigits: digits
|
|
326
|
+
}).format(value);
|
|
327
|
+
return `${cell.prefix ?? ""}${text}${cell.suffix ?? ""}`;
|
|
328
|
+
};
|
|
329
|
+
case "date":
|
|
330
|
+
return (row) => {
|
|
331
|
+
const raw = String(row[key] ?? "");
|
|
332
|
+
return raw ? formatDate(raw, cell.format) : "";
|
|
333
|
+
};
|
|
334
|
+
case "text":
|
|
335
|
+
return void 0;
|
|
336
|
+
default:
|
|
337
|
+
return void 0;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
232
340
|
var defaultFetcher = async (endpoint, params, signal) => {
|
|
233
341
|
const query = params.toString();
|
|
234
342
|
const url = `${endpoint}${query ? endpoint.includes("?") ? "&" : "?" : ""}${query}`;
|
|
@@ -289,78 +397,8 @@ function useRemoteSearchPage(endpoint, params) {
|
|
|
289
397
|
const reload = useCallback(() => setReloadKey((value) => value + 1), []);
|
|
290
398
|
return { page, isLoading, error, reload };
|
|
291
399
|
}
|
|
292
|
-
var HOST_STYLE = { position: "relative", display: "inline-flex" };
|
|
293
|
-
var PANEL_STYLE = {
|
|
294
|
-
position: "absolute",
|
|
295
|
-
top: "calc(100% + var(--wp-space-2))",
|
|
296
|
-
minWidth: "100%",
|
|
297
|
-
background: "var(--wp-surface-card)",
|
|
298
|
-
border: "1px solid var(--wp-border-subtle)",
|
|
299
|
-
borderRadius: "var(--wp-radius-lg)",
|
|
300
|
-
boxShadow: "var(--wp-shadow-lg)",
|
|
301
|
-
// 다이얼로그(--wp-z-dialog: 100)보다는 아래 — 모달 위로 삐져나오면 안 된다
|
|
302
|
-
zIndex: 90
|
|
303
|
-
};
|
|
304
|
-
var BODY_STYLE = {
|
|
305
|
-
maxHeight: "60vh",
|
|
306
|
-
overflow: "auto",
|
|
307
|
-
padding: "var(--wp-space-4)"
|
|
308
|
-
};
|
|
309
|
-
var FOOT_STYLE = {
|
|
310
|
-
display: "flex",
|
|
311
|
-
alignItems: "center",
|
|
312
|
-
justifyContent: "space-between",
|
|
313
|
-
gap: "var(--wp-space-2)",
|
|
314
|
-
padding: "var(--wp-space-3)",
|
|
315
|
-
borderTop: "1px solid var(--wp-divider)"
|
|
316
|
-
};
|
|
317
|
-
function Popover({
|
|
318
|
-
open,
|
|
319
|
-
onClose,
|
|
320
|
-
trigger,
|
|
321
|
-
footer,
|
|
322
|
-
alignLeft = true,
|
|
323
|
-
width,
|
|
324
|
-
children
|
|
325
|
-
}) {
|
|
326
|
-
const hostRef = useRef(null);
|
|
327
|
-
const closeRef = useRef(onClose);
|
|
328
|
-
closeRef.current = onClose;
|
|
329
|
-
useEffect(() => {
|
|
330
|
-
if (!open) return;
|
|
331
|
-
const closeOnOutside = (event) => {
|
|
332
|
-
if (event.target instanceof Node && hostRef.current?.contains(event.target)) return;
|
|
333
|
-
closeRef.current();
|
|
334
|
-
};
|
|
335
|
-
const closeOnEscape = (event) => {
|
|
336
|
-
if (event.key === "Escape") closeRef.current();
|
|
337
|
-
};
|
|
338
|
-
document.addEventListener("pointerdown", closeOnOutside);
|
|
339
|
-
document.addEventListener("keydown", closeOnEscape);
|
|
340
|
-
return () => {
|
|
341
|
-
document.removeEventListener("pointerdown", closeOnOutside);
|
|
342
|
-
document.removeEventListener("keydown", closeOnEscape);
|
|
343
|
-
};
|
|
344
|
-
}, [open]);
|
|
345
|
-
return /* @__PURE__ */ jsxs("div", { ref: hostRef, style: HOST_STYLE, children: [
|
|
346
|
-
trigger,
|
|
347
|
-
open ? /* @__PURE__ */ jsxs(
|
|
348
|
-
"div",
|
|
349
|
-
{
|
|
350
|
-
style: {
|
|
351
|
-
...PANEL_STYLE,
|
|
352
|
-
...alignLeft ? { left: 0 } : { right: 0 },
|
|
353
|
-
...width ? { width } : null
|
|
354
|
-
},
|
|
355
|
-
children: [
|
|
356
|
-
/* @__PURE__ */ jsx("div", { style: BODY_STYLE, children }),
|
|
357
|
-
footer ? /* @__PURE__ */ jsx("div", { style: FOOT_STYLE, children: footer }) : null
|
|
358
|
-
]
|
|
359
|
-
}
|
|
360
|
-
) : null
|
|
361
|
-
] });
|
|
362
|
-
}
|
|
363
400
|
var OVERSCAN = 10;
|
|
401
|
+
var CLIP_CSS = `.wp-table--clip tbody td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}`;
|
|
364
402
|
function ResultTable({
|
|
365
403
|
columns,
|
|
366
404
|
rows,
|
|
@@ -395,47 +433,25 @@ function ResultTable({
|
|
|
395
433
|
const table = /* @__PURE__ */ jsxs(
|
|
396
434
|
"table",
|
|
397
435
|
{
|
|
398
|
-
className:
|
|
436
|
+
className: `wp-table wp-table--hover${virtual ? " wp-table--sticky wp-table--clip" : ""}`,
|
|
399
437
|
style: virtual ? { tableLayout: "fixed" } : void 0,
|
|
400
438
|
children: [
|
|
401
439
|
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: columns.map((column) => /* @__PURE__ */ jsx(
|
|
402
440
|
"th",
|
|
403
441
|
{
|
|
404
|
-
style: {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
alignItems: "center",
|
|
418
|
-
gap: 3,
|
|
419
|
-
background: "none",
|
|
420
|
-
border: 0,
|
|
421
|
-
padding: 0,
|
|
422
|
-
cursor: "pointer",
|
|
423
|
-
color: "inherit",
|
|
424
|
-
font: "inherit",
|
|
425
|
-
letterSpacing: "inherit",
|
|
426
|
-
textTransform: "inherit"
|
|
427
|
-
},
|
|
428
|
-
children: [
|
|
429
|
-
column.header,
|
|
430
|
-
/* @__PURE__ */ jsx(
|
|
431
|
-
Icon,
|
|
432
|
-
{
|
|
433
|
-
name: sort && sort.key === column.key ? sort.dir === "asc" ? "chevron-up" : "chevron-down" : "chevrons-up-down",
|
|
434
|
-
size: 12
|
|
435
|
-
}
|
|
436
|
-
)
|
|
437
|
-
]
|
|
438
|
-
}
|
|
442
|
+
style: { width: column.width, textAlign: column.align === "right" ? "right" : void 0 },
|
|
443
|
+
children: column.sortable && onSort ? (
|
|
444
|
+
// 인라인 style로는 :hover를 표현할 수 없다 — 색 피드백은 .wp-table__sort:hover가 낸다
|
|
445
|
+
/* @__PURE__ */ jsxs("button", { type: "button", className: "wp-table__sort", onClick: () => onSort(column.key), children: [
|
|
446
|
+
column.header,
|
|
447
|
+
/* @__PURE__ */ jsx(
|
|
448
|
+
Icon,
|
|
449
|
+
{
|
|
450
|
+
name: sort && sort.key === column.key ? sort.dir === "asc" ? "chevron-up" : "chevron-down" : "chevrons-up-down",
|
|
451
|
+
size: 12
|
|
452
|
+
}
|
|
453
|
+
)
|
|
454
|
+
] })
|
|
439
455
|
) : column.header
|
|
440
456
|
},
|
|
441
457
|
column.key
|
|
@@ -451,24 +467,13 @@ function ResultTable({
|
|
|
451
467
|
ref: offset === 0 ? firstRowRef : void 0,
|
|
452
468
|
"aria-selected": selected || void 0,
|
|
453
469
|
onClick: onRowClick ? () => onRowClick(row) : void 0,
|
|
454
|
-
style: {
|
|
455
|
-
...onRowClick ? { cursor: "pointer" } : null,
|
|
456
|
-
...selected ? { background: "var(--wp-surface-selected)" } : null
|
|
457
|
-
},
|
|
470
|
+
style: onRowClick ? { cursor: "pointer" } : void 0,
|
|
458
471
|
children: columns.map((column) => {
|
|
459
472
|
const cls = [];
|
|
460
473
|
if (column.align === "right") cls.push("wp-table__num");
|
|
461
474
|
if (column.mono) cls.push("wp-table__mono");
|
|
462
475
|
if (column.strong) cls.push("wp-table__strong");
|
|
463
|
-
return /* @__PURE__ */ jsx(
|
|
464
|
-
"td",
|
|
465
|
-
{
|
|
466
|
-
className: cls.join(" ") || void 0,
|
|
467
|
-
style: virtual ? { whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } : void 0,
|
|
468
|
-
children: column.render ? column.render(row, index) : row[column.key]
|
|
469
|
-
},
|
|
470
|
-
column.key
|
|
471
|
-
);
|
|
476
|
+
return /* @__PURE__ */ jsx("td", { className: cls.join(" ") || void 0, children: column.render ? column.render(row, index) : row[column.key] }, column.key);
|
|
472
477
|
})
|
|
473
478
|
},
|
|
474
479
|
row.id ?? index
|
|
@@ -480,494 +485,40 @@ function ResultTable({
|
|
|
480
485
|
}
|
|
481
486
|
);
|
|
482
487
|
if (!virtual) return table;
|
|
483
|
-
return /* @__PURE__ */
|
|
488
|
+
return /* @__PURE__ */ jsxs("div", { ref: scrollRef, onScroll: handleScroll, style: { height, overflowY: "auto" }, children: [
|
|
489
|
+
/* @__PURE__ */ jsx(HoistedStyle, { id: "wp-table-clip", css: CLIP_CSS }),
|
|
490
|
+
table
|
|
491
|
+
] });
|
|
484
492
|
}
|
|
485
493
|
function ResultEmptyState({ title }) {
|
|
486
494
|
return /* @__PURE__ */ jsx(EmptyState, { icon: "scan-search", title: `${title}\uC774(\uAC00) \uC5C6\uC2B5\uB2C8\uB2E4`, tight: true, children: "\uAC80\uC0C9 \uC870\uAC74\uC744 \uBC14\uAFB8\uAC70\uB098 \uCD08\uAE30\uD654\uD574 \uBCF4\uC138\uC694." });
|
|
487
495
|
}
|
|
488
|
-
|
|
489
|
-
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
490
|
-
}
|
|
491
|
-
function joinMultiValue(values) {
|
|
492
|
-
return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean))).sort().join(",");
|
|
493
|
-
}
|
|
494
|
-
function normalizeSearchText(value) {
|
|
495
|
-
return value.toLowerCase().replace(/\s+/g, "");
|
|
496
|
-
}
|
|
497
|
-
function includesLooseSearch(text, keyword) {
|
|
498
|
-
const normalizedKeyword = normalizeSearchText(keyword);
|
|
499
|
-
return normalizedKeyword.length === 0 || normalizeSearchText(text).includes(normalizedKeyword);
|
|
500
|
-
}
|
|
501
|
-
function searchFieldKeys(fields) {
|
|
502
|
-
return fields.flatMap((field) => {
|
|
503
|
-
if (field.type === "dateRange") {
|
|
504
|
-
return [field.fromKey ?? `${field.key}From`, field.toKey ?? `${field.key}To`];
|
|
505
|
-
}
|
|
506
|
-
if (field.type === "numberRange") {
|
|
507
|
-
return [field.fromKey ?? `${field.key}Min`, field.toKey ?? `${field.key}Max`];
|
|
508
|
-
}
|
|
509
|
-
return [field.key];
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
function searchValuesFromParams(fields, params) {
|
|
513
|
-
const values = {};
|
|
514
|
-
for (const key of searchFieldKeys(fields)) {
|
|
515
|
-
const value = params.get(key);
|
|
516
|
-
if (value) values[key] = value;
|
|
517
|
-
}
|
|
518
|
-
return values;
|
|
519
|
-
}
|
|
520
|
-
function cleanSearchValues(fields, values) {
|
|
521
|
-
const allowed = new Set(searchFieldKeys(fields));
|
|
522
|
-
const fieldsByKey = new Map(fields.map((field) => [field.key, field]));
|
|
523
|
-
return Object.fromEntries(
|
|
524
|
-
Object.entries(values).map(([key, value]) => {
|
|
525
|
-
const field = fieldsByKey.get(key);
|
|
526
|
-
const cleaned = field?.type === "select" || field?.type === "cascading" ? joinMultiValue(splitMultiValue(value)) : value.trim();
|
|
527
|
-
return [key, cleaned];
|
|
528
|
-
}).filter(([key, value]) => allowed.has(key) && value)
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
|
-
function cascadingPathMap(options) {
|
|
532
|
-
const paths = /* @__PURE__ */ new Map();
|
|
533
|
-
const visit = (option, parents) => {
|
|
534
|
-
const path = [...parents, option];
|
|
535
|
-
paths.set(option.value, path);
|
|
536
|
-
option.children?.forEach((child) => visit(child, path));
|
|
537
|
-
};
|
|
538
|
-
options.forEach((option) => visit(option, []));
|
|
539
|
-
return paths;
|
|
540
|
-
}
|
|
541
|
-
function cascadingValuePaths(options) {
|
|
542
|
-
const map = /* @__PURE__ */ new Map();
|
|
543
|
-
for (const [value, path] of cascadingPathMap(options)) {
|
|
544
|
-
map.set(
|
|
545
|
-
value,
|
|
546
|
-
path.map((option) => option.value)
|
|
547
|
-
);
|
|
548
|
-
}
|
|
549
|
-
return map;
|
|
550
|
-
}
|
|
551
|
-
function cascadingPathLabel(pathMap, value) {
|
|
552
|
-
const path = pathMap.get(value);
|
|
553
|
-
return path?.length ? path.map((option) => option.label).join(" > ") : value;
|
|
554
|
-
}
|
|
555
|
-
function activeSearchCriteria(fields, values) {
|
|
556
|
-
const criteria = [];
|
|
557
|
-
for (const field of fields) {
|
|
558
|
-
if (field.type === "select") {
|
|
559
|
-
for (const value2 of splitMultiValue(values[field.key] ?? "")) {
|
|
560
|
-
const label = field.options.find((o) => o.value === value2)?.label ?? value2;
|
|
561
|
-
criteria.push({
|
|
562
|
-
key: `${field.key}:${value2}`,
|
|
563
|
-
label: `${field.label}: ${label}`,
|
|
564
|
-
clearKeys: [field.key],
|
|
565
|
-
valueKey: field.key,
|
|
566
|
-
value: value2
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
continue;
|
|
570
|
-
}
|
|
571
|
-
if (field.type === "cascading") {
|
|
572
|
-
const pathMap = cascadingPathMap(field.options);
|
|
573
|
-
for (const value2 of splitMultiValue(values[field.key] ?? "")) {
|
|
574
|
-
criteria.push({
|
|
575
|
-
key: `${field.key}:${value2}`,
|
|
576
|
-
label: `${field.label}: ${cascadingPathLabel(pathMap, value2)}`,
|
|
577
|
-
clearKeys: [field.key],
|
|
578
|
-
valueKey: field.key,
|
|
579
|
-
value: value2
|
|
580
|
-
});
|
|
581
|
-
}
|
|
582
|
-
continue;
|
|
583
|
-
}
|
|
584
|
-
if (field.type === "dateRange" || field.type === "numberRange") {
|
|
585
|
-
const fromKey = field.fromKey ?? `${field.key}${field.type === "dateRange" ? "From" : "Min"}`;
|
|
586
|
-
const toKey = field.toKey ?? `${field.key}${field.type === "dateRange" ? "To" : "Max"}`;
|
|
587
|
-
const from = values[fromKey];
|
|
588
|
-
const to = values[toKey];
|
|
589
|
-
if (from || to) {
|
|
590
|
-
criteria.push({
|
|
591
|
-
key: field.key,
|
|
592
|
-
label: `${field.label}: ${from ?? ""}~${to ?? ""}`,
|
|
593
|
-
clearKeys: [fromKey, toKey]
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
continue;
|
|
597
|
-
}
|
|
598
|
-
const value = values[field.key];
|
|
599
|
-
if (value) {
|
|
600
|
-
criteria.push({
|
|
601
|
-
key: field.key,
|
|
602
|
-
label: `${field.label}: ${value}`,
|
|
603
|
-
clearKeys: [field.key]
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
return criteria;
|
|
608
|
-
}
|
|
496
|
+
var EMPTY_FILTERS = [];
|
|
609
497
|
var EMPTY_FIELDS = [];
|
|
610
|
-
var
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
hideSearchInput = false
|
|
620
|
-
}) {
|
|
621
|
-
const [open, setOpen] = useState(false);
|
|
622
|
-
const [draft, setDraft] = useState(advancedValues);
|
|
623
|
-
const hasAdvanced = advancedFields.length > 0;
|
|
624
|
-
useEffect(() => {
|
|
625
|
-
if (!open) setDraft(advancedValues);
|
|
626
|
-
}, [advancedValues, open]);
|
|
627
|
-
const activeCriteria = useMemo(
|
|
628
|
-
() => activeSearchCriteria(advancedFields, advancedValues),
|
|
629
|
-
[advancedFields, advancedValues]
|
|
630
|
-
);
|
|
631
|
-
const submit = (event) => {
|
|
632
|
-
event.preventDefault();
|
|
633
|
-
onSearch(search.trim(), cleanSearchValues(advancedFields, advancedValues));
|
|
634
|
-
setOpen(false);
|
|
635
|
-
};
|
|
636
|
-
const applyAdvancedSearch = () => {
|
|
637
|
-
onSearch(search.trim(), cleanSearchValues(advancedFields, draft));
|
|
638
|
-
setOpen(false);
|
|
639
|
-
};
|
|
640
|
-
const resetAdvancedSearch = () => {
|
|
641
|
-
setDraft({});
|
|
642
|
-
onSearch(search.trim(), {});
|
|
643
|
-
setOpen(false);
|
|
644
|
-
};
|
|
645
|
-
const removeCriterion = (criterion) => {
|
|
646
|
-
const nextValues = { ...advancedValues };
|
|
647
|
-
if (criterion.valueKey && criterion.value !== void 0) {
|
|
648
|
-
const remaining = splitMultiValue(nextValues[criterion.valueKey] ?? "").filter(
|
|
649
|
-
(value) => value !== criterion.value
|
|
650
|
-
);
|
|
651
|
-
if (remaining.length > 0) nextValues[criterion.valueKey] = joinMultiValue(remaining);
|
|
652
|
-
else delete nextValues[criterion.valueKey];
|
|
653
|
-
} else {
|
|
654
|
-
criterion.clearKeys.forEach((key) => delete nextValues[key]);
|
|
655
|
-
}
|
|
656
|
-
setDraft(nextValues);
|
|
657
|
-
onSearch(search.trim(), cleanSearchValues(advancedFields, nextValues));
|
|
658
|
-
};
|
|
659
|
-
return /* @__PURE__ */ jsxs("form", { className: ["wp-row", className].filter(Boolean).join(" "), onSubmit: submit, children: [
|
|
660
|
-
activeCriteria.length > 0 ? /* @__PURE__ */ jsx("div", { className: "wp-row", style: { flexWrap: "wrap" }, children: activeCriteria.map((criterion) => /* @__PURE__ */ jsx(Tag, { onRemove: () => removeCriterion(criterion), children: criterion.label }, criterion.key)) }) : null,
|
|
661
|
-
!hideSearchInput ? /* @__PURE__ */ jsx(
|
|
662
|
-
Input,
|
|
663
|
-
{
|
|
664
|
-
icon: "search",
|
|
665
|
-
value: search,
|
|
666
|
-
onChange: (event) => onSearchChange(event.target.value),
|
|
667
|
-
placeholder
|
|
668
|
-
}
|
|
669
|
-
) : null,
|
|
670
|
-
hasAdvanced ? /* @__PURE__ */ jsx(
|
|
671
|
-
Popover,
|
|
672
|
-
{
|
|
673
|
-
open,
|
|
674
|
-
onClose: () => setOpen(false),
|
|
675
|
-
alignLeft: false,
|
|
676
|
-
width: "min(560px, 80vw)",
|
|
677
|
-
trigger: /* @__PURE__ */ jsx(
|
|
678
|
-
IconButton,
|
|
679
|
-
{
|
|
680
|
-
type: "button",
|
|
681
|
-
icon: "sliders-horizontal",
|
|
682
|
-
label: "\uC0C1\uC138\uAC80\uC0C9",
|
|
683
|
-
"aria-expanded": open,
|
|
684
|
-
style: open ? { background: "var(--wp-surface-active)" } : void 0,
|
|
685
|
-
onClick: () => setOpen((value) => !value)
|
|
686
|
-
}
|
|
687
|
-
),
|
|
688
|
-
footer: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
689
|
-
/* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", icon: "refresh-cw", onClick: resetAdvancedSearch, children: "\uCD08\uAE30\uD654" }),
|
|
690
|
-
/* @__PURE__ */ jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: applyAdvancedSearch, children: "\uC801\uC6A9" })
|
|
691
|
-
] }),
|
|
692
|
-
children: /* @__PURE__ */ jsx("div", { className: "wp-grid wp-grid--2", children: advancedFields.map((field) => /* @__PURE__ */ jsx(
|
|
693
|
-
SearchFieldInput,
|
|
694
|
-
{
|
|
695
|
-
field,
|
|
696
|
-
values: draft,
|
|
697
|
-
onChange: (patch) => setDraft((current) => ({ ...current, ...patch }))
|
|
698
|
-
},
|
|
699
|
-
field.key
|
|
700
|
-
)) })
|
|
701
|
-
}
|
|
702
|
-
) : null
|
|
703
|
-
] });
|
|
704
|
-
}
|
|
705
|
-
function SearchFieldInput({
|
|
706
|
-
field,
|
|
707
|
-
values,
|
|
708
|
-
onChange
|
|
709
|
-
}) {
|
|
710
|
-
if (field.type === "select") {
|
|
711
|
-
return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsx(MultiSelectInput, { field, values, onChange }) });
|
|
712
|
-
}
|
|
713
|
-
if (field.type === "cascading") {
|
|
714
|
-
return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsx(CascadingInput, { field, values, onChange }) });
|
|
715
|
-
}
|
|
716
|
-
if (field.type === "dateRange") {
|
|
717
|
-
const fromKey = field.fromKey ?? `${field.key}From`;
|
|
718
|
-
const toKey = field.toKey ?? `${field.key}To`;
|
|
719
|
-
return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsxs("div", { className: "wp-row", children: [
|
|
720
|
-
/* @__PURE__ */ jsx(
|
|
721
|
-
Input,
|
|
722
|
-
{
|
|
723
|
-
type: "date",
|
|
724
|
-
mono: true,
|
|
725
|
-
value: values[fromKey] ?? "",
|
|
726
|
-
onChange: (e) => onChange({ [fromKey]: e.target.value })
|
|
727
|
-
}
|
|
728
|
-
),
|
|
729
|
-
/* @__PURE__ */ jsx("span", { className: "wp-caption", children: "~" }),
|
|
730
|
-
/* @__PURE__ */ jsx(
|
|
731
|
-
Input,
|
|
732
|
-
{
|
|
733
|
-
type: "date",
|
|
734
|
-
mono: true,
|
|
735
|
-
value: values[toKey] ?? "",
|
|
736
|
-
onChange: (e) => onChange({ [toKey]: e.target.value })
|
|
737
|
-
}
|
|
738
|
-
)
|
|
739
|
-
] }) });
|
|
740
|
-
}
|
|
741
|
-
if (field.type === "numberRange") {
|
|
742
|
-
const fromKey = field.fromKey ?? `${field.key}Min`;
|
|
743
|
-
const toKey = field.toKey ?? `${field.key}Max`;
|
|
744
|
-
return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsxs("div", { className: "wp-row", children: [
|
|
745
|
-
/* @__PURE__ */ jsx(
|
|
746
|
-
Input,
|
|
747
|
-
{
|
|
748
|
-
type: "number",
|
|
749
|
-
mono: true,
|
|
750
|
-
placeholder: field.minPlaceholder ?? "\uCD5C\uC18C",
|
|
751
|
-
value: values[fromKey] ?? "",
|
|
752
|
-
onChange: (e) => onChange({ [fromKey]: e.target.value })
|
|
753
|
-
}
|
|
754
|
-
),
|
|
755
|
-
/* @__PURE__ */ jsx("span", { className: "wp-caption", children: "~" }),
|
|
756
|
-
/* @__PURE__ */ jsx(
|
|
757
|
-
Input,
|
|
758
|
-
{
|
|
759
|
-
type: "number",
|
|
760
|
-
mono: true,
|
|
761
|
-
placeholder: field.maxPlaceholder ?? "\uCD5C\uB300",
|
|
762
|
-
value: values[toKey] ?? "",
|
|
763
|
-
onChange: (e) => onChange({ [toKey]: e.target.value })
|
|
764
|
-
}
|
|
765
|
-
)
|
|
766
|
-
] }) });
|
|
767
|
-
}
|
|
768
|
-
return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsx(
|
|
769
|
-
Input,
|
|
770
|
-
{
|
|
771
|
-
placeholder: field.placeholder,
|
|
772
|
-
value: values[field.key] ?? "",
|
|
773
|
-
onChange: (e) => onChange({ [field.key]: e.target.value })
|
|
774
|
-
}
|
|
775
|
-
) });
|
|
776
|
-
}
|
|
777
|
-
function multiSelectLabel(field, values) {
|
|
778
|
-
if (values.length === 0) return field.allLabel ?? "\uC804\uCCB4";
|
|
779
|
-
const labels = values.map((value) => field.options.find((item) => item.value === value)?.label ?? value);
|
|
780
|
-
if (labels.length <= 2) return labels.join(", ");
|
|
781
|
-
return `${labels[0]} \uC678 ${labels.length - 1}`;
|
|
782
|
-
}
|
|
783
|
-
function MultiSelectInput({
|
|
784
|
-
field,
|
|
785
|
-
values,
|
|
786
|
-
onChange
|
|
787
|
-
}) {
|
|
788
|
-
const [open, setOpen] = useState(false);
|
|
789
|
-
const selected = splitMultiValue(values[field.key] ?? "");
|
|
790
|
-
const toggle = (value, checked) => {
|
|
791
|
-
const next = checked ? [...selected, value] : selected.filter((item) => item !== value);
|
|
792
|
-
onChange({ [field.key]: joinMultiValue(next) });
|
|
793
|
-
};
|
|
794
|
-
return /* @__PURE__ */ jsx(
|
|
795
|
-
Popover,
|
|
796
|
-
{
|
|
797
|
-
open,
|
|
798
|
-
onClose: () => setOpen(false),
|
|
799
|
-
trigger: /* @__PURE__ */ jsx(
|
|
800
|
-
Button,
|
|
801
|
-
{
|
|
802
|
-
type: "button",
|
|
803
|
-
variant: "secondary",
|
|
804
|
-
block: true,
|
|
805
|
-
iconEnd: "chevron-down",
|
|
806
|
-
onClick: () => setOpen((value) => !value),
|
|
807
|
-
children: multiSelectLabel(field, selected)
|
|
808
|
-
}
|
|
809
|
-
),
|
|
810
|
-
children: /* @__PURE__ */ jsx("div", { className: "wp-col", children: field.options.map((option) => /* @__PURE__ */ jsx(
|
|
811
|
-
Checkbox,
|
|
812
|
-
{
|
|
813
|
-
label: option.label,
|
|
814
|
-
checked: selected.includes(option.value),
|
|
815
|
-
onChange: (e) => toggle(option.value, e.target.checked)
|
|
816
|
-
},
|
|
817
|
-
option.value
|
|
818
|
-
)) })
|
|
819
|
-
}
|
|
498
|
+
var PAGE_SIZE_CHOICES = [10, 20, 50, 100];
|
|
499
|
+
var DEFAULT_VIEWS = [
|
|
500
|
+
{ value: "list", icon: "list", label: "\uBAA9\uB85D" },
|
|
501
|
+
{ value: "grid", icon: "layout-grid", label: "\uCE74\uB4DC" }
|
|
502
|
+
];
|
|
503
|
+
function normalizePageSizes(sizes) {
|
|
504
|
+
if (!Array.isArray(sizes)) return void 0;
|
|
505
|
+
const clean = [...new Set(sizes.map(Number).filter((n) => Number.isFinite(n) && n > 0).map(Math.floor))].sort(
|
|
506
|
+
(a, b) => a - b
|
|
820
507
|
);
|
|
508
|
+
return clean.length > 0 ? clean : void 0;
|
|
821
509
|
}
|
|
822
|
-
function
|
|
510
|
+
function resolveViewItems(views, disableGrid, disableList) {
|
|
511
|
+
const source = Array.isArray(views) && views.length > 0 ? views : DEFAULT_VIEWS.filter((v) => !(v.value === "grid" && disableGrid) && !(v.value === "list" && disableList));
|
|
823
512
|
const seen = /* @__PURE__ */ new Set();
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
}
|
|
830
|
-
function cascadingLevels(options, selectedPaths) {
|
|
831
|
-
const levels = [];
|
|
832
|
-
let currentOptions = options;
|
|
833
|
-
let depth = 0;
|
|
834
|
-
while (currentOptions.length > 0) {
|
|
835
|
-
levels.push(currentOptions);
|
|
836
|
-
const selectedAtDepth = uniqueCascadingOptions(
|
|
837
|
-
selectedPaths.map((path) => path[depth]).filter(
|
|
838
|
-
(option) => Boolean(option && currentOptions.some((current) => current.value === option.value))
|
|
839
|
-
)
|
|
840
|
-
);
|
|
841
|
-
const nextOptions = uniqueCascadingOptions(selectedAtDepth.flatMap((option) => option.children ?? []));
|
|
842
|
-
if (nextOptions.length === 0) break;
|
|
843
|
-
currentOptions = nextOptions;
|
|
844
|
-
depth += 1;
|
|
513
|
+
const items = [];
|
|
514
|
+
for (const view of source) {
|
|
515
|
+
const base = DEFAULT_VIEWS.find((d) => d.value === view?.value);
|
|
516
|
+
if (!base || seen.has(base.value)) continue;
|
|
517
|
+
seen.add(base.value);
|
|
518
|
+
items.push({ value: base.value, icon: view.icon ?? base.icon, label: view.label ?? base.label });
|
|
845
519
|
}
|
|
846
|
-
return
|
|
847
|
-
}
|
|
848
|
-
function selectedValuesForCascadingLevel(selectedPaths, depth, options) {
|
|
849
|
-
const optionValues = new Set(options.map((option) => option.value));
|
|
850
|
-
return Array.from(
|
|
851
|
-
new Set(
|
|
852
|
-
selectedPaths.map((path) => path[depth]?.value).filter((value) => Boolean(value) && optionValues.has(value))
|
|
853
|
-
)
|
|
854
|
-
);
|
|
855
|
-
}
|
|
856
|
-
function cascadingPathIncludes(pathMap, value, maybeAncestor) {
|
|
857
|
-
return pathMap.get(value)?.some((option) => option.value === maybeAncestor) ?? false;
|
|
858
|
-
}
|
|
859
|
-
function cascadingValuesOverlap(leftValue, rightValue, pathMap) {
|
|
860
|
-
return cascadingPathIncludes(pathMap, leftValue, rightValue) || cascadingPathIncludes(pathMap, rightValue, leftValue);
|
|
861
|
-
}
|
|
862
|
-
function cascadingLevelLabel(values, allLabel, pathMap, showPathLabels) {
|
|
863
|
-
if (values.length === 0) return allLabel;
|
|
864
|
-
const labels = values.map((value) => {
|
|
865
|
-
if (showPathLabels) return cascadingPathLabel(pathMap, value);
|
|
866
|
-
return pathMap.get(value)?.at(-1)?.label ?? value;
|
|
867
|
-
});
|
|
868
|
-
if (labels.length <= 2) return labels.join(", ");
|
|
869
|
-
return `${labels[0]} \uC678 ${labels.length - 1}`;
|
|
870
|
-
}
|
|
871
|
-
function cascadingOptionLabel(option, pathMap, showPathLabels) {
|
|
872
|
-
return showPathLabels ? cascadingPathLabel(pathMap, option.value) : option.label;
|
|
873
|
-
}
|
|
874
|
-
function CascadingInput({
|
|
875
|
-
field,
|
|
876
|
-
values,
|
|
877
|
-
onChange
|
|
878
|
-
}) {
|
|
879
|
-
const pathMap = useMemo(() => cascadingPathMap(field.options), [field.options]);
|
|
880
|
-
const selectedValues = useMemo(
|
|
881
|
-
() => splitMultiValue(values[field.key] ?? "").filter((value) => pathMap.has(value)),
|
|
882
|
-
[field.key, pathMap, values]
|
|
883
|
-
);
|
|
884
|
-
const selectedPaths = useMemo(
|
|
885
|
-
() => selectedValues.map((value) => pathMap.get(value)).filter((path) => Boolean(path)),
|
|
886
|
-
[pathMap, selectedValues]
|
|
887
|
-
);
|
|
888
|
-
const levels = useMemo(() => cascadingLevels(field.options, selectedPaths), [field.options, selectedPaths]);
|
|
889
|
-
const setSelectedValues = (nextValues) => {
|
|
890
|
-
onChange({ [field.key]: joinMultiValue(nextValues) });
|
|
891
|
-
};
|
|
892
|
-
const toggleOption = (option, nextChecked) => {
|
|
893
|
-
const withoutOverlap = selectedValues.filter(
|
|
894
|
-
(value) => !cascadingValuesOverlap(option.value, value, pathMap)
|
|
895
|
-
);
|
|
896
|
-
setSelectedValues(nextChecked ? [...withoutOverlap, option.value] : withoutOverlap);
|
|
897
|
-
};
|
|
898
|
-
const clearLevel = (depth) => {
|
|
899
|
-
if (depth === 0) {
|
|
900
|
-
setSelectedValues([]);
|
|
901
|
-
return;
|
|
902
|
-
}
|
|
903
|
-
const parents = selectedValuesForCascadingLevel(selectedPaths, depth - 1, levels[depth - 1] ?? []);
|
|
904
|
-
const rest = selectedValues.filter((value) => {
|
|
905
|
-
const parentValue = pathMap.get(value)?.[depth - 1]?.value;
|
|
906
|
-
return !parentValue || !parents.includes(parentValue);
|
|
907
|
-
});
|
|
908
|
-
setSelectedValues([...rest, ...parents]);
|
|
909
|
-
};
|
|
910
|
-
return /* @__PURE__ */ jsx("div", { className: "wp-col", children: levels.map((options, depth) => {
|
|
911
|
-
const levelValues = selectedValuesForCascadingLevel(selectedPaths, depth, options);
|
|
912
|
-
const showPathLabels = depth > 0 && selectedValuesForCascadingLevel(selectedPaths, depth - 1, levels[depth - 1] ?? []).length > 1;
|
|
913
|
-
return /* @__PURE__ */ jsx(
|
|
914
|
-
CascadingLevelSelect,
|
|
915
|
-
{
|
|
916
|
-
allLabel: depth === 0 ? field.allLabel ?? "\uC804\uCCB4" : "\uC804\uCCB4",
|
|
917
|
-
options,
|
|
918
|
-
pathMap,
|
|
919
|
-
selectedValues: levelValues,
|
|
920
|
-
showPathLabels,
|
|
921
|
-
onClear: () => clearLevel(depth),
|
|
922
|
-
onToggle: toggleOption
|
|
923
|
-
},
|
|
924
|
-
depth
|
|
925
|
-
);
|
|
926
|
-
}) });
|
|
520
|
+
return items.length > 0 ? items : [DEFAULT_VIEWS[0]];
|
|
927
521
|
}
|
|
928
|
-
function CascadingLevelSelect({
|
|
929
|
-
options,
|
|
930
|
-
pathMap,
|
|
931
|
-
selectedValues,
|
|
932
|
-
allLabel,
|
|
933
|
-
showPathLabels,
|
|
934
|
-
onClear,
|
|
935
|
-
onToggle
|
|
936
|
-
}) {
|
|
937
|
-
const [open, setOpen] = useState(false);
|
|
938
|
-
const selectedSet = new Set(selectedValues);
|
|
939
|
-
return /* @__PURE__ */ jsx(
|
|
940
|
-
Popover,
|
|
941
|
-
{
|
|
942
|
-
open,
|
|
943
|
-
onClose: () => setOpen(false),
|
|
944
|
-
trigger: /* @__PURE__ */ jsx(
|
|
945
|
-
Button,
|
|
946
|
-
{
|
|
947
|
-
type: "button",
|
|
948
|
-
variant: "secondary",
|
|
949
|
-
block: true,
|
|
950
|
-
iconEnd: "chevron-down",
|
|
951
|
-
onClick: () => setOpen((value) => !value),
|
|
952
|
-
children: cascadingLevelLabel(selectedValues, allLabel, pathMap, showPathLabels)
|
|
953
|
-
}
|
|
954
|
-
),
|
|
955
|
-
footer: /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "xs", icon: "refresh-cw", onClick: onClear, children: allLabel }),
|
|
956
|
-
children: /* @__PURE__ */ jsx("div", { className: "wp-col", style: { minWidth: 200 }, children: options.map((option) => /* @__PURE__ */ jsx(
|
|
957
|
-
Checkbox,
|
|
958
|
-
{
|
|
959
|
-
label: cascadingOptionLabel(option, pathMap, showPathLabels),
|
|
960
|
-
checked: selectedSet.has(option.value),
|
|
961
|
-
onChange: (e) => onToggle(option, e.target.checked)
|
|
962
|
-
},
|
|
963
|
-
option.value
|
|
964
|
-
)) })
|
|
965
|
-
}
|
|
966
|
-
);
|
|
967
|
-
}
|
|
968
|
-
var EMPTY_FILTERS = [];
|
|
969
|
-
var EMPTY_FIELDS2 = [];
|
|
970
|
-
var PAGE_SIZE_CHOICES = [10, 20, 50, 100];
|
|
971
522
|
function SearchResults({
|
|
972
523
|
item,
|
|
973
524
|
columns,
|
|
@@ -975,6 +526,8 @@ function SearchResults({
|
|
|
975
526
|
leftFilters = EMPTY_FILTERS,
|
|
976
527
|
rightFilters = EMPTY_FILTERS,
|
|
977
528
|
initialView = "list",
|
|
529
|
+
views,
|
|
530
|
+
pageSizes,
|
|
978
531
|
className,
|
|
979
532
|
searchParams,
|
|
980
533
|
onParamsChange,
|
|
@@ -986,26 +539,28 @@ function SearchResults({
|
|
|
986
539
|
headerActions,
|
|
987
540
|
isLoading = false,
|
|
988
541
|
hideHeader = false,
|
|
989
|
-
advancedSearchFields =
|
|
542
|
+
advancedSearchFields = EMPTY_FIELDS,
|
|
990
543
|
searchPlaceholder,
|
|
991
544
|
virtual
|
|
992
545
|
}) {
|
|
993
546
|
const hasActions = actions.length > 0;
|
|
547
|
+
const sizeChoices = normalizePageSizes(pageSizes) ?? PAGE_SIZE_CHOICES;
|
|
548
|
+
const defaultPageSize = sizeChoices[0];
|
|
994
549
|
const page = Number(searchParams.get("page")) || 1;
|
|
995
|
-
const pageSize = Number(searchParams.get("limit")) ||
|
|
996
|
-
const pageSizeChoices =
|
|
997
|
-
const
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
};
|
|
550
|
+
const pageSize = Number(searchParams.get("limit")) || defaultPageSize;
|
|
551
|
+
const pageSizeChoices = sizeChoices.includes(pageSize) ? sizeChoices : [...sizeChoices, pageSize].sort((a, b) => a - b);
|
|
552
|
+
const viewItems = useMemo(
|
|
553
|
+
() => resolveViewItems(views, disableGrid, disableList),
|
|
554
|
+
[views, disableGrid, disableList]
|
|
555
|
+
);
|
|
556
|
+
const applyViewConstraints = (candidate) => viewItems.some((v) => v.value === candidate) ? candidate : viewItems[0].value;
|
|
1003
557
|
const appliedSearch = searchParams.get("search") ?? "";
|
|
1004
558
|
const [view, setView] = useState(() => applyViewConstraints(initialView));
|
|
1005
559
|
const [search, setSearch] = useState(appliedSearch);
|
|
1006
560
|
const [sort, setSort] = useState(null);
|
|
1007
561
|
const [selected, setSelected] = useState(/* @__PURE__ */ new Set());
|
|
1008
562
|
const selectedRowsRef = useRef(/* @__PURE__ */ new Map());
|
|
563
|
+
const activeView = applyViewConstraints(view);
|
|
1009
564
|
useEffect(() => {
|
|
1010
565
|
setSearch(appliedSearch);
|
|
1011
566
|
}, [appliedSearch]);
|
|
@@ -1094,7 +649,7 @@ function SearchResults({
|
|
|
1094
649
|
const safePage = Math.min(Math.max(1, page), pageCount);
|
|
1095
650
|
const virtualThreshold = typeof virtual === "number" ? virtual : 150;
|
|
1096
651
|
const virtualEnabled = virtual === true || typeof virtual === "number";
|
|
1097
|
-
const virtualListActive = virtualEnabled &&
|
|
652
|
+
const virtualListActive = virtualEnabled && activeView === "list" && processed.length > virtualThreshold;
|
|
1098
653
|
const visibleItems = clientPaginated && !virtualListActive ? processed.slice((safePage - 1) * pageSize, safePage * pageSize) : processed;
|
|
1099
654
|
const visibleIds = visibleItems.map((row) => row.id);
|
|
1100
655
|
const allSelected = visibleIds.length > 0 && visibleIds.every((id) => selected.has(id));
|
|
@@ -1125,12 +680,12 @@ function SearchResults({
|
|
|
1125
680
|
{
|
|
1126
681
|
key: "__select",
|
|
1127
682
|
header: /* @__PURE__ */ jsx(
|
|
1128
|
-
|
|
683
|
+
Checkbox,
|
|
1129
684
|
{
|
|
1130
685
|
checked: allSelected,
|
|
1131
686
|
indeterminate: !allSelected && someSelected,
|
|
1132
|
-
label: `${title} \uC804\uCCB4 \uC120\uD0DD`,
|
|
1133
|
-
onChange: (
|
|
687
|
+
"aria-label": `${title} \uC804\uCCB4 \uC120\uD0DD`,
|
|
688
|
+
onChange: (e) => toggleSelectAll(e.target.checked)
|
|
1134
689
|
}
|
|
1135
690
|
),
|
|
1136
691
|
width: "36px",
|
|
@@ -1186,7 +741,14 @@ function SearchResults({
|
|
|
1186
741
|
placeholder: searchPlaceholder
|
|
1187
742
|
}
|
|
1188
743
|
),
|
|
1189
|
-
|
|
744
|
+
viewItems.length > 1 ? /* @__PURE__ */ jsx(
|
|
745
|
+
SegmentedControl,
|
|
746
|
+
{
|
|
747
|
+
items: viewItems,
|
|
748
|
+
value: activeView,
|
|
749
|
+
onChange: (next) => setView(applyViewConstraints(next))
|
|
750
|
+
}
|
|
751
|
+
) : null,
|
|
1190
752
|
headerActions
|
|
1191
753
|
] })
|
|
1192
754
|
] }) : null,
|
|
@@ -1211,21 +773,16 @@ function SearchResults({
|
|
|
1211
773
|
)) })
|
|
1212
774
|
] }) : null,
|
|
1213
775
|
hasActions && selectedItems.length > 0 ? (
|
|
1214
|
-
// 플로팅 벌크 액션 바 — 스크롤 위치와 무관하게 화면 하단 중앙에
|
|
1215
|
-
//
|
|
776
|
+
// 플로팅 벌크 액션 바 — 스크롤 위치와 무관하게 화면 하단 중앙에 뜬다.
|
|
777
|
+
// 생김새는 DS의 .wp-toolbar가 맡고, 여기서는 "화면 하단 중앙 고정"만 얹는다
|
|
778
|
+
// (--floating은 absolute라 스크롤을 따라오지 않는다).
|
|
1216
779
|
/* @__PURE__ */ jsxs(
|
|
1217
780
|
"div",
|
|
1218
781
|
{
|
|
1219
782
|
role: "toolbar",
|
|
783
|
+
className: "wp-toolbar",
|
|
1220
784
|
"aria-label": `${title} \uC120\uD0DD \uD56D\uBAA9 \uC77C\uAD04 \uC791\uC5C5`,
|
|
1221
785
|
style: {
|
|
1222
|
-
display: "flex",
|
|
1223
|
-
alignItems: "center",
|
|
1224
|
-
gap: 6,
|
|
1225
|
-
padding: 5,
|
|
1226
|
-
background: "var(--wp-surface-card)",
|
|
1227
|
-
border: "1px solid var(--wp-border-subtle)",
|
|
1228
|
-
borderRadius: "var(--wp-radius-md)",
|
|
1229
786
|
position: "fixed",
|
|
1230
787
|
bottom: "var(--wp-space-6)",
|
|
1231
788
|
left: "50%",
|
|
@@ -1241,7 +798,7 @@ function SearchResults({
|
|
|
1241
798
|
selectedItems.length,
|
|
1242
799
|
"\uAC74 \uC120\uD0DD\uB428"
|
|
1243
800
|
] }),
|
|
1244
|
-
/* @__PURE__ */ jsx(
|
|
801
|
+
/* @__PURE__ */ jsx("span", { className: "wp-toolbar__sep" }),
|
|
1245
802
|
actions.map((action) => /* @__PURE__ */ jsx(
|
|
1246
803
|
Button,
|
|
1247
804
|
{
|
|
@@ -1253,13 +810,13 @@ function SearchResults({
|
|
|
1253
810
|
},
|
|
1254
811
|
action.label
|
|
1255
812
|
)),
|
|
1256
|
-
/* @__PURE__ */ jsx(
|
|
813
|
+
/* @__PURE__ */ jsx("span", { className: "wp-toolbar__sep" }),
|
|
1257
814
|
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", icon: "x", onClick: () => setSelected(/* @__PURE__ */ new Set()), children: "\uC120\uD0DD \uD574\uC81C" })
|
|
1258
815
|
]
|
|
1259
816
|
}
|
|
1260
817
|
)
|
|
1261
818
|
) : null,
|
|
1262
|
-
isLoading ? /* @__PURE__ */ jsx("div", { className: "wp-row", style: { justifyContent: "center", padding: "var(--wp-space-8) 0" }, children: /* @__PURE__ */ jsx(Spinner, { size: "lg", label: `${title} \uBD88\uB7EC\uC624\uB294 \uC911` }) }) :
|
|
819
|
+
isLoading ? /* @__PURE__ */ jsx("div", { className: "wp-row", style: { justifyContent: "center", padding: "var(--wp-space-8) 0" }, children: /* @__PURE__ */ jsx(Spinner, { size: "lg", label: `${title} \uBD88\uB7EC\uC624\uB294 \uC911` }) }) : activeView === "list" ? /* @__PURE__ */ jsx(
|
|
1263
820
|
ResultTable,
|
|
1264
821
|
{
|
|
1265
822
|
columns: tableColumns,
|
|
@@ -1283,8 +840,11 @@ function SearchResults({
|
|
|
1283
840
|
options: pageSizeChoices.map((size) => ({ value: String(size), label: `${size}\uAC74` })),
|
|
1284
841
|
value: String(pageSize),
|
|
1285
842
|
onChange: (e) => (
|
|
1286
|
-
// 기본값
|
|
1287
|
-
updateParams({
|
|
843
|
+
// 기본값(첫 후보)은 파라미터를 지워 상태를 깔끔하게 유지하고, 페이지는 1로 되돌린다
|
|
844
|
+
updateParams({
|
|
845
|
+
limit: e.target.value === String(defaultPageSize) ? null : e.target.value,
|
|
846
|
+
page: null
|
|
847
|
+
})
|
|
1288
848
|
)
|
|
1289
849
|
}
|
|
1290
850
|
)
|
|
@@ -1316,65 +876,6 @@ function formatCell(value) {
|
|
|
1316
876
|
if (value instanceof Date) return value.toLocaleString("ko-KR");
|
|
1317
877
|
return value;
|
|
1318
878
|
}
|
|
1319
|
-
function ToolbarSeparator() {
|
|
1320
|
-
return /* @__PURE__ */ jsx("span", { style: { width: 1, height: 20, background: "var(--wp-border-subtle)", flex: "none" } });
|
|
1321
|
-
}
|
|
1322
|
-
function SelectCheckbox({
|
|
1323
|
-
checked,
|
|
1324
|
-
indeterminate,
|
|
1325
|
-
label,
|
|
1326
|
-
onChange
|
|
1327
|
-
}) {
|
|
1328
|
-
const ref = useRef(null);
|
|
1329
|
-
useEffect(() => {
|
|
1330
|
-
if (ref.current) ref.current.indeterminate = indeterminate;
|
|
1331
|
-
}, [indeterminate]);
|
|
1332
|
-
return /* @__PURE__ */ jsxs("label", { className: "wp-check", children: [
|
|
1333
|
-
/* @__PURE__ */ jsx(
|
|
1334
|
-
"input",
|
|
1335
|
-
{
|
|
1336
|
-
ref,
|
|
1337
|
-
type: "checkbox",
|
|
1338
|
-
checked,
|
|
1339
|
-
"aria-label": label,
|
|
1340
|
-
onChange: (e) => onChange(e.target.checked)
|
|
1341
|
-
}
|
|
1342
|
-
),
|
|
1343
|
-
/* @__PURE__ */ jsx(
|
|
1344
|
-
"span",
|
|
1345
|
-
{
|
|
1346
|
-
className: "wp-check__box",
|
|
1347
|
-
style: indeterminate && !checked ? {
|
|
1348
|
-
background: "var(--wp-action-primary-bg)",
|
|
1349
|
-
borderColor: "var(--wp-action-primary-bg)",
|
|
1350
|
-
color: "var(--wp-white)"
|
|
1351
|
-
} : void 0,
|
|
1352
|
-
children: /* @__PURE__ */ jsx(Icon, { name: indeterminate && !checked ? "minus" : "check", size: 12, strokeWidth: 2.5 })
|
|
1353
|
-
}
|
|
1354
|
-
)
|
|
1355
|
-
] });
|
|
1356
|
-
}
|
|
1357
|
-
function ViewToggle({ value, onChange }) {
|
|
1358
|
-
const items = [
|
|
1359
|
-
{ value: "list", icon: "list", label: "\uBAA9\uB85D" },
|
|
1360
|
-
{ value: "grid", icon: "layout-grid", label: "\uCE74\uB4DC" }
|
|
1361
|
-
];
|
|
1362
|
-
return /* @__PURE__ */ jsx("div", { className: "wp-seg", role: "tablist", children: items.map((tab) => /* @__PURE__ */ jsxs(
|
|
1363
|
-
"button",
|
|
1364
|
-
{
|
|
1365
|
-
type: "button",
|
|
1366
|
-
role: "tab",
|
|
1367
|
-
className: "wp-seg__item",
|
|
1368
|
-
"aria-selected": tab.value === value,
|
|
1369
|
-
onClick: () => onChange(tab.value),
|
|
1370
|
-
children: [
|
|
1371
|
-
/* @__PURE__ */ jsx(Icon, { name: tab.icon, size: 14 }),
|
|
1372
|
-
tab.label
|
|
1373
|
-
]
|
|
1374
|
-
},
|
|
1375
|
-
tab.value
|
|
1376
|
-
)) });
|
|
1377
|
-
}
|
|
1378
879
|
function resolveRowPart(part, item) {
|
|
1379
880
|
return typeof part === "function" ? part(item) : part;
|
|
1380
881
|
}
|
|
@@ -1434,6 +935,7 @@ function FilterMultiSelect({
|
|
|
1434
935
|
type: "button",
|
|
1435
936
|
size: "sm",
|
|
1436
937
|
variant: values.length > 0 ? "primary" : "secondary",
|
|
938
|
+
icon: "funnel",
|
|
1437
939
|
iconEnd: "chevron-down",
|
|
1438
940
|
onClick: () => setOpen((value) => !value),
|
|
1439
941
|
children: label
|
|
@@ -1457,7 +959,7 @@ function FilterMultiSelect({
|
|
|
1457
959
|
function toList(action) {
|
|
1458
960
|
return Array.isArray(action) ? action : [action];
|
|
1459
961
|
}
|
|
1460
|
-
function mapColumns(columns) {
|
|
962
|
+
function mapColumns(columns, dispatch) {
|
|
1461
963
|
return (columns ?? []).map((column) => ({
|
|
1462
964
|
key: column.key,
|
|
1463
965
|
title: column.title,
|
|
@@ -1466,14 +968,29 @@ function mapColumns(columns) {
|
|
|
1466
968
|
ratio: column.ratio,
|
|
1467
969
|
mono: column.mono,
|
|
1468
970
|
strong: column.strong,
|
|
1469
|
-
render: column.cell
|
|
1470
|
-
const value = String(row[column.key] ?? "");
|
|
1471
|
-
if (!value) return null;
|
|
1472
|
-
return /* @__PURE__ */ jsx(Badge, { tone: column.cell?.toneMap?.[value] ?? "neutral", children: value });
|
|
1473
|
-
} : void 0
|
|
971
|
+
render: cellRenderer(column.cell, column.key, dispatch)
|
|
1474
972
|
}));
|
|
1475
973
|
}
|
|
1476
|
-
function
|
|
974
|
+
function matchesCondition(condition, row) {
|
|
975
|
+
if (!condition || typeof condition.key !== "string") return true;
|
|
976
|
+
const value = row[condition.key];
|
|
977
|
+
if (condition.equals !== void 0) return String(value ?? "") === String(condition.equals);
|
|
978
|
+
if (Array.isArray(condition.in)) return condition.in.some((v) => String(v) === String(value ?? ""));
|
|
979
|
+
if (condition.notEmpty === false) return true;
|
|
980
|
+
return value !== void 0 && value !== null && String(value) !== "";
|
|
981
|
+
}
|
|
982
|
+
function mapCardRows(rows, dispatch) {
|
|
983
|
+
return rows?.map((row) => {
|
|
984
|
+
const render = cellRenderer(row.cell, row.valueKey, dispatch);
|
|
985
|
+
return {
|
|
986
|
+
icon: row.icon ? /* @__PURE__ */ jsx(Icon, { name: row.icon, size: 14 }) : void 0,
|
|
987
|
+
label: row.label,
|
|
988
|
+
value: render ?? ((item) => String(item[row.valueKey] ?? "")),
|
|
989
|
+
show: row.showWhen ? (item) => matchesCondition(row.showWhen, item) : void 0
|
|
990
|
+
};
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
function mapGridCard(gridCard, dispatch) {
|
|
1477
994
|
if (!gridCard) return void 0;
|
|
1478
995
|
return {
|
|
1479
996
|
title: (row) => String(row[gridCard.titleKey] ?? ""),
|
|
@@ -1483,12 +1000,34 @@ function mapGridCard(gridCard) {
|
|
|
1483
1000
|
if (!value) return null;
|
|
1484
1001
|
return { text: value, variant: gridCard.badge.toneMap?.[value] ?? "neutral" };
|
|
1485
1002
|
} : void 0,
|
|
1486
|
-
rows: gridCard.rows
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1003
|
+
rows: mapCardRows(gridCard.rows, dispatch),
|
|
1004
|
+
leftRows: mapCardRows(gridCard.leftRows, dispatch),
|
|
1005
|
+
rightRows: mapCardRows(gridCard.rightRows, dispatch),
|
|
1006
|
+
note: gridCard.noteKey ? (row) => String(row[gridCard.noteKey] ?? "") || null : void 0,
|
|
1007
|
+
actions: gridCard.actions?.length ? (row) => (
|
|
1008
|
+
// 카드 전체가 클릭 대상(rowClick)일 수 있으므로 버튼 클릭은 여기서 전파를 끊는다
|
|
1009
|
+
/* @__PURE__ */ jsx("span", { className: "wp-row", onClick: (e) => e.stopPropagation(), children: gridCard.actions.map((action) => /* @__PURE__ */ jsx(
|
|
1010
|
+
Button,
|
|
1011
|
+
{
|
|
1012
|
+
size: "xs",
|
|
1013
|
+
icon: action.icon,
|
|
1014
|
+
variant: action.variant === "destructive" ? "danger" : action.variant === "outline" ? "secondary" : "ghost",
|
|
1015
|
+
onClick: () => toList(action.run).forEach((a) => dispatch(a, row)),
|
|
1016
|
+
children: action.label
|
|
1017
|
+
},
|
|
1018
|
+
action.label
|
|
1019
|
+
)) })
|
|
1020
|
+
) : void 0
|
|
1490
1021
|
};
|
|
1491
1022
|
}
|
|
1023
|
+
function mapViews(views, gridCard, disableGrid, disableList) {
|
|
1024
|
+
if (!Array.isArray(views) || views.length === 0) return void 0;
|
|
1025
|
+
const gridOff = disableGrid ?? !gridCard;
|
|
1026
|
+
const kept = views.filter(
|
|
1027
|
+
(view) => !(view?.value === "grid" && gridOff) && !(view?.value === "list" && disableList === true)
|
|
1028
|
+
);
|
|
1029
|
+
return kept.length > 0 ? kept : void 0;
|
|
1030
|
+
}
|
|
1492
1031
|
function mapActions(actions, dispatch) {
|
|
1493
1032
|
return (actions ?? []).map((action) => ({
|
|
1494
1033
|
label: action.label,
|
|
@@ -1510,6 +1049,8 @@ function SduiSearchResults({
|
|
|
1510
1049
|
gridCard,
|
|
1511
1050
|
actions,
|
|
1512
1051
|
initialView,
|
|
1052
|
+
views,
|
|
1053
|
+
pageSizes,
|
|
1513
1054
|
disableGrid,
|
|
1514
1055
|
disableList,
|
|
1515
1056
|
searchPlaceholder,
|
|
@@ -1525,12 +1066,14 @@ function SduiSearchResults({
|
|
|
1525
1066
|
{
|
|
1526
1067
|
title,
|
|
1527
1068
|
item: { data: rows, total: rows.length },
|
|
1528
|
-
columns: mapColumns(columns),
|
|
1529
|
-
gridConfig: mapGridCard(gridCard),
|
|
1069
|
+
columns: mapColumns(columns, dispatch),
|
|
1070
|
+
gridConfig: mapGridCard(gridCard, dispatch),
|
|
1530
1071
|
leftFilters,
|
|
1531
1072
|
rightFilters,
|
|
1532
1073
|
actions: mapActions(actions, dispatch),
|
|
1533
1074
|
initialView,
|
|
1075
|
+
views: mapViews(views, gridCard, disableGrid, disableList),
|
|
1076
|
+
pageSizes,
|
|
1534
1077
|
disableGrid: disableGrid ?? !gridCard,
|
|
1535
1078
|
disableList,
|
|
1536
1079
|
advancedSearchFields: fields,
|
|
@@ -1553,6 +1096,8 @@ function RemoteSearchResults({
|
|
|
1553
1096
|
gridCard,
|
|
1554
1097
|
actions,
|
|
1555
1098
|
initialView,
|
|
1099
|
+
views,
|
|
1100
|
+
pageSizes,
|
|
1556
1101
|
disableGrid,
|
|
1557
1102
|
disableList,
|
|
1558
1103
|
searchPlaceholder,
|
|
@@ -1567,12 +1112,13 @@ function RemoteSearchResults({
|
|
|
1567
1112
|
initial.set("limit", String(pageSize));
|
|
1568
1113
|
return initial;
|
|
1569
1114
|
});
|
|
1115
|
+
const defaultPageSize = normalizePageSizes(pageSizes)?.[0] ?? 10;
|
|
1570
1116
|
const query = useMemo(() => {
|
|
1571
1117
|
const next = new URLSearchParams(params);
|
|
1572
1118
|
if (!next.has("page")) next.set("page", "1");
|
|
1573
|
-
if (!next.has("limit")) next.set("limit",
|
|
1119
|
+
if (!next.has("limit")) next.set("limit", String(defaultPageSize));
|
|
1574
1120
|
return next;
|
|
1575
|
-
}, [params]);
|
|
1121
|
+
}, [params, defaultPageSize]);
|
|
1576
1122
|
const { page, isLoading, error, reload } = useRemoteSearchPage(endpoint, query);
|
|
1577
1123
|
if (error && page.data.length === 0) {
|
|
1578
1124
|
return /* @__PURE__ */ jsx(
|
|
@@ -1590,12 +1136,14 @@ function RemoteSearchResults({
|
|
|
1590
1136
|
{
|
|
1591
1137
|
title,
|
|
1592
1138
|
item: page,
|
|
1593
|
-
columns: mapColumns(columns),
|
|
1594
|
-
gridConfig: mapGridCard(gridCard),
|
|
1139
|
+
columns: mapColumns(columns, dispatch),
|
|
1140
|
+
gridConfig: mapGridCard(gridCard, dispatch),
|
|
1595
1141
|
leftFilters,
|
|
1596
1142
|
rightFilters,
|
|
1597
1143
|
actions: mapActions(actions, dispatch),
|
|
1598
1144
|
initialView,
|
|
1145
|
+
views: mapViews(views, gridCard, disableGrid, disableList),
|
|
1146
|
+
pageSizes,
|
|
1599
1147
|
disableGrid: disableGrid ?? !gridCard,
|
|
1600
1148
|
disableList,
|
|
1601
1149
|
advancedSearchFields: fields,
|
|
@@ -1768,6 +1316,13 @@ var defaultRegistry = {
|
|
|
1768
1316
|
// 권한 게이트 — 판정은 SduiRenderer의 can 프롭(permission.tsx)에 위임한다.
|
|
1769
1317
|
// 주입 전까지는 통과시키되 console.error를 한 번 낸다(근거는 permission.tsx의 MISSING 주석).
|
|
1770
1318
|
Can,
|
|
1319
|
+
// 0.3.0에서 이관한 것들은 여기 **없다.**
|
|
1320
|
+
// - DS 6종(Avatar·Breadcrumb·Radio·SegmentedControl·SidebarNav·Tooltip)은
|
|
1321
|
+
// components.tsx에 있고 export만 한다. 기본 어휘를 넓히는 것은 별개 결정이라,
|
|
1322
|
+
// 이름을 올리려면 `@wipco/sdui/meta`의 메타데이터를 같은 커밋에서 채워야 한다
|
|
1323
|
+
// (SduiComponentName이 그 Record의 키라 빠지면 컴파일이 막힌다).
|
|
1324
|
+
// - 표시 위젯 20종은 `widgetComponents` 묶음으로 나가 있다. 정적 import로 core에
|
|
1325
|
+
// 붙지 않게 하려는 것이고 이유는 위 Chart·MapPreview와 같다(widgets/index.ts 참고).
|
|
1771
1326
|
// 검색+목록 화면. 스펙 이름은 ERP 어휘를 그대로 쓰고, 구현은 JSON 전용 어댑터를 가리킨다
|
|
1772
1327
|
// (SearchResults 자체는 함수 프롭을 받는 타입드 컴포넌트라 스펙에서 직접 못 쓴다).
|
|
1773
1328
|
SearchResults: SduiSearchResults,
|
|
@@ -1995,7 +1550,7 @@ function eventValue(arg) {
|
|
|
1995
1550
|
}
|
|
1996
1551
|
return arg;
|
|
1997
1552
|
}
|
|
1998
|
-
function
|
|
1553
|
+
function toNumber2(v) {
|
|
1999
1554
|
if (typeof v === "number") return v;
|
|
2000
1555
|
if (typeof v === "string") {
|
|
2001
1556
|
const n = Number(v.replace(/,/g, ""));
|
|
@@ -2040,9 +1595,9 @@ function ruleError(rule, value, values, label, customValidators) {
|
|
|
2040
1595
|
case "required":
|
|
2041
1596
|
return empty || value === false ? rule.message ?? `${label}\uC744(\uB97C) \uC785\uB825\uD558\uC138\uC694` : null;
|
|
2042
1597
|
case "min":
|
|
2043
|
-
return !empty &&
|
|
1598
|
+
return !empty && toNumber2(value) < rule.value ? rule.message ?? `${label}\uC740(\uB294) ${rule.value} \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4` : null;
|
|
2044
1599
|
case "max":
|
|
2045
|
-
return !empty &&
|
|
1600
|
+
return !empty && toNumber2(value) > rule.value ? rule.message ?? `${label}\uC740(\uB294) ${rule.value} \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4` : null;
|
|
2046
1601
|
case "minLength":
|
|
2047
1602
|
return !empty && String(value).length < rule.value ? rule.message ?? `${label}\uC740(\uB294) ${rule.value}\uC790 \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4` : null;
|
|
2048
1603
|
case "maxLength":
|
|
@@ -2194,6 +1749,7 @@ function SduiRenderer({
|
|
|
2194
1749
|
registry = defaultRegistry,
|
|
2195
1750
|
adapter,
|
|
2196
1751
|
syncUrlState = true,
|
|
1752
|
+
can,
|
|
2197
1753
|
onCustomAction,
|
|
2198
1754
|
customValidators
|
|
2199
1755
|
}) {
|
|
@@ -2338,8 +1894,8 @@ function SduiRenderer({
|
|
|
2338
1894
|
const resolved = args.map((arg) => resolveValue(arg, scope));
|
|
2339
1895
|
const nums = (v) => {
|
|
2340
1896
|
if (Array.isArray(v))
|
|
2341
|
-
return v.map((row) => field && isPlainObject2(row) ?
|
|
2342
|
-
return [
|
|
1897
|
+
return v.map((row) => field && isPlainObject2(row) ? toNumber2(row[field]) : toNumber2(row));
|
|
1898
|
+
return [toNumber2(v)];
|
|
2343
1899
|
};
|
|
2344
1900
|
switch (op) {
|
|
2345
1901
|
case "sum":
|
|
@@ -2347,17 +1903,17 @@ function SduiRenderer({
|
|
|
2347
1903
|
case "count":
|
|
2348
1904
|
return resolved.reduce((a, v) => a + (Array.isArray(v) ? v.length : v == null ? 0 : 1), 0);
|
|
2349
1905
|
case "sub": {
|
|
2350
|
-
const list = resolved.map((v) =>
|
|
1906
|
+
const list = resolved.map((v) => toNumber2(v));
|
|
2351
1907
|
return list.length === 0 ? 0 : list.reduce((a, b) => a - b);
|
|
2352
1908
|
}
|
|
2353
1909
|
case "mul":
|
|
2354
|
-
return resolved.map((v) =>
|
|
1910
|
+
return resolved.map((v) => toNumber2(v)).reduce((a, b) => a * b, 1);
|
|
2355
1911
|
case "div": {
|
|
2356
|
-
const [a, b] = resolved.map((v) =>
|
|
1912
|
+
const [a, b] = resolved.map((v) => toNumber2(v));
|
|
2357
1913
|
return b === 0 ? 0 : a / b;
|
|
2358
1914
|
}
|
|
2359
1915
|
case "percent": {
|
|
2360
|
-
const [a, b] = resolved.map((v) =>
|
|
1916
|
+
const [a, b] = resolved.map((v) => toNumber2(v));
|
|
2361
1917
|
return b === 0 ? 0 : Math.round(a / b * 1e3) / 10;
|
|
2362
1918
|
}
|
|
2363
1919
|
case "concat":
|
|
@@ -2370,13 +1926,13 @@ function SduiRenderer({
|
|
|
2370
1926
|
}
|
|
2371
1927
|
// 수량 비교 — 정원 초과 차단(Button.disabled), 임계값 경고(Show.when) 등
|
|
2372
1928
|
case "gt":
|
|
2373
|
-
return
|
|
1929
|
+
return toNumber2(resolved[0]) > toNumber2(resolved[1]);
|
|
2374
1930
|
case "gte":
|
|
2375
|
-
return
|
|
1931
|
+
return toNumber2(resolved[0]) >= toNumber2(resolved[1]);
|
|
2376
1932
|
case "lt":
|
|
2377
|
-
return
|
|
1933
|
+
return toNumber2(resolved[0]) < toNumber2(resolved[1]);
|
|
2378
1934
|
case "lte":
|
|
2379
|
-
return
|
|
1935
|
+
return toNumber2(resolved[0]) <= toNumber2(resolved[1]);
|
|
2380
1936
|
/** 부정 — 조건을 뒤집어 "없을 때만" 렌더링하는 데 쓴다 */
|
|
2381
1937
|
case "not":
|
|
2382
1938
|
return !resolved[0];
|
|
@@ -2570,14 +2126,14 @@ function SduiRenderer({
|
|
|
2570
2126
|
}),
|
|
2571
2127
|
[state, setEntries]
|
|
2572
2128
|
);
|
|
2573
|
-
return /* @__PURE__ */ jsx(SduiDispatchContext.Provider, { value: dispatch, children: /* @__PURE__ */ jsx(SduiRuntimeContext.Provider, { value: runtime, children: /* @__PURE__ */ jsxs("div", { className: "sdui-root", children: [
|
|
2129
|
+
return /* @__PURE__ */ jsx(SduiDispatchContext.Provider, { value: dispatch, children: /* @__PURE__ */ jsx(SduiRuntimeContext.Provider, { value: runtime, children: /* @__PURE__ */ jsx(SduiPermissionProvider, { can, children: /* @__PURE__ */ jsxs("div", { className: "sdui-root", children: [
|
|
2574
2130
|
page.title || page.subtitle ? /* @__PURE__ */ jsx("div", { className: "wp-page-head", children: /* @__PURE__ */ jsxs("div", { className: "wp-page-head__text", children: [
|
|
2575
2131
|
page.title ? /* @__PURE__ */ jsx("h1", { className: "wp-page-head__title", children: page.title }) : null,
|
|
2576
2132
|
page.subtitle ? /* @__PURE__ */ jsx("div", { className: "wp-page-head__sub", children: page.subtitle }) : null
|
|
2577
2133
|
] }) }) : null,
|
|
2578
2134
|
/* @__PURE__ */ jsx("div", { className: "wp-col", children: Array.isArray(page.body) ? page.body.map((child, i) => renderChild(child, i, EMPTY_SCOPE, engine)) : /* @__PURE__ */ jsx(SpecErrorCard, { children: "[sdui] page.body\uB294 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4" }) }),
|
|
2579
2135
|
toasts.length > 0 ? /* @__PURE__ */ jsx("div", { className: "wp-toast-region", children: toasts.map((t) => /* @__PURE__ */ jsx(Toast, { tone: t.tone, title: t.title, onClose: () => closeToast(t.id), children: t.message }, t.id)) }) : null
|
|
2580
|
-
] }) }) });
|
|
2136
|
+
] }) }) }) });
|
|
2581
2137
|
}
|
|
2582
2138
|
|
|
2583
|
-
export { Actions, Can, Col, Divider, Grid, KeyValueList, NodeErrorBoundary, PageHead, RemoteSearchResults, ResultTable, Row, SduiDialog, SduiForm, SduiPermissionProvider, SduiRenderer, SduiSearchDataProvider, SduiSearchResults, SearchResults,
|
|
2139
|
+
export { Actions, Can, Col, Divider, Grid, KeyValueList, NodeErrorBoundary, PageHead, RemoteSearchResults, ResultTable, Row, SduiDialog, SduiForm, SduiPermissionProvider, SduiRenderer, SduiSearchDataProvider, SduiSearchResults, SearchResults, Section, Show, Spacer, Text, cellRenderer, createRegistry, defaultAdapter, defaultRegistry, normalizePageSizes, useRemoteSearchPage, useSduiCan, useSduiDispatch, useSduiRuntime, useSearchFetcher, validateSpec };
|