@wipco/sdui 0.1.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-ZUCI34QY.js +1290 -0
- package/dist/index.d.ts +391 -79
- package/dist/index.js +277 -758
- package/dist/meta.d.ts +10 -2
- package/dist/meta.js +268 -27
- package/dist/rich.js +3 -2
- package/dist/styles.css +334 -13
- 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/chunk-AR2JYE75.js +0 -889
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,497 +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
|
-
return (
|
|
487
|
-
// 아이콘 세트에 scan-search가 없어 같은 뜻의 search로 대체
|
|
488
|
-
/* @__PURE__ */ jsx(EmptyState, { icon: "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." })
|
|
489
|
-
);
|
|
490
|
-
}
|
|
491
|
-
function splitMultiValue(value) {
|
|
492
|
-
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
493
|
-
}
|
|
494
|
-
function joinMultiValue(values) {
|
|
495
|
-
return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean))).sort().join(",");
|
|
496
|
-
}
|
|
497
|
-
function normalizeSearchText(value) {
|
|
498
|
-
return value.toLowerCase().replace(/\s+/g, "");
|
|
499
|
-
}
|
|
500
|
-
function includesLooseSearch(text, keyword) {
|
|
501
|
-
const normalizedKeyword = normalizeSearchText(keyword);
|
|
502
|
-
return normalizedKeyword.length === 0 || normalizeSearchText(text).includes(normalizedKeyword);
|
|
503
|
-
}
|
|
504
|
-
function searchFieldKeys(fields) {
|
|
505
|
-
return fields.flatMap((field) => {
|
|
506
|
-
if (field.type === "dateRange") {
|
|
507
|
-
return [field.fromKey ?? `${field.key}From`, field.toKey ?? `${field.key}To`];
|
|
508
|
-
}
|
|
509
|
-
if (field.type === "numberRange") {
|
|
510
|
-
return [field.fromKey ?? `${field.key}Min`, field.toKey ?? `${field.key}Max`];
|
|
511
|
-
}
|
|
512
|
-
return [field.key];
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
function searchValuesFromParams(fields, params) {
|
|
516
|
-
const values = {};
|
|
517
|
-
for (const key of searchFieldKeys(fields)) {
|
|
518
|
-
const value = params.get(key);
|
|
519
|
-
if (value) values[key] = value;
|
|
520
|
-
}
|
|
521
|
-
return values;
|
|
522
|
-
}
|
|
523
|
-
function cleanSearchValues(fields, values) {
|
|
524
|
-
const allowed = new Set(searchFieldKeys(fields));
|
|
525
|
-
const fieldsByKey = new Map(fields.map((field) => [field.key, field]));
|
|
526
|
-
return Object.fromEntries(
|
|
527
|
-
Object.entries(values).map(([key, value]) => {
|
|
528
|
-
const field = fieldsByKey.get(key);
|
|
529
|
-
const cleaned = field?.type === "select" || field?.type === "cascading" ? joinMultiValue(splitMultiValue(value)) : value.trim();
|
|
530
|
-
return [key, cleaned];
|
|
531
|
-
}).filter(([key, value]) => allowed.has(key) && value)
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
function cascadingPathMap(options) {
|
|
535
|
-
const paths = /* @__PURE__ */ new Map();
|
|
536
|
-
const visit = (option, parents) => {
|
|
537
|
-
const path = [...parents, option];
|
|
538
|
-
paths.set(option.value, path);
|
|
539
|
-
option.children?.forEach((child) => visit(child, path));
|
|
540
|
-
};
|
|
541
|
-
options.forEach((option) => visit(option, []));
|
|
542
|
-
return paths;
|
|
543
|
-
}
|
|
544
|
-
function cascadingValuePaths(options) {
|
|
545
|
-
const map = /* @__PURE__ */ new Map();
|
|
546
|
-
for (const [value, path] of cascadingPathMap(options)) {
|
|
547
|
-
map.set(
|
|
548
|
-
value,
|
|
549
|
-
path.map((option) => option.value)
|
|
550
|
-
);
|
|
551
|
-
}
|
|
552
|
-
return map;
|
|
553
|
-
}
|
|
554
|
-
function cascadingPathLabel(pathMap, value) {
|
|
555
|
-
const path = pathMap.get(value);
|
|
556
|
-
return path?.length ? path.map((option) => option.label).join(" > ") : value;
|
|
557
|
-
}
|
|
558
|
-
function activeSearchCriteria(fields, values) {
|
|
559
|
-
const criteria = [];
|
|
560
|
-
for (const field of fields) {
|
|
561
|
-
if (field.type === "select") {
|
|
562
|
-
for (const value2 of splitMultiValue(values[field.key] ?? "")) {
|
|
563
|
-
const label = field.options.find((o) => o.value === value2)?.label ?? value2;
|
|
564
|
-
criteria.push({
|
|
565
|
-
key: `${field.key}:${value2}`,
|
|
566
|
-
label: `${field.label}: ${label}`,
|
|
567
|
-
clearKeys: [field.key],
|
|
568
|
-
valueKey: field.key,
|
|
569
|
-
value: value2
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
continue;
|
|
573
|
-
}
|
|
574
|
-
if (field.type === "cascading") {
|
|
575
|
-
const pathMap = cascadingPathMap(field.options);
|
|
576
|
-
for (const value2 of splitMultiValue(values[field.key] ?? "")) {
|
|
577
|
-
criteria.push({
|
|
578
|
-
key: `${field.key}:${value2}`,
|
|
579
|
-
label: `${field.label}: ${cascadingPathLabel(pathMap, value2)}`,
|
|
580
|
-
clearKeys: [field.key],
|
|
581
|
-
valueKey: field.key,
|
|
582
|
-
value: value2
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
continue;
|
|
586
|
-
}
|
|
587
|
-
if (field.type === "dateRange" || field.type === "numberRange") {
|
|
588
|
-
const fromKey = field.fromKey ?? `${field.key}${field.type === "dateRange" ? "From" : "Min"}`;
|
|
589
|
-
const toKey = field.toKey ?? `${field.key}${field.type === "dateRange" ? "To" : "Max"}`;
|
|
590
|
-
const from = values[fromKey];
|
|
591
|
-
const to = values[toKey];
|
|
592
|
-
if (from || to) {
|
|
593
|
-
criteria.push({
|
|
594
|
-
key: field.key,
|
|
595
|
-
label: `${field.label}: ${from ?? ""}~${to ?? ""}`,
|
|
596
|
-
clearKeys: [fromKey, toKey]
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
continue;
|
|
600
|
-
}
|
|
601
|
-
const value = values[field.key];
|
|
602
|
-
if (value) {
|
|
603
|
-
criteria.push({
|
|
604
|
-
key: field.key,
|
|
605
|
-
label: `${field.label}: ${value}`,
|
|
606
|
-
clearKeys: [field.key]
|
|
607
|
-
});
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
return criteria;
|
|
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." });
|
|
611
495
|
}
|
|
496
|
+
var EMPTY_FILTERS = [];
|
|
612
497
|
var EMPTY_FIELDS = [];
|
|
613
|
-
var
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
hideSearchInput = false
|
|
623
|
-
}) {
|
|
624
|
-
const [open, setOpen] = useState(false);
|
|
625
|
-
const [draft, setDraft] = useState(advancedValues);
|
|
626
|
-
const hasAdvanced = advancedFields.length > 0;
|
|
627
|
-
useEffect(() => {
|
|
628
|
-
if (!open) setDraft(advancedValues);
|
|
629
|
-
}, [advancedValues, open]);
|
|
630
|
-
const activeCriteria = useMemo(
|
|
631
|
-
() => activeSearchCriteria(advancedFields, advancedValues),
|
|
632
|
-
[advancedFields, advancedValues]
|
|
633
|
-
);
|
|
634
|
-
const submit = (event) => {
|
|
635
|
-
event.preventDefault();
|
|
636
|
-
onSearch(search.trim(), cleanSearchValues(advancedFields, advancedValues));
|
|
637
|
-
setOpen(false);
|
|
638
|
-
};
|
|
639
|
-
const applyAdvancedSearch = () => {
|
|
640
|
-
onSearch(search.trim(), cleanSearchValues(advancedFields, draft));
|
|
641
|
-
setOpen(false);
|
|
642
|
-
};
|
|
643
|
-
const resetAdvancedSearch = () => {
|
|
644
|
-
setDraft({});
|
|
645
|
-
onSearch(search.trim(), {});
|
|
646
|
-
setOpen(false);
|
|
647
|
-
};
|
|
648
|
-
const removeCriterion = (criterion) => {
|
|
649
|
-
const nextValues = { ...advancedValues };
|
|
650
|
-
if (criterion.valueKey && criterion.value !== void 0) {
|
|
651
|
-
const remaining = splitMultiValue(nextValues[criterion.valueKey] ?? "").filter(
|
|
652
|
-
(value) => value !== criterion.value
|
|
653
|
-
);
|
|
654
|
-
if (remaining.length > 0) nextValues[criterion.valueKey] = joinMultiValue(remaining);
|
|
655
|
-
else delete nextValues[criterion.valueKey];
|
|
656
|
-
} else {
|
|
657
|
-
criterion.clearKeys.forEach((key) => delete nextValues[key]);
|
|
658
|
-
}
|
|
659
|
-
setDraft(nextValues);
|
|
660
|
-
onSearch(search.trim(), cleanSearchValues(advancedFields, nextValues));
|
|
661
|
-
};
|
|
662
|
-
return /* @__PURE__ */ jsxs("form", { className: ["wp-row", className].filter(Boolean).join(" "), onSubmit: submit, children: [
|
|
663
|
-
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,
|
|
664
|
-
!hideSearchInput ? /* @__PURE__ */ jsx(
|
|
665
|
-
Input,
|
|
666
|
-
{
|
|
667
|
-
icon: "search",
|
|
668
|
-
value: search,
|
|
669
|
-
onChange: (event) => onSearchChange(event.target.value),
|
|
670
|
-
placeholder
|
|
671
|
-
}
|
|
672
|
-
) : null,
|
|
673
|
-
hasAdvanced ? /* @__PURE__ */ jsx(
|
|
674
|
-
Popover,
|
|
675
|
-
{
|
|
676
|
-
open,
|
|
677
|
-
onClose: () => setOpen(false),
|
|
678
|
-
alignLeft: false,
|
|
679
|
-
width: "min(560px, 80vw)",
|
|
680
|
-
trigger: /* @__PURE__ */ jsx(
|
|
681
|
-
IconButton,
|
|
682
|
-
{
|
|
683
|
-
type: "button",
|
|
684
|
-
icon: "settings",
|
|
685
|
-
label: "\uC0C1\uC138\uAC80\uC0C9",
|
|
686
|
-
"aria-expanded": open,
|
|
687
|
-
style: open ? { background: "var(--wp-surface-active)" } : void 0,
|
|
688
|
-
onClick: () => setOpen((value) => !value)
|
|
689
|
-
}
|
|
690
|
-
),
|
|
691
|
-
footer: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
692
|
-
/* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "sm", icon: "refresh-cw", onClick: resetAdvancedSearch, children: "\uCD08\uAE30\uD654" }),
|
|
693
|
-
/* @__PURE__ */ jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: applyAdvancedSearch, children: "\uC801\uC6A9" })
|
|
694
|
-
] }),
|
|
695
|
-
children: /* @__PURE__ */ jsx("div", { className: "wp-grid wp-grid--2", children: advancedFields.map((field) => /* @__PURE__ */ jsx(
|
|
696
|
-
SearchFieldInput,
|
|
697
|
-
{
|
|
698
|
-
field,
|
|
699
|
-
values: draft,
|
|
700
|
-
onChange: (patch) => setDraft((current) => ({ ...current, ...patch }))
|
|
701
|
-
},
|
|
702
|
-
field.key
|
|
703
|
-
)) })
|
|
704
|
-
}
|
|
705
|
-
) : null
|
|
706
|
-
] });
|
|
707
|
-
}
|
|
708
|
-
function SearchFieldInput({
|
|
709
|
-
field,
|
|
710
|
-
values,
|
|
711
|
-
onChange
|
|
712
|
-
}) {
|
|
713
|
-
if (field.type === "select") {
|
|
714
|
-
return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsx(MultiSelectInput, { field, values, onChange }) });
|
|
715
|
-
}
|
|
716
|
-
if (field.type === "cascading") {
|
|
717
|
-
return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsx(CascadingInput, { field, values, onChange }) });
|
|
718
|
-
}
|
|
719
|
-
if (field.type === "dateRange") {
|
|
720
|
-
const fromKey = field.fromKey ?? `${field.key}From`;
|
|
721
|
-
const toKey = field.toKey ?? `${field.key}To`;
|
|
722
|
-
return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsxs("div", { className: "wp-row", children: [
|
|
723
|
-
/* @__PURE__ */ jsx(
|
|
724
|
-
Input,
|
|
725
|
-
{
|
|
726
|
-
type: "date",
|
|
727
|
-
mono: true,
|
|
728
|
-
value: values[fromKey] ?? "",
|
|
729
|
-
onChange: (e) => onChange({ [fromKey]: e.target.value })
|
|
730
|
-
}
|
|
731
|
-
),
|
|
732
|
-
/* @__PURE__ */ jsx("span", { className: "wp-caption", children: "~" }),
|
|
733
|
-
/* @__PURE__ */ jsx(
|
|
734
|
-
Input,
|
|
735
|
-
{
|
|
736
|
-
type: "date",
|
|
737
|
-
mono: true,
|
|
738
|
-
value: values[toKey] ?? "",
|
|
739
|
-
onChange: (e) => onChange({ [toKey]: e.target.value })
|
|
740
|
-
}
|
|
741
|
-
)
|
|
742
|
-
] }) });
|
|
743
|
-
}
|
|
744
|
-
if (field.type === "numberRange") {
|
|
745
|
-
const fromKey = field.fromKey ?? `${field.key}Min`;
|
|
746
|
-
const toKey = field.toKey ?? `${field.key}Max`;
|
|
747
|
-
return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsxs("div", { className: "wp-row", children: [
|
|
748
|
-
/* @__PURE__ */ jsx(
|
|
749
|
-
Input,
|
|
750
|
-
{
|
|
751
|
-
type: "number",
|
|
752
|
-
mono: true,
|
|
753
|
-
placeholder: field.minPlaceholder ?? "\uCD5C\uC18C",
|
|
754
|
-
value: values[fromKey] ?? "",
|
|
755
|
-
onChange: (e) => onChange({ [fromKey]: e.target.value })
|
|
756
|
-
}
|
|
757
|
-
),
|
|
758
|
-
/* @__PURE__ */ jsx("span", { className: "wp-caption", children: "~" }),
|
|
759
|
-
/* @__PURE__ */ jsx(
|
|
760
|
-
Input,
|
|
761
|
-
{
|
|
762
|
-
type: "number",
|
|
763
|
-
mono: true,
|
|
764
|
-
placeholder: field.maxPlaceholder ?? "\uCD5C\uB300",
|
|
765
|
-
value: values[toKey] ?? "",
|
|
766
|
-
onChange: (e) => onChange({ [toKey]: e.target.value })
|
|
767
|
-
}
|
|
768
|
-
)
|
|
769
|
-
] }) });
|
|
770
|
-
}
|
|
771
|
-
return /* @__PURE__ */ jsx(Field, { label: field.label, children: /* @__PURE__ */ jsx(
|
|
772
|
-
Input,
|
|
773
|
-
{
|
|
774
|
-
placeholder: field.placeholder,
|
|
775
|
-
value: values[field.key] ?? "",
|
|
776
|
-
onChange: (e) => onChange({ [field.key]: e.target.value })
|
|
777
|
-
}
|
|
778
|
-
) });
|
|
779
|
-
}
|
|
780
|
-
function multiSelectLabel(field, values) {
|
|
781
|
-
if (values.length === 0) return field.allLabel ?? "\uC804\uCCB4";
|
|
782
|
-
const labels = values.map((value) => field.options.find((item) => item.value === value)?.label ?? value);
|
|
783
|
-
if (labels.length <= 2) return labels.join(", ");
|
|
784
|
-
return `${labels[0]} \uC678 ${labels.length - 1}`;
|
|
785
|
-
}
|
|
786
|
-
function MultiSelectInput({
|
|
787
|
-
field,
|
|
788
|
-
values,
|
|
789
|
-
onChange
|
|
790
|
-
}) {
|
|
791
|
-
const [open, setOpen] = useState(false);
|
|
792
|
-
const selected = splitMultiValue(values[field.key] ?? "");
|
|
793
|
-
const toggle = (value, checked) => {
|
|
794
|
-
const next = checked ? [...selected, value] : selected.filter((item) => item !== value);
|
|
795
|
-
onChange({ [field.key]: joinMultiValue(next) });
|
|
796
|
-
};
|
|
797
|
-
return /* @__PURE__ */ jsx(
|
|
798
|
-
Popover,
|
|
799
|
-
{
|
|
800
|
-
open,
|
|
801
|
-
onClose: () => setOpen(false),
|
|
802
|
-
trigger: /* @__PURE__ */ jsx(
|
|
803
|
-
Button,
|
|
804
|
-
{
|
|
805
|
-
type: "button",
|
|
806
|
-
variant: "secondary",
|
|
807
|
-
block: true,
|
|
808
|
-
iconEnd: "chevron-down",
|
|
809
|
-
onClick: () => setOpen((value) => !value),
|
|
810
|
-
children: multiSelectLabel(field, selected)
|
|
811
|
-
}
|
|
812
|
-
),
|
|
813
|
-
children: /* @__PURE__ */ jsx("div", { className: "wp-col", children: field.options.map((option) => /* @__PURE__ */ jsx(
|
|
814
|
-
Checkbox,
|
|
815
|
-
{
|
|
816
|
-
label: option.label,
|
|
817
|
-
checked: selected.includes(option.value),
|
|
818
|
-
onChange: (e) => toggle(option.value, e.target.checked)
|
|
819
|
-
},
|
|
820
|
-
option.value
|
|
821
|
-
)) })
|
|
822
|
-
}
|
|
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
|
|
823
507
|
);
|
|
508
|
+
return clean.length > 0 ? clean : void 0;
|
|
824
509
|
}
|
|
825
|
-
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));
|
|
826
512
|
const seen = /* @__PURE__ */ new Set();
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
}
|
|
833
|
-
function cascadingLevels(options, selectedPaths) {
|
|
834
|
-
const levels = [];
|
|
835
|
-
let currentOptions = options;
|
|
836
|
-
let depth = 0;
|
|
837
|
-
while (currentOptions.length > 0) {
|
|
838
|
-
levels.push(currentOptions);
|
|
839
|
-
const selectedAtDepth = uniqueCascadingOptions(
|
|
840
|
-
selectedPaths.map((path) => path[depth]).filter(
|
|
841
|
-
(option) => Boolean(option && currentOptions.some((current) => current.value === option.value))
|
|
842
|
-
)
|
|
843
|
-
);
|
|
844
|
-
const nextOptions = uniqueCascadingOptions(selectedAtDepth.flatMap((option) => option.children ?? []));
|
|
845
|
-
if (nextOptions.length === 0) break;
|
|
846
|
-
currentOptions = nextOptions;
|
|
847
|
-
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 });
|
|
848
519
|
}
|
|
849
|
-
return
|
|
850
|
-
}
|
|
851
|
-
function selectedValuesForCascadingLevel(selectedPaths, depth, options) {
|
|
852
|
-
const optionValues = new Set(options.map((option) => option.value));
|
|
853
|
-
return Array.from(
|
|
854
|
-
new Set(
|
|
855
|
-
selectedPaths.map((path) => path[depth]?.value).filter((value) => Boolean(value) && optionValues.has(value))
|
|
856
|
-
)
|
|
857
|
-
);
|
|
858
|
-
}
|
|
859
|
-
function cascadingPathIncludes(pathMap, value, maybeAncestor) {
|
|
860
|
-
return pathMap.get(value)?.some((option) => option.value === maybeAncestor) ?? false;
|
|
520
|
+
return items.length > 0 ? items : [DEFAULT_VIEWS[0]];
|
|
861
521
|
}
|
|
862
|
-
function cascadingValuesOverlap(leftValue, rightValue, pathMap) {
|
|
863
|
-
return cascadingPathIncludes(pathMap, leftValue, rightValue) || cascadingPathIncludes(pathMap, rightValue, leftValue);
|
|
864
|
-
}
|
|
865
|
-
function cascadingLevelLabel(values, allLabel, pathMap, showPathLabels) {
|
|
866
|
-
if (values.length === 0) return allLabel;
|
|
867
|
-
const labels = values.map((value) => {
|
|
868
|
-
if (showPathLabels) return cascadingPathLabel(pathMap, value);
|
|
869
|
-
return pathMap.get(value)?.at(-1)?.label ?? value;
|
|
870
|
-
});
|
|
871
|
-
if (labels.length <= 2) return labels.join(", ");
|
|
872
|
-
return `${labels[0]} \uC678 ${labels.length - 1}`;
|
|
873
|
-
}
|
|
874
|
-
function cascadingOptionLabel(option, pathMap, showPathLabels) {
|
|
875
|
-
return showPathLabels ? cascadingPathLabel(pathMap, option.value) : option.label;
|
|
876
|
-
}
|
|
877
|
-
function CascadingInput({
|
|
878
|
-
field,
|
|
879
|
-
values,
|
|
880
|
-
onChange
|
|
881
|
-
}) {
|
|
882
|
-
const pathMap = useMemo(() => cascadingPathMap(field.options), [field.options]);
|
|
883
|
-
const selectedValues = useMemo(
|
|
884
|
-
() => splitMultiValue(values[field.key] ?? "").filter((value) => pathMap.has(value)),
|
|
885
|
-
[field.key, pathMap, values]
|
|
886
|
-
);
|
|
887
|
-
const selectedPaths = useMemo(
|
|
888
|
-
() => selectedValues.map((value) => pathMap.get(value)).filter((path) => Boolean(path)),
|
|
889
|
-
[pathMap, selectedValues]
|
|
890
|
-
);
|
|
891
|
-
const levels = useMemo(() => cascadingLevels(field.options, selectedPaths), [field.options, selectedPaths]);
|
|
892
|
-
const setSelectedValues = (nextValues) => {
|
|
893
|
-
onChange({ [field.key]: joinMultiValue(nextValues) });
|
|
894
|
-
};
|
|
895
|
-
const toggleOption = (option, nextChecked) => {
|
|
896
|
-
const withoutOverlap = selectedValues.filter(
|
|
897
|
-
(value) => !cascadingValuesOverlap(option.value, value, pathMap)
|
|
898
|
-
);
|
|
899
|
-
setSelectedValues(nextChecked ? [...withoutOverlap, option.value] : withoutOverlap);
|
|
900
|
-
};
|
|
901
|
-
const clearLevel = (depth) => {
|
|
902
|
-
if (depth === 0) {
|
|
903
|
-
setSelectedValues([]);
|
|
904
|
-
return;
|
|
905
|
-
}
|
|
906
|
-
const parents = selectedValuesForCascadingLevel(selectedPaths, depth - 1, levels[depth - 1] ?? []);
|
|
907
|
-
const rest = selectedValues.filter((value) => {
|
|
908
|
-
const parentValue = pathMap.get(value)?.[depth - 1]?.value;
|
|
909
|
-
return !parentValue || !parents.includes(parentValue);
|
|
910
|
-
});
|
|
911
|
-
setSelectedValues([...rest, ...parents]);
|
|
912
|
-
};
|
|
913
|
-
return /* @__PURE__ */ jsx("div", { className: "wp-col", children: levels.map((options, depth) => {
|
|
914
|
-
const levelValues = selectedValuesForCascadingLevel(selectedPaths, depth, options);
|
|
915
|
-
const showPathLabels = depth > 0 && selectedValuesForCascadingLevel(selectedPaths, depth - 1, levels[depth - 1] ?? []).length > 1;
|
|
916
|
-
return /* @__PURE__ */ jsx(
|
|
917
|
-
CascadingLevelSelect,
|
|
918
|
-
{
|
|
919
|
-
allLabel: depth === 0 ? field.allLabel ?? "\uC804\uCCB4" : "\uC804\uCCB4",
|
|
920
|
-
options,
|
|
921
|
-
pathMap,
|
|
922
|
-
selectedValues: levelValues,
|
|
923
|
-
showPathLabels,
|
|
924
|
-
onClear: () => clearLevel(depth),
|
|
925
|
-
onToggle: toggleOption
|
|
926
|
-
},
|
|
927
|
-
depth
|
|
928
|
-
);
|
|
929
|
-
}) });
|
|
930
|
-
}
|
|
931
|
-
function CascadingLevelSelect({
|
|
932
|
-
options,
|
|
933
|
-
pathMap,
|
|
934
|
-
selectedValues,
|
|
935
|
-
allLabel,
|
|
936
|
-
showPathLabels,
|
|
937
|
-
onClear,
|
|
938
|
-
onToggle
|
|
939
|
-
}) {
|
|
940
|
-
const [open, setOpen] = useState(false);
|
|
941
|
-
const selectedSet = new Set(selectedValues);
|
|
942
|
-
return /* @__PURE__ */ jsx(
|
|
943
|
-
Popover,
|
|
944
|
-
{
|
|
945
|
-
open,
|
|
946
|
-
onClose: () => setOpen(false),
|
|
947
|
-
trigger: /* @__PURE__ */ jsx(
|
|
948
|
-
Button,
|
|
949
|
-
{
|
|
950
|
-
type: "button",
|
|
951
|
-
variant: "secondary",
|
|
952
|
-
block: true,
|
|
953
|
-
iconEnd: "chevron-down",
|
|
954
|
-
onClick: () => setOpen((value) => !value),
|
|
955
|
-
children: cascadingLevelLabel(selectedValues, allLabel, pathMap, showPathLabels)
|
|
956
|
-
}
|
|
957
|
-
),
|
|
958
|
-
footer: /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", size: "xs", icon: "refresh-cw", onClick: onClear, children: allLabel }),
|
|
959
|
-
children: /* @__PURE__ */ jsx("div", { className: "wp-col", style: { minWidth: 200 }, children: options.map((option) => /* @__PURE__ */ jsx(
|
|
960
|
-
Checkbox,
|
|
961
|
-
{
|
|
962
|
-
label: cascadingOptionLabel(option, pathMap, showPathLabels),
|
|
963
|
-
checked: selectedSet.has(option.value),
|
|
964
|
-
onChange: (e) => onToggle(option, e.target.checked)
|
|
965
|
-
},
|
|
966
|
-
option.value
|
|
967
|
-
)) })
|
|
968
|
-
}
|
|
969
|
-
);
|
|
970
|
-
}
|
|
971
|
-
var EMPTY_FILTERS = [];
|
|
972
|
-
var EMPTY_FIELDS2 = [];
|
|
973
|
-
var PAGE_SIZE_CHOICES = [10, 20, 50, 100];
|
|
974
522
|
function SearchResults({
|
|
975
523
|
item,
|
|
976
524
|
columns,
|
|
@@ -978,6 +526,8 @@ function SearchResults({
|
|
|
978
526
|
leftFilters = EMPTY_FILTERS,
|
|
979
527
|
rightFilters = EMPTY_FILTERS,
|
|
980
528
|
initialView = "list",
|
|
529
|
+
views,
|
|
530
|
+
pageSizes,
|
|
981
531
|
className,
|
|
982
532
|
searchParams,
|
|
983
533
|
onParamsChange,
|
|
@@ -989,26 +539,28 @@ function SearchResults({
|
|
|
989
539
|
headerActions,
|
|
990
540
|
isLoading = false,
|
|
991
541
|
hideHeader = false,
|
|
992
|
-
advancedSearchFields =
|
|
542
|
+
advancedSearchFields = EMPTY_FIELDS,
|
|
993
543
|
searchPlaceholder,
|
|
994
544
|
virtual
|
|
995
545
|
}) {
|
|
996
546
|
const hasActions = actions.length > 0;
|
|
547
|
+
const sizeChoices = normalizePageSizes(pageSizes) ?? PAGE_SIZE_CHOICES;
|
|
548
|
+
const defaultPageSize = sizeChoices[0];
|
|
997
549
|
const page = Number(searchParams.get("page")) || 1;
|
|
998
|
-
const pageSize = Number(searchParams.get("limit")) ||
|
|
999
|
-
const pageSizeChoices =
|
|
1000
|
-
const
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
};
|
|
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;
|
|
1006
557
|
const appliedSearch = searchParams.get("search") ?? "";
|
|
1007
558
|
const [view, setView] = useState(() => applyViewConstraints(initialView));
|
|
1008
559
|
const [search, setSearch] = useState(appliedSearch);
|
|
1009
560
|
const [sort, setSort] = useState(null);
|
|
1010
561
|
const [selected, setSelected] = useState(/* @__PURE__ */ new Set());
|
|
1011
562
|
const selectedRowsRef = useRef(/* @__PURE__ */ new Map());
|
|
563
|
+
const activeView = applyViewConstraints(view);
|
|
1012
564
|
useEffect(() => {
|
|
1013
565
|
setSearch(appliedSearch);
|
|
1014
566
|
}, [appliedSearch]);
|
|
@@ -1097,7 +649,7 @@ function SearchResults({
|
|
|
1097
649
|
const safePage = Math.min(Math.max(1, page), pageCount);
|
|
1098
650
|
const virtualThreshold = typeof virtual === "number" ? virtual : 150;
|
|
1099
651
|
const virtualEnabled = virtual === true || typeof virtual === "number";
|
|
1100
|
-
const virtualListActive = virtualEnabled &&
|
|
652
|
+
const virtualListActive = virtualEnabled && activeView === "list" && processed.length > virtualThreshold;
|
|
1101
653
|
const visibleItems = clientPaginated && !virtualListActive ? processed.slice((safePage - 1) * pageSize, safePage * pageSize) : processed;
|
|
1102
654
|
const visibleIds = visibleItems.map((row) => row.id);
|
|
1103
655
|
const allSelected = visibleIds.length > 0 && visibleIds.every((id) => selected.has(id));
|
|
@@ -1128,12 +680,12 @@ function SearchResults({
|
|
|
1128
680
|
{
|
|
1129
681
|
key: "__select",
|
|
1130
682
|
header: /* @__PURE__ */ jsx(
|
|
1131
|
-
|
|
683
|
+
Checkbox,
|
|
1132
684
|
{
|
|
1133
685
|
checked: allSelected,
|
|
1134
686
|
indeterminate: !allSelected && someSelected,
|
|
1135
|
-
label: `${title} \uC804\uCCB4 \uC120\uD0DD`,
|
|
1136
|
-
onChange: (
|
|
687
|
+
"aria-label": `${title} \uC804\uCCB4 \uC120\uD0DD`,
|
|
688
|
+
onChange: (e) => toggleSelectAll(e.target.checked)
|
|
1137
689
|
}
|
|
1138
690
|
),
|
|
1139
691
|
width: "36px",
|
|
@@ -1189,7 +741,14 @@ function SearchResults({
|
|
|
1189
741
|
placeholder: searchPlaceholder
|
|
1190
742
|
}
|
|
1191
743
|
),
|
|
1192
|
-
|
|
744
|
+
viewItems.length > 1 ? /* @__PURE__ */ jsx(
|
|
745
|
+
SegmentedControl,
|
|
746
|
+
{
|
|
747
|
+
items: viewItems,
|
|
748
|
+
value: activeView,
|
|
749
|
+
onChange: (next) => setView(applyViewConstraints(next))
|
|
750
|
+
}
|
|
751
|
+
) : null,
|
|
1193
752
|
headerActions
|
|
1194
753
|
] })
|
|
1195
754
|
] }) : null,
|
|
@@ -1214,21 +773,16 @@ function SearchResults({
|
|
|
1214
773
|
)) })
|
|
1215
774
|
] }) : null,
|
|
1216
775
|
hasActions && selectedItems.length > 0 ? (
|
|
1217
|
-
// 플로팅 벌크 액션 바 — 스크롤 위치와 무관하게 화면 하단 중앙에
|
|
1218
|
-
//
|
|
776
|
+
// 플로팅 벌크 액션 바 — 스크롤 위치와 무관하게 화면 하단 중앙에 뜬다.
|
|
777
|
+
// 생김새는 DS의 .wp-toolbar가 맡고, 여기서는 "화면 하단 중앙 고정"만 얹는다
|
|
778
|
+
// (--floating은 absolute라 스크롤을 따라오지 않는다).
|
|
1219
779
|
/* @__PURE__ */ jsxs(
|
|
1220
780
|
"div",
|
|
1221
781
|
{
|
|
1222
782
|
role: "toolbar",
|
|
783
|
+
className: "wp-toolbar",
|
|
1223
784
|
"aria-label": `${title} \uC120\uD0DD \uD56D\uBAA9 \uC77C\uAD04 \uC791\uC5C5`,
|
|
1224
785
|
style: {
|
|
1225
|
-
display: "flex",
|
|
1226
|
-
alignItems: "center",
|
|
1227
|
-
gap: 6,
|
|
1228
|
-
padding: 5,
|
|
1229
|
-
background: "var(--wp-surface-card)",
|
|
1230
|
-
border: "1px solid var(--wp-border-subtle)",
|
|
1231
|
-
borderRadius: "var(--wp-radius-md)",
|
|
1232
786
|
position: "fixed",
|
|
1233
787
|
bottom: "var(--wp-space-6)",
|
|
1234
788
|
left: "50%",
|
|
@@ -1244,7 +798,7 @@ function SearchResults({
|
|
|
1244
798
|
selectedItems.length,
|
|
1245
799
|
"\uAC74 \uC120\uD0DD\uB428"
|
|
1246
800
|
] }),
|
|
1247
|
-
/* @__PURE__ */ jsx(
|
|
801
|
+
/* @__PURE__ */ jsx("span", { className: "wp-toolbar__sep" }),
|
|
1248
802
|
actions.map((action) => /* @__PURE__ */ jsx(
|
|
1249
803
|
Button,
|
|
1250
804
|
{
|
|
@@ -1256,13 +810,13 @@ function SearchResults({
|
|
|
1256
810
|
},
|
|
1257
811
|
action.label
|
|
1258
812
|
)),
|
|
1259
|
-
/* @__PURE__ */ jsx(
|
|
813
|
+
/* @__PURE__ */ jsx("span", { className: "wp-toolbar__sep" }),
|
|
1260
814
|
/* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", icon: "x", onClick: () => setSelected(/* @__PURE__ */ new Set()), children: "\uC120\uD0DD \uD574\uC81C" })
|
|
1261
815
|
]
|
|
1262
816
|
}
|
|
1263
817
|
)
|
|
1264
818
|
) : null,
|
|
1265
|
-
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(
|
|
1266
820
|
ResultTable,
|
|
1267
821
|
{
|
|
1268
822
|
columns: tableColumns,
|
|
@@ -1286,8 +840,11 @@ function SearchResults({
|
|
|
1286
840
|
options: pageSizeChoices.map((size) => ({ value: String(size), label: `${size}\uAC74` })),
|
|
1287
841
|
value: String(pageSize),
|
|
1288
842
|
onChange: (e) => (
|
|
1289
|
-
// 기본값
|
|
1290
|
-
updateParams({
|
|
843
|
+
// 기본값(첫 후보)은 파라미터를 지워 상태를 깔끔하게 유지하고, 페이지는 1로 되돌린다
|
|
844
|
+
updateParams({
|
|
845
|
+
limit: e.target.value === String(defaultPageSize) ? null : e.target.value,
|
|
846
|
+
page: null
|
|
847
|
+
})
|
|
1291
848
|
)
|
|
1292
849
|
}
|
|
1293
850
|
)
|
|
@@ -1319,99 +876,6 @@ function formatCell(value) {
|
|
|
1319
876
|
if (value instanceof Date) return value.toLocaleString("ko-KR");
|
|
1320
877
|
return value;
|
|
1321
878
|
}
|
|
1322
|
-
function ToolbarSeparator() {
|
|
1323
|
-
return /* @__PURE__ */ jsx("span", { style: { width: 1, height: 20, background: "var(--wp-border-subtle)", flex: "none" } });
|
|
1324
|
-
}
|
|
1325
|
-
function SelectCheckbox({
|
|
1326
|
-
checked,
|
|
1327
|
-
indeterminate,
|
|
1328
|
-
label,
|
|
1329
|
-
onChange
|
|
1330
|
-
}) {
|
|
1331
|
-
const ref = useRef(null);
|
|
1332
|
-
useEffect(() => {
|
|
1333
|
-
if (ref.current) ref.current.indeterminate = indeterminate;
|
|
1334
|
-
}, [indeterminate]);
|
|
1335
|
-
return /* @__PURE__ */ jsxs("label", { className: "wp-check", children: [
|
|
1336
|
-
/* @__PURE__ */ jsx(
|
|
1337
|
-
"input",
|
|
1338
|
-
{
|
|
1339
|
-
ref,
|
|
1340
|
-
type: "checkbox",
|
|
1341
|
-
checked,
|
|
1342
|
-
"aria-label": label,
|
|
1343
|
-
onChange: (e) => onChange(e.target.checked)
|
|
1344
|
-
}
|
|
1345
|
-
),
|
|
1346
|
-
/* @__PURE__ */ jsx(
|
|
1347
|
-
"span",
|
|
1348
|
-
{
|
|
1349
|
-
className: "wp-check__box",
|
|
1350
|
-
style: indeterminate && !checked ? {
|
|
1351
|
-
background: "var(--wp-action-primary-bg)",
|
|
1352
|
-
borderColor: "var(--wp-action-primary-bg)",
|
|
1353
|
-
color: "var(--wp-white)"
|
|
1354
|
-
} : void 0,
|
|
1355
|
-
children: /* @__PURE__ */ jsx(Icon, { name: indeterminate && !checked ? "minus" : "check", size: 12, strokeWidth: 2.5 })
|
|
1356
|
-
}
|
|
1357
|
-
)
|
|
1358
|
-
] });
|
|
1359
|
-
}
|
|
1360
|
-
function ViewToggle({ value, onChange }) {
|
|
1361
|
-
const items = [
|
|
1362
|
-
{ value: "list", icon: "list", label: "\uBAA9\uB85D" },
|
|
1363
|
-
// 아이콘 세트에 layout-grid가 없어 같은 뜻의 layout-dashboard로 대체
|
|
1364
|
-
{ value: "grid", icon: "layout-dashboard", label: "\uCE74\uB4DC" }
|
|
1365
|
-
];
|
|
1366
|
-
return /* @__PURE__ */ jsx(
|
|
1367
|
-
"div",
|
|
1368
|
-
{
|
|
1369
|
-
role: "tablist",
|
|
1370
|
-
style: {
|
|
1371
|
-
display: "inline-flex",
|
|
1372
|
-
padding: 2,
|
|
1373
|
-
gap: 2,
|
|
1374
|
-
background: "var(--wp-surface-sunken)",
|
|
1375
|
-
border: "1px solid var(--wp-border-subtle)",
|
|
1376
|
-
borderRadius: "var(--wp-radius-md)"
|
|
1377
|
-
},
|
|
1378
|
-
children: items.map((tab) => {
|
|
1379
|
-
const active = tab.value === value;
|
|
1380
|
-
return /* @__PURE__ */ jsxs(
|
|
1381
|
-
"button",
|
|
1382
|
-
{
|
|
1383
|
-
type: "button",
|
|
1384
|
-
role: "tab",
|
|
1385
|
-
"aria-selected": active,
|
|
1386
|
-
onClick: () => onChange(tab.value),
|
|
1387
|
-
style: {
|
|
1388
|
-
display: "inline-flex",
|
|
1389
|
-
alignItems: "center",
|
|
1390
|
-
justifyContent: "center",
|
|
1391
|
-
gap: 5,
|
|
1392
|
-
height: 24,
|
|
1393
|
-
padding: "0 10px",
|
|
1394
|
-
border: 0,
|
|
1395
|
-
borderRadius: "var(--wp-radius-sm)",
|
|
1396
|
-
fontSize: 12,
|
|
1397
|
-
fontWeight: 500,
|
|
1398
|
-
whiteSpace: "nowrap",
|
|
1399
|
-
cursor: "pointer",
|
|
1400
|
-
background: active ? "var(--wp-surface-card)" : "transparent",
|
|
1401
|
-
color: active ? "var(--wp-text-strong)" : "var(--wp-text-muted)",
|
|
1402
|
-
boxShadow: active ? "var(--wp-shadow-sm)" : void 0
|
|
1403
|
-
},
|
|
1404
|
-
children: [
|
|
1405
|
-
/* @__PURE__ */ jsx(Icon, { name: tab.icon, size: 14 }),
|
|
1406
|
-
tab.label
|
|
1407
|
-
]
|
|
1408
|
-
},
|
|
1409
|
-
tab.value
|
|
1410
|
-
);
|
|
1411
|
-
})
|
|
1412
|
-
}
|
|
1413
|
-
);
|
|
1414
|
-
}
|
|
1415
879
|
function resolveRowPart(part, item) {
|
|
1416
880
|
return typeof part === "function" ? part(item) : part;
|
|
1417
881
|
}
|
|
@@ -1471,6 +935,7 @@ function FilterMultiSelect({
|
|
|
1471
935
|
type: "button",
|
|
1472
936
|
size: "sm",
|
|
1473
937
|
variant: values.length > 0 ? "primary" : "secondary",
|
|
938
|
+
icon: "funnel",
|
|
1474
939
|
iconEnd: "chevron-down",
|
|
1475
940
|
onClick: () => setOpen((value) => !value),
|
|
1476
941
|
children: label
|
|
@@ -1494,7 +959,7 @@ function FilterMultiSelect({
|
|
|
1494
959
|
function toList(action) {
|
|
1495
960
|
return Array.isArray(action) ? action : [action];
|
|
1496
961
|
}
|
|
1497
|
-
function mapColumns(columns) {
|
|
962
|
+
function mapColumns(columns, dispatch) {
|
|
1498
963
|
return (columns ?? []).map((column) => ({
|
|
1499
964
|
key: column.key,
|
|
1500
965
|
title: column.title,
|
|
@@ -1503,14 +968,29 @@ function mapColumns(columns) {
|
|
|
1503
968
|
ratio: column.ratio,
|
|
1504
969
|
mono: column.mono,
|
|
1505
970
|
strong: column.strong,
|
|
1506
|
-
render: column.cell
|
|
1507
|
-
const value = String(row[column.key] ?? "");
|
|
1508
|
-
if (!value) return null;
|
|
1509
|
-
return /* @__PURE__ */ jsx(Badge, { tone: column.cell?.toneMap?.[value] ?? "neutral", children: value });
|
|
1510
|
-
} : void 0
|
|
971
|
+
render: cellRenderer(column.cell, column.key, dispatch)
|
|
1511
972
|
}));
|
|
1512
973
|
}
|
|
1513
|
-
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) {
|
|
1514
994
|
if (!gridCard) return void 0;
|
|
1515
995
|
return {
|
|
1516
996
|
title: (row) => String(row[gridCard.titleKey] ?? ""),
|
|
@@ -1520,12 +1000,34 @@ function mapGridCard(gridCard) {
|
|
|
1520
1000
|
if (!value) return null;
|
|
1521
1001
|
return { text: value, variant: gridCard.badge.toneMap?.[value] ?? "neutral" };
|
|
1522
1002
|
} : void 0,
|
|
1523
|
-
rows: gridCard.rows
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
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
|
|
1527
1021
|
};
|
|
1528
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
|
+
}
|
|
1529
1031
|
function mapActions(actions, dispatch) {
|
|
1530
1032
|
return (actions ?? []).map((action) => ({
|
|
1531
1033
|
label: action.label,
|
|
@@ -1547,6 +1049,8 @@ function SduiSearchResults({
|
|
|
1547
1049
|
gridCard,
|
|
1548
1050
|
actions,
|
|
1549
1051
|
initialView,
|
|
1052
|
+
views,
|
|
1053
|
+
pageSizes,
|
|
1550
1054
|
disableGrid,
|
|
1551
1055
|
disableList,
|
|
1552
1056
|
searchPlaceholder,
|
|
@@ -1562,12 +1066,14 @@ function SduiSearchResults({
|
|
|
1562
1066
|
{
|
|
1563
1067
|
title,
|
|
1564
1068
|
item: { data: rows, total: rows.length },
|
|
1565
|
-
columns: mapColumns(columns),
|
|
1566
|
-
gridConfig: mapGridCard(gridCard),
|
|
1069
|
+
columns: mapColumns(columns, dispatch),
|
|
1070
|
+
gridConfig: mapGridCard(gridCard, dispatch),
|
|
1567
1071
|
leftFilters,
|
|
1568
1072
|
rightFilters,
|
|
1569
1073
|
actions: mapActions(actions, dispatch),
|
|
1570
1074
|
initialView,
|
|
1075
|
+
views: mapViews(views, gridCard, disableGrid, disableList),
|
|
1076
|
+
pageSizes,
|
|
1571
1077
|
disableGrid: disableGrid ?? !gridCard,
|
|
1572
1078
|
disableList,
|
|
1573
1079
|
advancedSearchFields: fields,
|
|
@@ -1590,6 +1096,8 @@ function RemoteSearchResults({
|
|
|
1590
1096
|
gridCard,
|
|
1591
1097
|
actions,
|
|
1592
1098
|
initialView,
|
|
1099
|
+
views,
|
|
1100
|
+
pageSizes,
|
|
1593
1101
|
disableGrid,
|
|
1594
1102
|
disableList,
|
|
1595
1103
|
searchPlaceholder,
|
|
@@ -1604,12 +1112,13 @@ function RemoteSearchResults({
|
|
|
1604
1112
|
initial.set("limit", String(pageSize));
|
|
1605
1113
|
return initial;
|
|
1606
1114
|
});
|
|
1115
|
+
const defaultPageSize = normalizePageSizes(pageSizes)?.[0] ?? 10;
|
|
1607
1116
|
const query = useMemo(() => {
|
|
1608
1117
|
const next = new URLSearchParams(params);
|
|
1609
1118
|
if (!next.has("page")) next.set("page", "1");
|
|
1610
|
-
if (!next.has("limit")) next.set("limit",
|
|
1119
|
+
if (!next.has("limit")) next.set("limit", String(defaultPageSize));
|
|
1611
1120
|
return next;
|
|
1612
|
-
}, [params]);
|
|
1121
|
+
}, [params, defaultPageSize]);
|
|
1613
1122
|
const { page, isLoading, error, reload } = useRemoteSearchPage(endpoint, query);
|
|
1614
1123
|
if (error && page.data.length === 0) {
|
|
1615
1124
|
return /* @__PURE__ */ jsx(
|
|
@@ -1627,12 +1136,14 @@ function RemoteSearchResults({
|
|
|
1627
1136
|
{
|
|
1628
1137
|
title,
|
|
1629
1138
|
item: page,
|
|
1630
|
-
columns: mapColumns(columns),
|
|
1631
|
-
gridConfig: mapGridCard(gridCard),
|
|
1139
|
+
columns: mapColumns(columns, dispatch),
|
|
1140
|
+
gridConfig: mapGridCard(gridCard, dispatch),
|
|
1632
1141
|
leftFilters,
|
|
1633
1142
|
rightFilters,
|
|
1634
1143
|
actions: mapActions(actions, dispatch),
|
|
1635
1144
|
initialView,
|
|
1145
|
+
views: mapViews(views, gridCard, disableGrid, disableList),
|
|
1146
|
+
pageSizes,
|
|
1636
1147
|
disableGrid: disableGrid ?? !gridCard,
|
|
1637
1148
|
disableList,
|
|
1638
1149
|
advancedSearchFields: fields,
|
|
@@ -1805,6 +1316,13 @@ var defaultRegistry = {
|
|
|
1805
1316
|
// 권한 게이트 — 판정은 SduiRenderer의 can 프롭(permission.tsx)에 위임한다.
|
|
1806
1317
|
// 주입 전까지는 통과시키되 console.error를 한 번 낸다(근거는 permission.tsx의 MISSING 주석).
|
|
1807
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 참고).
|
|
1808
1326
|
// 검색+목록 화면. 스펙 이름은 ERP 어휘를 그대로 쓰고, 구현은 JSON 전용 어댑터를 가리킨다
|
|
1809
1327
|
// (SearchResults 자체는 함수 프롭을 받는 타입드 컴포넌트라 스펙에서 직접 못 쓴다).
|
|
1810
1328
|
SearchResults: SduiSearchResults,
|
|
@@ -2032,7 +1550,7 @@ function eventValue(arg) {
|
|
|
2032
1550
|
}
|
|
2033
1551
|
return arg;
|
|
2034
1552
|
}
|
|
2035
|
-
function
|
|
1553
|
+
function toNumber2(v) {
|
|
2036
1554
|
if (typeof v === "number") return v;
|
|
2037
1555
|
if (typeof v === "string") {
|
|
2038
1556
|
const n = Number(v.replace(/,/g, ""));
|
|
@@ -2077,9 +1595,9 @@ function ruleError(rule, value, values, label, customValidators) {
|
|
|
2077
1595
|
case "required":
|
|
2078
1596
|
return empty || value === false ? rule.message ?? `${label}\uC744(\uB97C) \uC785\uB825\uD558\uC138\uC694` : null;
|
|
2079
1597
|
case "min":
|
|
2080
|
-
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;
|
|
2081
1599
|
case "max":
|
|
2082
|
-
return !empty &&
|
|
1600
|
+
return !empty && toNumber2(value) > rule.value ? rule.message ?? `${label}\uC740(\uB294) ${rule.value} \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4` : null;
|
|
2083
1601
|
case "minLength":
|
|
2084
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;
|
|
2085
1603
|
case "maxLength":
|
|
@@ -2231,6 +1749,7 @@ function SduiRenderer({
|
|
|
2231
1749
|
registry = defaultRegistry,
|
|
2232
1750
|
adapter,
|
|
2233
1751
|
syncUrlState = true,
|
|
1752
|
+
can,
|
|
2234
1753
|
onCustomAction,
|
|
2235
1754
|
customValidators
|
|
2236
1755
|
}) {
|
|
@@ -2375,8 +1894,8 @@ function SduiRenderer({
|
|
|
2375
1894
|
const resolved = args.map((arg) => resolveValue(arg, scope));
|
|
2376
1895
|
const nums = (v) => {
|
|
2377
1896
|
if (Array.isArray(v))
|
|
2378
|
-
return v.map((row) => field && isPlainObject2(row) ?
|
|
2379
|
-
return [
|
|
1897
|
+
return v.map((row) => field && isPlainObject2(row) ? toNumber2(row[field]) : toNumber2(row));
|
|
1898
|
+
return [toNumber2(v)];
|
|
2380
1899
|
};
|
|
2381
1900
|
switch (op) {
|
|
2382
1901
|
case "sum":
|
|
@@ -2384,17 +1903,17 @@ function SduiRenderer({
|
|
|
2384
1903
|
case "count":
|
|
2385
1904
|
return resolved.reduce((a, v) => a + (Array.isArray(v) ? v.length : v == null ? 0 : 1), 0);
|
|
2386
1905
|
case "sub": {
|
|
2387
|
-
const list = resolved.map((v) =>
|
|
1906
|
+
const list = resolved.map((v) => toNumber2(v));
|
|
2388
1907
|
return list.length === 0 ? 0 : list.reduce((a, b) => a - b);
|
|
2389
1908
|
}
|
|
2390
1909
|
case "mul":
|
|
2391
|
-
return resolved.map((v) =>
|
|
1910
|
+
return resolved.map((v) => toNumber2(v)).reduce((a, b) => a * b, 1);
|
|
2392
1911
|
case "div": {
|
|
2393
|
-
const [a, b] = resolved.map((v) =>
|
|
1912
|
+
const [a, b] = resolved.map((v) => toNumber2(v));
|
|
2394
1913
|
return b === 0 ? 0 : a / b;
|
|
2395
1914
|
}
|
|
2396
1915
|
case "percent": {
|
|
2397
|
-
const [a, b] = resolved.map((v) =>
|
|
1916
|
+
const [a, b] = resolved.map((v) => toNumber2(v));
|
|
2398
1917
|
return b === 0 ? 0 : Math.round(a / b * 1e3) / 10;
|
|
2399
1918
|
}
|
|
2400
1919
|
case "concat":
|
|
@@ -2407,13 +1926,13 @@ function SduiRenderer({
|
|
|
2407
1926
|
}
|
|
2408
1927
|
// 수량 비교 — 정원 초과 차단(Button.disabled), 임계값 경고(Show.when) 등
|
|
2409
1928
|
case "gt":
|
|
2410
|
-
return
|
|
1929
|
+
return toNumber2(resolved[0]) > toNumber2(resolved[1]);
|
|
2411
1930
|
case "gte":
|
|
2412
|
-
return
|
|
1931
|
+
return toNumber2(resolved[0]) >= toNumber2(resolved[1]);
|
|
2413
1932
|
case "lt":
|
|
2414
|
-
return
|
|
1933
|
+
return toNumber2(resolved[0]) < toNumber2(resolved[1]);
|
|
2415
1934
|
case "lte":
|
|
2416
|
-
return
|
|
1935
|
+
return toNumber2(resolved[0]) <= toNumber2(resolved[1]);
|
|
2417
1936
|
/** 부정 — 조건을 뒤집어 "없을 때만" 렌더링하는 데 쓴다 */
|
|
2418
1937
|
case "not":
|
|
2419
1938
|
return !resolved[0];
|
|
@@ -2607,14 +2126,14 @@ function SduiRenderer({
|
|
|
2607
2126
|
}),
|
|
2608
2127
|
[state, setEntries]
|
|
2609
2128
|
);
|
|
2610
|
-
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: [
|
|
2611
2130
|
page.title || page.subtitle ? /* @__PURE__ */ jsx("div", { className: "wp-page-head", children: /* @__PURE__ */ jsxs("div", { className: "wp-page-head__text", children: [
|
|
2612
2131
|
page.title ? /* @__PURE__ */ jsx("h1", { className: "wp-page-head__title", children: page.title }) : null,
|
|
2613
2132
|
page.subtitle ? /* @__PURE__ */ jsx("div", { className: "wp-page-head__sub", children: page.subtitle }) : null
|
|
2614
2133
|
] }) }) : null,
|
|
2615
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" }) }),
|
|
2616
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
|
|
2617
|
-
] }) }) });
|
|
2136
|
+
] }) }) }) });
|
|
2618
2137
|
}
|
|
2619
2138
|
|
|
2620
|
-
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 };
|