@wipco/sdui 1.4.2 → 1.5.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/dist/index.d.ts CHANGED
@@ -738,6 +738,19 @@ interface RemotePageState {
738
738
  * 그래서 늦게 도착한 응답은 아래 `alive` 검사로 한 번 더 버린다.
739
739
  */
740
740
  declare function useRemoteSearchPage(endpoint: string, params: URLSearchParams): RemotePageState;
741
+ interface RemoteSummaryState {
742
+ /** 합계 응답 객체 — 첫 응답 전·실패 시 null (직전 값은 재조회 중에도 유지) */
743
+ values: Record<string, unknown> | null;
744
+ isLoading: boolean;
745
+ error: Error | null;
746
+ }
747
+ /**
748
+ * 목록 위 합계 칸(`summary.endpoint`) 조회 — 응답은 `{ data, total }` 이 아니라 객체 하나라
749
+ * 목록 fetcher(toPage)를 거치지 않고 어댑터 fetch(없으면 same-origin fetch)로 직접 받는다.
750
+ * 목록과 같은 재조회 신호(invalidate 접두어·SSE)를 받도록 자기 endpoint 로 구독한다.
751
+ * endpoint 가 null 이면 아무것도 하지 않는다.
752
+ */
753
+ declare function useRemoteSearchSummary(endpoint: string | null, params: URLSearchParams): RemoteSummaryState;
741
754
 
742
755
  /**
743
756
  * 검색 툴바 — ERP `app/components/search/SearchToolbar.tsx`의 계약(타입·헬퍼·동작)을
@@ -881,6 +894,22 @@ interface SearchFilter {
881
894
  value: string;
882
895
  }[];
883
896
  }
897
+ /**
898
+ * 목록 맨 위의 구분 탭 — 한 쿼리 키의 값을 탭으로 고른다(전체/용역/상품/기타 같은).
899
+ *
900
+ * 필터 칩(leftFilters)과 같은 파라미터 규칙이다: 고른 탭의 `value` 가 `key` 파라미터가
901
+ * 되고, 빈 문자열 탭은 파라미터를 지운다(= 전체). 값에 쉼표를 넣으면 여러 값 중 하나
902
+ * (`기타,미수주`)라는 뜻이다 — 서버 `=` 필터와 로컬 재필터가 둘 다 그렇게 읽는다.
903
+ * 탭을 바꾸면 1페이지로 돌아간다.
904
+ */
905
+ interface SearchTabs {
906
+ key: string;
907
+ items: {
908
+ value: string;
909
+ label: string;
910
+ icon?: string;
911
+ }[];
912
+ }
884
913
  /**
885
914
  * 목록/카드 전환 버튼 한 칸.
886
915
  *
@@ -925,6 +954,10 @@ interface SearchResultsProps<T> {
925
954
  gridConfig?: GridCardConfig<T>;
926
955
  leftFilters?: SearchFilter[];
927
956
  rightFilters?: SearchFilter[];
957
+ /** 목록 맨 위(툴바 위)의 구분 탭 */
958
+ tabs?: SearchTabs;
959
+ /** 탭보다도 위에 놓이는 요약 영역 — 합계 칸(SearchSummaryCards) 자리 */
960
+ summary?: React__default.ReactNode;
928
961
  initialView?: 'grid' | 'list';
929
962
  /**
930
963
  * 노출할 보기 목록. 생략하면 목록/카드 두 칸이 기본값이고,
@@ -966,7 +999,7 @@ interface SearchResultsProps<T> {
966
999
  declare function normalizePageSizes(sizes: number[] | undefined): number[] | undefined;
967
1000
  declare function SearchResults<T extends {
968
1001
  id: string;
969
- }>({ item, columns, gridConfig, leftFilters, rightFilters, initialView, views, pageSizes, className, style, searchParams, onParamsChange, actions, disableGrid, disableList, onRowClick, title, headerActions, isLoading, hideHeader, advancedSearchFields: rawAdvancedSearchFields, searchPlaceholder, virtual, }: SearchResultsProps<T>): React__default.JSX.Element;
1002
+ }>({ item, columns, gridConfig, leftFilters, rightFilters, tabs, summary, initialView, views, pageSizes, className, style, searchParams, onParamsChange, actions, disableGrid, disableList, onRowClick, title, headerActions, isLoading, hideHeader, advancedSearchFields: rawAdvancedSearchFields, searchPlaceholder, virtual, }: SearchResultsProps<T>): React__default.JSX.Element;
970
1003
 
971
1004
  /**
972
1005
  * 셀 표현 어휘 — 목록 컬럼의 셀과 카드 행의 값이 **같은 규칙**을 쓴다.
@@ -1099,6 +1132,43 @@ declare function imageRenderer(cell: SduiImageCell, key: string, width?: number
1099
1132
  */
1100
1133
  declare function cellRenderer(cell: SduiCell | undefined, key: string, dispatch: SduiDispatch): ((row: SearchRow) => React__default.ReactNode) | undefined;
1101
1134
 
1135
+ /**
1136
+ * 목록 위 합계 칸 — "지금 검색된 결과 전체"의 합(계약금액·공급가액·세액 같은)을 숫자 카드로.
1137
+ *
1138
+ * 값은 목록과 **같은 쿼리**로 따로 조회한다(`summary.endpoint`). 현재 페이지 행을 더하면
1139
+ * 10건짜리 부분합이 전체 합처럼 보이므로 로컬 합산은 하지 않는다 — 합은 서버가 낸다.
1140
+ * 쿼리에서 빠지는 것은 page/limit 둘뿐이다(`summaryParams`): 검색어·상세검색·필터·탭이
1141
+ * 전부 합계에도 걸려야 "검색된 결과에 따른 합"이 된다.
1142
+ */
1143
+
1144
+ interface SduiSearchSummaryItem {
1145
+ /** 응답 객체에서 읽을 필드 — 점 경로 허용 */
1146
+ key: string;
1147
+ label: string;
1148
+ /** 값 뒤 단위 — "원", "건" */
1149
+ unit?: string;
1150
+ icon?: string;
1151
+ /** 값 표현 — 컬럼 셀과 같은 어휘(주로 `{ type: 'number' }`). 생략하면 숫자는 천 단위 구분 */
1152
+ cell?: SduiCell;
1153
+ }
1154
+ interface SduiSearchSummary {
1155
+ /**
1156
+ * 합계 endpoint — 목록의 쿼리(page/limit 제외)가 그대로 붙는다. 응답은 객체 하나이고
1157
+ * 칸마다 `items[].key` 로 값을 꺼낸다.
1158
+ */
1159
+ endpoint: string;
1160
+ items: SduiSearchSummaryItem[];
1161
+ }
1162
+ /** 합계 조회에 넘길 쿼리 — 목록 쿼리에서 페이지 축(page/limit)만 뺀다. */
1163
+ declare function summaryParams(params: URLSearchParams): URLSearchParams;
1164
+ interface SearchSummaryCardsProps {
1165
+ items: SduiSearchSummaryItem[];
1166
+ /** 합계 응답 객체 — 아직 없으면 값 자리에 '—' */
1167
+ values: Record<string, unknown> | null | undefined;
1168
+ dispatch: SduiDispatch;
1169
+ }
1170
+ declare function SearchSummaryCards({ items, values, dispatch }: SearchSummaryCardsProps): React__default.JSX.Element | null;
1171
+
1102
1172
  /** JSON 컬럼 정의 — render 함수 대신 선언적 cell 스펙을 쓴다 */
1103
1173
  interface SduiSearchColumn {
1104
1174
  key: string;
@@ -1181,6 +1251,8 @@ interface SduiSearchCommonProps {
1181
1251
  fields?: SearchField[];
1182
1252
  leftFilters?: SearchFilter[];
1183
1253
  rightFilters?: SearchFilter[];
1254
+ /** 목록 맨 위의 구분 탭 — 한 쿼리 키의 값을 탭으로 고른다. 빈 값 탭 = 전체 */
1255
+ tabs?: SearchTabs;
1184
1256
  gridCard?: SduiGridCard;
1185
1257
  actions?: SduiSearchAction[];
1186
1258
  initialView?: 'grid' | 'list';
@@ -1235,6 +1307,11 @@ interface RemoteSearchResultsProps extends SduiSearchCommonProps {
1235
1307
  searchParams?: URLSearchParams;
1236
1308
  /** 파라미터 변경 통지 — searchParams 없이 쓰면 내부 상태를 유지한 채 관찰만 한다 */
1237
1309
  onParamsChange?: (next: URLSearchParams) => void;
1310
+ /**
1311
+ * 탭 위 합계 칸 — 목록과 같은 쿼리(page/limit 제외)로 `summary.endpoint` 를 조회해
1312
+ * 응답 객체의 `items[].key` 값을 숫자 카드로 보인다. 검색·상세검색·탭을 바꾸면 따라 바뀐다.
1313
+ */
1314
+ summary?: SduiSearchSummary;
1238
1315
  }
1239
1316
  /** 바깥에서 온 `style`·`className`을 뿌리에 얹어 내보낸다. 까닭은 {@link ../boxed.tsx}에 있다. */
1240
1317
  declare const SduiSearchResults: React__default.FC<SduiSearchResultsProps & SduiBoxProps>;
@@ -2085,4 +2162,4 @@ interface ImageViewerProps {
2085
2162
  */
2086
2163
  declare function ImageViewer({ src, alt, open, onClose }: ImageViewerProps): React__default.ReactPortal | null;
2087
2164
 
2088
- export { Actions, type ActionsProps, Alert, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeTone, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonProps, CALC_OPS, Can, type CanProps, Card, type CardProps, type CascadingSearchOption, Checkbox, type CheckboxProps, Col, type ColProps, type CustomActionHandler, type CustomValidator, DatePickerProps, DateRangePickerProps, Dialog, type DialogProps, Divider, EmptyState, type EmptyStateProps, Field, type FieldProps, Grid, type GridCardConfig, type GridCardRow, type GridProps, ICON_NAMES, Icon, IconButton, type IconButtonProps, type IconProps, ImageViewer, type ImageViewerProps, Input, type InputProps, type Justify, type KeyValueItem, KeyValueList, type KeyValueListProps, type MeterTone, NodeErrorBoundary, OverlayContainerContext, OverlayLayer, PageHead, type PageHeadProps, Pagination, type PaginationProps, ProgressBar, type ProgressBarProps, Radio, type RadioProps, type RemotePageState, RemoteSearchResults, type RemoteSearchResultsProps, type ResolvedPatternSpec, ResultTable, type ResultTableColumn, type ResultTableProps, Row, type RowProps, SduiAction, type SduiAdapter, type SduiAssetContext, type SduiAssetResolver, type SduiBadgeCell, SduiBoxProps, type SduiCanFn, type SduiCanQuery, type SduiCanSetting, type SduiCell, SduiColumnsScale, SduiContainerLayoutProps, type SduiContainerRoleProps, type SduiDateCell, SduiDialog, type SduiDispatch, SduiForm, type SduiFormConfig, type SduiFormProps, SduiGapScale, type SduiGridCard, type SduiGridCardAction, type SduiGridCardRow, type SduiIconCell, type SduiImageCell, type SduiLinkCell, type SduiNumberCell, SduiPage, SduiPermissionProvider, type SduiProgressCell, SduiPropValue, type SduiRegistry, SduiRenderer, type SduiRendererProps, type SduiRowCondition, SduiRule, type SduiRuntime, type SduiSearchAction, type SduiSearchColumn, SduiSearchDataProvider, SduiSearchInvalidationProvider, SduiSearchResults, type SduiSearchResultsProps, type SduiTagCell, SduiTextAlignScale, type SduiTextCell, type SduiUseSpec, SduiWeightScale, type SearchAction, type SearchColumn, type SearchFetcher, type SearchField, type SearchFilter, type SearchInvalidator, type SearchOption, type SearchPage, SearchResults, type SearchResultsProps, type SearchRow, SearchToolbar, type SearchValues, type SearchViewItem, Section, type SectionProps, SegmentedControl, type SegmentedControlProps, type SegmentedItem, Select, type SelectOption, type SelectOptionRecord, type SelectProps, Show, type ShowProps, SidebarNav, type SidebarNavItem, type SidebarNavProps, type SidebarNavSection, type SortDir, SortHandle, type SortHandleProps, SortableList, type SortableListProps, Spacer, type SpacerProps, type SpecIdCandidate, type SpecIssue, Spinner, type SpinnerProps, StatCard, type StatCardProps, Switch, type SwitchProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Tag, type TagProps, Text, type TextPreset, type TextProps, Textarea, type TextareaProps, TimePickerProps, Toast, type ToastProps, Tooltip, type TooltipProps, boxed, cascadingValuePaths, cellRenderer, cleanSearchValues, createRegistry, createSearchInvalidator, defaultAdapter, defaultRegistry, getOverlayRoot, imageRenderer, includesLooseSearch, isValidSpecId, joinMultiValue, loadOptionalModule, mapActions, mapCardRows, mapColumns, mapGridCard, mapViews, matchesCondition, normalizePageSizes, normalizeSearchText, patternSpecCandidates, resolvePatternSpec, searchFieldKeys, searchValuesFromParams, sortableDropIndex, sortableMove, splitMultiValue, useOverlayLayer, useRemoteSearchPage, useSduiAssetResolver, useSduiCan, useSduiDispatch, useSduiRuntime, useSduiSandbox, useSearchFetcher, validateSpec, withProps };
2165
+ export { Actions, type ActionsProps, Alert, type AlertProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeTone, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, type ButtonProps, CALC_OPS, Can, type CanProps, Card, type CardProps, type CascadingSearchOption, Checkbox, type CheckboxProps, Col, type ColProps, type CustomActionHandler, type CustomValidator, DatePickerProps, DateRangePickerProps, Dialog, type DialogProps, Divider, EmptyState, type EmptyStateProps, Field, type FieldProps, Grid, type GridCardConfig, type GridCardRow, type GridProps, ICON_NAMES, Icon, IconButton, type IconButtonProps, type IconProps, ImageViewer, type ImageViewerProps, Input, type InputProps, type Justify, type KeyValueItem, KeyValueList, type KeyValueListProps, type MeterTone, NodeErrorBoundary, OverlayContainerContext, OverlayLayer, PageHead, type PageHeadProps, Pagination, type PaginationProps, ProgressBar, type ProgressBarProps, Radio, type RadioProps, type RemotePageState, RemoteSearchResults, type RemoteSearchResultsProps, type RemoteSummaryState, type ResolvedPatternSpec, ResultTable, type ResultTableColumn, type ResultTableProps, Row, type RowProps, SduiAction, type SduiAdapter, type SduiAssetContext, type SduiAssetResolver, type SduiBadgeCell, SduiBoxProps, type SduiCanFn, type SduiCanQuery, type SduiCanSetting, type SduiCell, SduiColumnsScale, SduiContainerLayoutProps, type SduiContainerRoleProps, type SduiDateCell, SduiDialog, type SduiDispatch, SduiForm, type SduiFormConfig, type SduiFormProps, SduiGapScale, type SduiGridCard, type SduiGridCardAction, type SduiGridCardRow, type SduiIconCell, type SduiImageCell, type SduiLinkCell, type SduiNumberCell, SduiPage, SduiPermissionProvider, type SduiProgressCell, SduiPropValue, type SduiRegistry, SduiRenderer, type SduiRendererProps, type SduiRowCondition, SduiRule, type SduiRuntime, type SduiSearchAction, type SduiSearchColumn, SduiSearchDataProvider, SduiSearchInvalidationProvider, SduiSearchResults, type SduiSearchResultsProps, type SduiSearchSummary, type SduiSearchSummaryItem, type SduiTagCell, SduiTextAlignScale, type SduiTextCell, type SduiUseSpec, SduiWeightScale, type SearchAction, type SearchColumn, type SearchFetcher, type SearchField, type SearchFilter, type SearchInvalidator, type SearchOption, type SearchPage, SearchResults, type SearchResultsProps, type SearchRow, SearchSummaryCards, type SearchSummaryCardsProps, type SearchTabs, SearchToolbar, type SearchValues, type SearchViewItem, Section, type SectionProps, SegmentedControl, type SegmentedControlProps, type SegmentedItem, Select, type SelectOption, type SelectOptionRecord, type SelectProps, Show, type ShowProps, SidebarNav, type SidebarNavItem, type SidebarNavProps, type SidebarNavSection, type SortDir, SortHandle, type SortHandleProps, SortableList, type SortableListProps, Spacer, type SpacerProps, type SpecIdCandidate, type SpecIssue, Spinner, type SpinnerProps, StatCard, type StatCardProps, Switch, type SwitchProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Tag, type TagProps, Text, type TextPreset, type TextProps, Textarea, type TextareaProps, TimePickerProps, Toast, type ToastProps, Tooltip, type TooltipProps, boxed, cascadingValuePaths, cellRenderer, cleanSearchValues, createRegistry, createSearchInvalidator, defaultAdapter, defaultRegistry, getOverlayRoot, imageRenderer, includesLooseSearch, isValidSpecId, joinMultiValue, loadOptionalModule, mapActions, mapCardRows, mapColumns, mapGridCard, mapViews, matchesCondition, normalizePageSizes, normalizeSearchText, patternSpecCandidates, resolvePatternSpec, searchFieldKeys, searchValuesFromParams, sortableDropIndex, sortableMove, splitMultiValue, summaryParams, useOverlayLayer, useRemoteSearchPage, useRemoteSearchSummary, useSduiAssetResolver, useSduiCan, useSduiDispatch, useSduiRuntime, useSduiSandbox, useSearchFetcher, validateSpec, withProps };
package/dist/index.js CHANGED
@@ -11,9 +11,9 @@ import { parseYmd, YMD_RE, TimePicker, DateRangePicker, DatePicker } from './chu
11
11
  export { DatePicker, DateRangePicker, TimePicker } from './chunk-RWSH2MBU.js';
12
12
  import { HoistedStyle } from './chunk-RIZRM7OO.js';
13
13
  export { OverlayContainerContext, OverlayLayer, getOverlayRoot, useOverlayLayer } from './chunk-XQPTSEJC.js';
14
- import { DEV, isSafeNavigationTarget, useSduiAdapter, hostFetchOf, useSduiSandbox, resolveAdapter, assetResolverOf, SduiAdapterContext } from './chunk-KO6OEY3G.js';
14
+ import { DEV, useSduiAdapter, hostFetchOf, isSafeNavigationTarget, useSduiSandbox, resolveAdapter, assetResolverOf, SduiAdapterContext } from './chunk-KO6OEY3G.js';
15
15
  export { defaultAdapter, useSduiAssetResolver, useSduiSandbox } from './chunk-KO6OEY3G.js';
16
- import { EmptyState, Button, Checkbox, SegmentedControl, Spinner, Select, Pagination, fieldOf, Icon, Card, Badge, ProgressBar, Tag, Toast, Textarea, Tabs, Table, Switch, StatCard, Input, IconButton, Field, Alert, warnOnce, isPlainObject, CALC_OPS, evaluateCalc, ICON_NODES, toNumber } from './chunk-RNOUXUBO.js';
16
+ import { EmptyState, Button, Checkbox, Tabs, SegmentedControl, Spinner, Select, Pagination, fieldOf, StatCard, Icon, Card, Badge, ProgressBar, Tag, Toast, Textarea, Table, Switch, Input, IconButton, Field, Alert, warnOnce, isPlainObject, CALC_OPS, evaluateCalc, ICON_NODES, toNumber } from './chunk-RNOUXUBO.js';
17
17
  export { Alert, Avatar, Badge, Breadcrumb, Button, CALC_OPS, 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-RNOUXUBO.js';
18
18
  import { boxed, LAYOUT_CSS, childLayoutFrom, CHILD_LAYOUT_PROPS } from './chunk-5WW4MBRO.js';
19
19
  export { CHILD_LAYOUT_PROPS, LAYOUT_CSS, boxed, childLayoutFrom, columnsClass, containerLayoutStyle, joinClass, mergeStyle, typographyStyle } from './chunk-5WW4MBRO.js';
@@ -471,6 +471,78 @@ function useRemoteSearchPage(endpoint, params) {
471
471
  const reload = useCallback(() => setReloadKey((value) => value + 1), []);
472
472
  return { page, isLoading, error, reload };
473
473
  }
474
+ function useRemoteSearchSummary(endpoint, params) {
475
+ const adapter = useSduiAdapter();
476
+ const invalidator = useContext(SearchInvalidationContext);
477
+ const [values, setValues] = useState(null);
478
+ const [isLoading, setIsLoading] = useState(endpoint !== null);
479
+ const [error, setError] = useState(null);
480
+ const [reloadKey, setReloadKey] = useState(0);
481
+ useEffect(() => {
482
+ if (!invalidator || endpoint === null) return void 0;
483
+ return invalidator.subscribe(endpoint, () => setReloadKey((value) => value + 1));
484
+ }, [invalidator, endpoint]);
485
+ const query = params.toString();
486
+ const hostFetchRef = useRef(hostFetchOf(adapter));
487
+ hostFetchRef.current = hostFetchOf(adapter);
488
+ useEffect(() => {
489
+ if (endpoint === null) return void 0;
490
+ const controller = new AbortController();
491
+ let alive = true;
492
+ const url = searchUrl(endpoint, new URLSearchParams(query));
493
+ setIsLoading(true);
494
+ const hostFetch = hostFetchRef.current;
495
+ const request = hostFetch ? hostFetch(url, { method: "GET", signal: controller.signal }) : fetch(url, { headers: { accept: "application/json" }, credentials: "same-origin", signal: controller.signal }).then(
496
+ async (response) => {
497
+ const body = await response.json().catch(() => null);
498
+ if (!response.ok) throw new Error(body?.message ?? `\uD569\uACC4\uB97C \uBD88\uB7EC\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 (${response.status})`);
499
+ return body;
500
+ }
501
+ );
502
+ request.then(
503
+ (body) => {
504
+ if (!alive) return;
505
+ setValues(body && typeof body === "object" && !Array.isArray(body) ? body : null);
506
+ setError(null);
507
+ setIsLoading(false);
508
+ },
509
+ (cause) => {
510
+ if (!alive || controller.signal.aborted) return;
511
+ setError(cause instanceof Error ? cause : new Error("\uD569\uACC4\uB97C \uBD88\uB7EC\uC624\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4"));
512
+ setIsLoading(false);
513
+ }
514
+ );
515
+ return () => {
516
+ alive = false;
517
+ controller.abort();
518
+ };
519
+ }, [endpoint, query, reloadKey]);
520
+ return { values, isLoading, error };
521
+ }
522
+ function summaryParams(params) {
523
+ const next = new URLSearchParams(params);
524
+ next.delete("page");
525
+ next.delete("limit");
526
+ return next;
527
+ }
528
+ function plainValue(value) {
529
+ if (value === null || value === void 0 || value === "") return "\u2014";
530
+ if (typeof value === "number") return value.toLocaleString("ko-KR");
531
+ if (typeof value === "string" && /^-?\d+(\.\d+)?$/.test(value)) return Number(value).toLocaleString("ko-KR");
532
+ return String(value);
533
+ }
534
+ function SearchSummaryCards({ items, values, dispatch }) {
535
+ const list = Array.isArray(items) ? items.filter((item) => item && typeof item.key === "string") : [];
536
+ if (list.length === 0) return null;
537
+ const columns = Math.min(Math.max(list.length, 2), 4);
538
+ const row = values ?? {};
539
+ return /* @__PURE__ */ jsx("div", { className: `wp-grid wp-grid--${columns}`, "aria-label": "\uAC80\uC0C9 \uACB0\uACFC \uD569\uACC4", children: list.map((item) => {
540
+ const render = item.cell ? cellRenderer(item.cell, item.key, dispatch) : void 0;
541
+ const raw = values ? fieldOf(row, item.key) : void 0;
542
+ const value = values && render && raw !== void 0 && raw !== null ? render(row) : plainValue(raw);
543
+ return /* @__PURE__ */ jsx(StatCard, { size: "sm", label: item.label, icon: item.icon, value, unit: item.unit }, item.key);
544
+ }) });
545
+ }
474
546
  var OVERSCAN = 10;
475
547
  var CLIP_CSS = `.wp-table--clip tbody td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}`;
476
548
  function ResultTable({
@@ -606,6 +678,8 @@ function SearchResults({
606
678
  gridConfig,
607
679
  leftFilters = EMPTY_FILTERS,
608
680
  rightFilters = EMPTY_FILTERS,
681
+ tabs,
682
+ summary,
609
683
  initialView = "list",
610
684
  views,
611
685
  pageSizes,
@@ -668,8 +742,12 @@ function SearchResults({
668
742
  const values = splitMultiValue(searchParams.get(filter.key) ?? "");
669
743
  if (values.length > 0) entries.push({ key: filter.key, values });
670
744
  }
745
+ if (tabs && typeof tabs.key === "string") {
746
+ const values = splitMultiValue(searchParams.get(tabs.key) ?? "");
747
+ if (values.length > 0) entries.push({ key: tabs.key, values });
748
+ }
671
749
  return entries;
672
- }, [leftFilters, rightFilters, searchParams]);
750
+ }, [leftFilters, rightFilters, tabs, searchParams]);
673
751
  const hasAllRows = item.data.length >= item.total;
674
752
  const processed = useMemo(() => {
675
753
  const cell = (row, key) => fieldOf(row, key);
@@ -708,7 +786,8 @@ function SearchResults({
708
786
  if (max !== null) rows = rows.filter((row) => (numericValue(cell(row, field.key)) ?? Infinity) <= max);
709
787
  } else {
710
788
  const keyword = advancedValues[field.key];
711
- const baseKey = field.key.endsWith("Contains") ? field.key.slice(0, -"Contains".length) : field.key;
789
+ const suffix = ["_contains", "Contains"].find((s) => field.key.endsWith(s));
790
+ const baseKey = suffix ? field.key.slice(0, -suffix.length) : field.key;
712
791
  if (keyword) rows = rows.filter((row) => includesLooseSearch(String(cell(row, baseKey) ?? ""), keyword));
713
792
  }
714
793
  }
@@ -804,6 +883,16 @@ function SearchResults({
804
883
  };
805
884
  const empty = /* @__PURE__ */ jsx(ResultEmptyState, { title });
806
885
  return /* @__PURE__ */ jsxs("div", { className: ["wp-col", className].filter(Boolean).join(" "), style, "aria-label": title, children: [
886
+ summary ?? null,
887
+ tabs && Array.isArray(tabs.items) && tabs.items.length > 0 ? /* @__PURE__ */ jsx(
888
+ Tabs,
889
+ {
890
+ "aria-label": `${title} \uAD6C\uBD84`,
891
+ items: tabs.items.map((tab) => ({ value: tab.value ?? "", label: tab.label, icon: tab.icon })),
892
+ value: searchParams.get(tabs.key) ?? "",
893
+ onChange: (value) => updateParams({ [tabs.key]: value || null, page: null })
894
+ }
895
+ ) : null,
807
896
  !hideHeader ? /* @__PURE__ */ jsxs("div", { className: "wp-row wp-row--between", style: { flexWrap: "wrap" }, children: [
808
897
  /* @__PURE__ */ jsxs("span", { className: "wp-caption", children: [
809
898
  "\uCD1D ",
@@ -1140,6 +1229,7 @@ function SduiSearchResultsImpl({
1140
1229
  fields,
1141
1230
  leftFilters,
1142
1231
  rightFilters,
1232
+ tabs,
1143
1233
  gridCard,
1144
1234
  actions,
1145
1235
  initialView,
@@ -1164,6 +1254,7 @@ function SduiSearchResultsImpl({
1164
1254
  gridConfig: mapGridCard(gridCard, dispatch),
1165
1255
  leftFilters,
1166
1256
  rightFilters,
1257
+ tabs,
1167
1258
  actions: mapActions(actions, dispatch),
1168
1259
  initialView,
1169
1260
  views: mapViews(views, gridCard, disableGrid, disableList),
@@ -1187,6 +1278,7 @@ function RemoteSearchResultsImpl({
1187
1278
  fields,
1188
1279
  leftFilters,
1189
1280
  rightFilters,
1281
+ tabs,
1190
1282
  gridCard,
1191
1283
  actions,
1192
1284
  initialView,
@@ -1200,7 +1292,8 @@ function RemoteSearchResultsImpl({
1200
1292
  virtual,
1201
1293
  pageSize,
1202
1294
  searchParams: controlledParams,
1203
- onParamsChange
1295
+ onParamsChange,
1296
+ summary
1204
1297
  }) {
1205
1298
  const dispatch = useSduiDispatch();
1206
1299
  const defaultPageSize = normalizePageSizes(pageSizes)?.[0] ?? 10;
@@ -1221,6 +1314,11 @@ function RemoteSearchResultsImpl({
1221
1314
  return next;
1222
1315
  }, [params, defaultPageSize]);
1223
1316
  const { page, isLoading, error, reload } = useRemoteSearchPage(endpoint, query);
1317
+ const summaryQuery = useMemo(() => summaryParams(params), [params]);
1318
+ const summaryState = useRemoteSearchSummary(
1319
+ summary && typeof summary.endpoint === "string" ? summary.endpoint : null,
1320
+ summaryQuery
1321
+ );
1224
1322
  if (error && page.data.length === 0) {
1225
1323
  return /* @__PURE__ */ jsx(
1226
1324
  EmptyState,
@@ -1241,6 +1339,7 @@ function RemoteSearchResultsImpl({
1241
1339
  gridConfig: mapGridCard(gridCard, dispatch),
1242
1340
  leftFilters,
1243
1341
  rightFilters,
1342
+ tabs,
1244
1343
  actions: mapActions(actions, dispatch),
1245
1344
  initialView,
1246
1345
  views: mapViews(views, gridCard, disableGrid, disableList),
@@ -1252,6 +1351,7 @@ function RemoteSearchResultsImpl({
1252
1351
  headerActions,
1253
1352
  virtual,
1254
1353
  isLoading,
1354
+ summary: summary ? /* @__PURE__ */ jsx(SearchSummaryCards, { items: summary.items, values: summaryState.values, dispatch }) : void 0,
1255
1355
  onRowClick: rowClick ? (row) => toList(rowClick).forEach((a) => dispatch(a, row)) : void 0,
1256
1356
  searchParams: params,
1257
1357
  onParamsChange: handleParamsChange
@@ -3119,4 +3219,4 @@ async function resolvePatternSpec(id, load) {
3119
3219
  // src/icon-names.ts
3120
3220
  var ICON_NAMES = Object.keys(ICON_NODES);
3121
3221
 
3122
- export { Can, ICON_NAMES, NodeErrorBoundary, RemoteSearchResults, ResultTable, SduiForm, SduiRenderer, SduiSearchDataProvider, SduiSearchInvalidationProvider, SduiSearchResults, SearchResults, SortHandle, SortableList, cellRenderer, createRegistry, createSearchInvalidator, defaultRegistry, imageRenderer, isValidSpecId, mapActions, mapCardRows, mapColumns, mapGridCard, mapViews, matchesCondition, normalizePageSizes, patternSpecCandidates, resolvePatternSpec, sortableDropIndex, sortableMove, useRemoteSearchPage, useSduiRuntime, useSearchFetcher, validateSpec, withProps };
3222
+ export { Can, ICON_NAMES, NodeErrorBoundary, RemoteSearchResults, ResultTable, SduiForm, SduiRenderer, SduiSearchDataProvider, SduiSearchInvalidationProvider, SduiSearchResults, SearchResults, SearchSummaryCards, SortHandle, SortableList, cellRenderer, createRegistry, createSearchInvalidator, defaultRegistry, imageRenderer, isValidSpecId, mapActions, mapCardRows, mapColumns, mapGridCard, mapViews, matchesCondition, normalizePageSizes, patternSpecCandidates, resolvePatternSpec, sortableDropIndex, sortableMove, summaryParams, useRemoteSearchPage, useRemoteSearchSummary, useSduiRuntime, useSearchFetcher, validateSpec, withProps };
package/dist/meta.js CHANGED
@@ -2174,6 +2174,27 @@ var searchCommonProps = [
2174
2174
  { name: "options", kind: "array", label: "\uC635\uC158", description: "{ key, label, value }" }
2175
2175
  ]
2176
2176
  },
2177
+ {
2178
+ name: "tabs",
2179
+ kind: "object",
2180
+ label: "\uAD6C\uBD84 \uD0ED",
2181
+ description: "\uBAA9\uB85D \uB9E8 \uC704(\uD234\uBC14 \uC704)\uC758 \uD0ED. \uACE0\uB978 \uD0ED\uC758 value\uAC00 key \uD30C\uB77C\uBBF8\uD130\uAC00 \uB418\uACE0 \uBE48 value \uD0ED\uC740 \uD30C\uB77C\uBBF8\uD130\uB97C \uC9C0\uC6B4\uB2E4(= \uC804\uCCB4). value\uC5D0 \uC27C\uD45C\uB97C \uB123\uC73C\uBA74 \uC5EC\uB7EC \uAC12 \uC911 \uD558\uB098. \uD0ED\uC744 \uBC14\uAFB8\uBA74 1\uD398\uC774\uC9C0\uB85C \uB3CC\uC544\uAC04\uB2E4.",
2182
+ group: "data",
2183
+ fields: [
2184
+ { name: "key", kind: "string", label: "\uCFFC\uB9AC \uD0A4", required: true },
2185
+ {
2186
+ name: "items",
2187
+ kind: "array",
2188
+ label: "\uD0ED",
2189
+ required: true,
2190
+ item: [
2191
+ { name: "value", kind: "string", label: "\uAC12", description: "\uBE48 \uBB38\uC790\uC5F4 = \uC804\uCCB4", required: true },
2192
+ { name: "label", kind: "string", label: "\uB77C\uBCA8", required: true },
2193
+ { name: "icon", kind: "icon", label: "\uC544\uC774\uCF58" }
2194
+ ]
2195
+ }
2196
+ ]
2197
+ },
2177
2198
  {
2178
2199
  name: "gridCard",
2179
2200
  kind: "object",
@@ -2360,6 +2381,35 @@ var RemoteSearchResultsMeta = {
2360
2381
  group: "data"
2361
2382
  },
2362
2383
  { name: "pageSize", kind: "number", label: "\uD398\uC774\uC9C0\uB2F9 \uD589 \uC218", default: 10, group: "data" },
2384
+ {
2385
+ name: "summary",
2386
+ kind: "object",
2387
+ label: "\uD569\uACC4 \uCE78",
2388
+ description: "\uD0ED \uC704 \uC22B\uC790 \uCE74\uB4DC. \uBAA9\uB85D\uACFC \uAC19\uC740 \uCFFC\uB9AC(page/limit \uC81C\uC678)\uB85C endpoint\uB97C \uC870\uD68C\uD574 \uC751\uB2F5 \uAC1D\uCCB4\uC758 items[].key \uAC12\uC744 \uBCF4\uC778\uB2E4 \u2014 \uAC80\uC0C9\xB7\uC0C1\uC138\uAC80\uC0C9\xB7\uD0ED\uC744 \uB530\uB77C \uBC14\uB010\uB2E4.",
2389
+ group: "data",
2390
+ fields: [
2391
+ { name: "endpoint", kind: "string", label: "\uD569\uACC4 endpoint", required: true },
2392
+ {
2393
+ name: "items",
2394
+ kind: "array",
2395
+ label: "\uCE78",
2396
+ required: true,
2397
+ item: [
2398
+ { name: "key", kind: "string", label: "\uC751\uB2F5 \uD544\uB4DC", required: true },
2399
+ { name: "label", kind: "string", label: "\uB77C\uBCA8", required: true },
2400
+ { name: "unit", kind: "string", label: "\uB2E8\uC704" },
2401
+ { name: "icon", kind: "icon", label: "\uC544\uC774\uCF58" },
2402
+ {
2403
+ name: "cell",
2404
+ kind: "object",
2405
+ label: "\uAC12 \uD45C\uD604",
2406
+ description: "\uCEEC\uB7FC \uC140\uACFC \uAC19\uC740 \uC5B4\uD718. \uC0DD\uB7B5\uD558\uBA74 \uC22B\uC790\uB294 \uCC9C \uB2E8\uC704 \uAD6C\uBD84.",
2407
+ fields: searchCellFields
2408
+ }
2409
+ ]
2410
+ }
2411
+ ]
2412
+ },
2363
2413
  ...searchCommonProps
2364
2414
  ],
2365
2415
  slots: searchSlots,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipco/sdui",
3
- "version": "1.4.2",
3
+ "version": "1.5.0",
4
4
  "description": "WIPCO 서버 주도 UI(SDUI) 렌더러 — JSON 페이지 스펙을 React 컴포넌트로 렌더링",
5
5
  "keywords": [
6
6
  "sdui",