kokotable 0.95.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/README.md +320 -0
- package/contract/table-request.schema.json +424 -0
- package/dist/core/src/adapters/sources.d.ts +59 -0
- package/dist/core/src/adapters/sources.js +235 -0
- package/dist/core/src/client/aggregate.d.ts +94 -0
- package/dist/core/src/client/aggregate.js +222 -0
- package/dist/core/src/client/groupRows.d.ts +40 -0
- package/dist/core/src/client/groupRows.js +145 -0
- package/dist/core/src/client/runClientQuery.d.ts +67 -0
- package/dist/core/src/client/runClientQuery.js +157 -0
- package/dist/core/src/columns/ColumnBuilder.d.ts +121 -0
- package/dist/core/src/columns/ColumnBuilder.js +273 -0
- package/dist/core/src/columns/cascade.d.ts +24 -0
- package/dist/core/src/columns/cascade.js +93 -0
- package/dist/core/src/columns/deriveOperators.d.ts +38 -0
- package/dist/core/src/columns/deriveOperators.js +94 -0
- package/dist/core/src/columns/expandSorting.d.ts +19 -0
- package/dist/core/src/columns/expandSorting.js +52 -0
- package/dist/core/src/columns/helper.d.ts +196 -0
- package/dist/core/src/columns/helper.js +250 -0
- package/dist/core/src/columns/labels.d.ts +12 -0
- package/dist/core/src/columns/labels.js +21 -0
- package/dist/core/src/contract.d.ts +12 -0
- package/dist/core/src/contract.js +12 -0
- package/dist/core/src/contracts/aiColumnMeta.d.ts +102 -0
- package/dist/core/src/contracts/aiColumnMeta.js +197 -0
- package/dist/core/src/contracts/describeColumns.d.ts +48 -0
- package/dist/core/src/contracts/describeColumns.js +40 -0
- package/dist/core/src/contracts/exportData.d.ts +150 -0
- package/dist/core/src/contracts/exportData.js +228 -0
- package/dist/core/src/contracts/index.d.ts +6 -0
- package/dist/core/src/contracts/index.js +12 -0
- package/dist/core/src/contracts/legacyUrl.d.ts +47 -0
- package/dist/core/src/contracts/legacyUrl.js +156 -0
- package/dist/core/src/contracts/preset.d.ts +104 -0
- package/dist/core/src/contracts/preset.js +141 -0
- package/dist/core/src/contracts/presetStore.d.ts +81 -0
- package/dist/core/src/contracts/presetStore.js +146 -0
- package/dist/core/src/contracts/usePresets.d.ts +51 -0
- package/dist/core/src/contracts/usePresets.js +170 -0
- package/dist/core/src/defaults.d.ts +7 -0
- package/dist/core/src/defaults.js +9 -0
- package/dist/core/src/filters/codec.d.ts +20 -0
- package/dist/core/src/filters/codec.js +61 -0
- package/dist/core/src/filters/date.d.ts +30 -0
- package/dist/core/src/filters/date.js +179 -0
- package/dist/core/src/filters/describe.d.ts +45 -0
- package/dist/core/src/filters/describe.js +189 -0
- package/dist/core/src/filters/kind.d.ts +52 -0
- package/dist/core/src/filters/kind.js +110 -0
- package/dist/core/src/filters/kinds/_list.d.ts +13 -0
- package/dist/core/src/filters/kinds/_list.js +42 -0
- package/dist/core/src/filters/kinds/boolean.d.ts +3 -0
- package/dist/core/src/filters/kinds/boolean.js +21 -0
- package/dist/core/src/filters/kinds/cascade.d.ts +6 -0
- package/dist/core/src/filters/kinds/cascade.js +16 -0
- package/dist/core/src/filters/kinds/commonCode.d.ts +3 -0
- package/dist/core/src/filters/kinds/commonCode.js +13 -0
- package/dist/core/src/filters/kinds/date.d.ts +9 -0
- package/dist/core/src/filters/kinds/date.js +32 -0
- package/dist/core/src/filters/kinds/enum.d.ts +4 -0
- package/dist/core/src/filters/kinds/enum.js +13 -0
- package/dist/core/src/filters/kinds/index.d.ts +49 -0
- package/dist/core/src/filters/kinds/index.js +39 -0
- package/dist/core/src/filters/kinds/linear.d.ts +5 -0
- package/dist/core/src/filters/kinds/linear.js +16 -0
- package/dist/core/src/filters/kinds/number.d.ts +5 -0
- package/dist/core/src/filters/kinds/number.js +19 -0
- package/dist/core/src/filters/kinds/period.d.ts +4 -0
- package/dist/core/src/filters/kinds/period.js +17 -0
- package/dist/core/src/filters/kinds/relation.d.ts +5 -0
- package/dist/core/src/filters/kinds/relation.js +17 -0
- package/dist/core/src/filters/kinds/schedule.d.ts +5 -0
- package/dist/core/src/filters/kinds/schedule.js +16 -0
- package/dist/core/src/filters/kinds/text.d.ts +4 -0
- package/dist/core/src/filters/kinds/text.js +18 -0
- package/dist/core/src/filters/linear.d.ts +33 -0
- package/dist/core/src/filters/linear.js +65 -0
- package/dist/core/src/filters/list.d.ts +32 -0
- package/dist/core/src/filters/list.js +87 -0
- package/dist/core/src/filters/number.d.ts +17 -0
- package/dist/core/src/filters/number.js +82 -0
- package/dist/core/src/filters/registry.d.ts +23 -0
- package/dist/core/src/filters/registry.js +121 -0
- package/dist/core/src/filters/relation.d.ts +26 -0
- package/dist/core/src/filters/relation.js +75 -0
- package/dist/core/src/filters/schedule.d.ts +21 -0
- package/dist/core/src/filters/schedule.js +60 -0
- package/dist/core/src/filters/text.d.ts +44 -0
- package/dist/core/src/filters/text.js +127 -0
- package/dist/core/src/hooks/index.d.ts +6 -0
- package/dist/core/src/hooks/index.js +6 -0
- package/dist/core/src/hooks/useDataTable.d.ts +13 -0
- package/dist/core/src/hooks/useDataTable.js +109 -0
- package/dist/core/src/hooks/useGrouping.d.ts +33 -0
- package/dist/core/src/hooks/useGrouping.js +57 -0
- package/dist/core/src/hooks/useSearchHistory.d.ts +20 -0
- package/dist/core/src/hooks/useSearchHistory.js +52 -0
- package/dist/core/src/hooks/useTableData.d.ts +25 -0
- package/dist/core/src/hooks/useTableData.js +49 -0
- package/dist/core/src/hooks/useTableFromDefinition.d.ts +47 -0
- package/dist/core/src/hooks/useTableFromDefinition.js +174 -0
- package/dist/core/src/hooks/useTableSearch.d.ts +10 -0
- package/dist/core/src/hooks/useTableSearch.js +434 -0
- package/dist/core/src/hooks/useUrlSync.d.ts +64 -0
- package/dist/core/src/hooks/useUrlSync.js +145 -0
- package/dist/core/src/index.d.ts +38 -0
- package/dist/core/src/index.js +43 -0
- package/dist/core/src/pivot/runClientPivot.d.ts +101 -0
- package/dist/core/src/pivot/runClientPivot.js +230 -0
- package/dist/core/src/pivot/transforms.d.ts +19 -0
- package/dist/core/src/pivot/transforms.js +105 -0
- package/dist/core/src/registry.d.ts +46 -0
- package/dist/core/src/registry.js +67 -0
- package/dist/core/src/types/column.d.ts +213 -0
- package/dist/core/src/types/column.js +1 -0
- package/dist/core/src/types/data.d.ts +63 -0
- package/dist/core/src/types/data.js +1 -0
- package/dist/core/src/types/definition.d.ts +239 -0
- package/dist/core/src/types/definition.js +59 -0
- package/dist/core/src/types/filter.d.ts +94 -0
- package/dist/core/src/types/filter.js +1 -0
- package/dist/core/src/types/filterKind.d.ts +204 -0
- package/dist/core/src/types/filterKind.js +1 -0
- package/dist/core/src/types/grouping.d.ts +80 -0
- package/dist/core/src/types/grouping.js +4 -0
- package/dist/core/src/types/index.d.ts +8 -0
- package/dist/core/src/types/index.js +8 -0
- package/dist/core/src/types/operators.d.ts +64 -0
- package/dist/core/src/types/operators.js +108 -0
- package/dist/core/src/types/request.d.ts +114 -0
- package/dist/core/src/types/request.js +1 -0
- package/dist/core/src/types/search.d.ts +112 -0
- package/dist/core/src/types/search.js +1 -0
- package/dist/core/src/types/sources.d.ts +84 -0
- package/dist/core/src/types/sources.js +1 -0
- package/dist/core/src/types/table.d.ts +40 -0
- package/dist/core/src/types/table.js +1 -0
- package/dist/core/src/utils/compare.d.ts +13 -0
- package/dist/core/src/utils/compare.js +53 -0
- package/dist/core/src/utils/dateText.d.ts +12 -0
- package/dist/core/src/utils/dateText.js +30 -0
- package/dist/core/src/utils/getValue.d.ts +9 -0
- package/dist/core/src/utils/getValue.js +32 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/mui/src/TableLayout.d.ts +75 -0
- package/dist/mui/src/TableLayout.js +162 -0
- package/dist/mui/src/TableScreen.d.ts +70 -0
- package/dist/mui/src/TableScreen.js +174 -0
- package/dist/mui/src/addons/FilterModal.d.ts +45 -0
- package/dist/mui/src/addons/FilterModal.js +39 -0
- package/dist/mui/src/addons/FilterStatusModal.d.ts +40 -0
- package/dist/mui/src/addons/FilterStatusModal.js +68 -0
- package/dist/mui/src/addons/PresetBar.d.ts +56 -0
- package/dist/mui/src/addons/PresetBar.js +145 -0
- package/dist/mui/src/addons/ShareDropdown.d.ts +35 -0
- package/dist/mui/src/addons/ShareDropdown.js +41 -0
- package/dist/mui/src/addons/searchStore.d.ts +44 -0
- package/dist/mui/src/addons/searchStore.js +112 -0
- package/dist/mui/src/addons/searchSummary.d.ts +106 -0
- package/dist/mui/src/addons/searchSummary.js +109 -0
- package/dist/mui/src/columns/displayColumns.d.ts +100 -0
- package/dist/mui/src/columns/displayColumns.js +174 -0
- package/dist/mui/src/content/CardContent.d.ts +24 -0
- package/dist/mui/src/content/CardContent.js +25 -0
- package/dist/mui/src/content/ColumnMenu.d.ts +43 -0
- package/dist/mui/src/content/ColumnMenu.js +40 -0
- package/dist/mui/src/content/ColumnVisibilityMenu.d.ts +14 -0
- package/dist/mui/src/content/ColumnVisibilityMenu.js +37 -0
- package/dist/mui/src/content/EditCell.d.ts +19 -0
- package/dist/mui/src/content/EditCell.js +61 -0
- package/dist/mui/src/content/HeaderCell.d.ts +59 -0
- package/dist/mui/src/content/HeaderCell.js +88 -0
- package/dist/mui/src/content/SelectionActionBar.d.ts +17 -0
- package/dist/mui/src/content/SelectionActionBar.js +19 -0
- package/dist/mui/src/content/SortControl.d.ts +17 -0
- package/dist/mui/src/content/SortControl.js +45 -0
- package/dist/mui/src/content/TableContent.d.ts +191 -0
- package/dist/mui/src/content/TableContent.js +525 -0
- package/dist/mui/src/content/TableErrorFallback.d.ts +24 -0
- package/dist/mui/src/content/TableErrorFallback.js +29 -0
- package/dist/mui/src/content/cellError.d.ts +13 -0
- package/dist/mui/src/content/cellError.js +20 -0
- package/dist/mui/src/content/cellRenderers.d.ts +71 -0
- package/dist/mui/src/content/cellRenderers.js +127 -0
- package/dist/mui/src/content/cellValue.d.ts +27 -0
- package/dist/mui/src/content/cellValue.js +82 -0
- package/dist/mui/src/content/columnOrder.d.ts +7 -0
- package/dist/mui/src/content/columnOrder.js +48 -0
- package/dist/mui/src/content/density.d.ts +13 -0
- package/dist/mui/src/content/density.js +12 -0
- package/dist/mui/src/content/menuItems.d.ts +118 -0
- package/dist/mui/src/content/menuItems.js +128 -0
- package/dist/mui/src/content/pinning.d.ts +17 -0
- package/dist/mui/src/content/pinning.js +65 -0
- package/dist/mui/src/exporters/index.d.ts +1 -0
- package/dist/mui/src/exporters/index.js +1 -0
- package/dist/mui/src/exporters/useExportFormatters.d.ts +13 -0
- package/dist/mui/src/exporters/useExportFormatters.js +17 -0
- package/dist/mui/src/filter/FilterArea.d.ts +40 -0
- package/dist/mui/src/filter/FilterArea.js +37 -0
- package/dist/mui/src/filter/FilterInput.d.ts +14 -0
- package/dist/mui/src/filter/FilterInput.js +18 -0
- package/dist/mui/src/filter/inputs/DateFilterInput.d.ts +14 -0
- package/dist/mui/src/filter/inputs/DateFilterInput.js +112 -0
- package/dist/mui/src/filter/inputs/LinearFilterInput.d.ts +10 -0
- package/dist/mui/src/filter/inputs/LinearFilterInput.js +35 -0
- package/dist/mui/src/filter/inputs/NumberFilterInput.d.ts +12 -0
- package/dist/mui/src/filter/inputs/NumberFilterInput.js +111 -0
- package/dist/mui/src/filter/inputs/OperatorMenu.d.ts +14 -0
- package/dist/mui/src/filter/inputs/OperatorMenu.js +25 -0
- package/dist/mui/src/filter/inputs/RelationFilterInput.d.ts +11 -0
- package/dist/mui/src/filter/inputs/RelationFilterInput.js +67 -0
- package/dist/mui/src/filter/inputs/ScheduleFilterInput.d.ts +8 -0
- package/dist/mui/src/filter/inputs/ScheduleFilterInput.js +17 -0
- package/dist/mui/src/filter/inputs/SelectFilterInput.d.ts +16 -0
- package/dist/mui/src/filter/inputs/SelectFilterInput.js +57 -0
- package/dist/mui/src/filter/inputs/TextFilterInput.d.ts +13 -0
- package/dist/mui/src/filter/inputs/TextFilterInput.js +55 -0
- package/dist/mui/src/filter/kinds.d.ts +58 -0
- package/dist/mui/src/filter/kinds.js +103 -0
- package/dist/mui/src/grouping/AggregateChip.d.ts +55 -0
- package/dist/mui/src/grouping/AggregateChip.js +128 -0
- package/dist/mui/src/grouping/AggregationChips.d.ts +49 -0
- package/dist/mui/src/grouping/AggregationChips.js +80 -0
- package/dist/mui/src/grouping/AggregationSettingsButton.d.ts +14 -0
- package/dist/mui/src/grouping/AggregationSettingsButton.js +19 -0
- package/dist/mui/src/grouping/GroupingBar.d.ts +59 -0
- package/dist/mui/src/grouping/GroupingBar.js +267 -0
- package/dist/mui/src/grouping/StepExpandButton.d.ts +12 -0
- package/dist/mui/src/grouping/StepExpandButton.js +18 -0
- package/dist/mui/src/grouping/groupRow.d.ts +68 -0
- package/dist/mui/src/grouping/groupRow.js +129 -0
- package/dist/mui/src/headers/defaultHeader.d.ts +28 -0
- package/dist/mui/src/headers/defaultHeader.js +90 -0
- package/dist/mui/src/headers/defineHeader.d.ts +21 -0
- package/dist/mui/src/headers/defineHeader.js +68 -0
- package/dist/mui/src/headers/index.d.ts +5 -0
- package/dist/mui/src/headers/index.js +3 -0
- package/dist/mui/src/headers/meta.d.ts +18 -0
- package/dist/mui/src/headers/meta.js +11 -0
- package/dist/mui/src/headers/types.d.ts +84 -0
- package/dist/mui/src/headers/types.js +1 -0
- package/dist/mui/src/index.d.ts +110 -0
- package/dist/mui/src/index.js +79 -0
- package/dist/mui/src/layout/ResizeHandle.d.ts +30 -0
- package/dist/mui/src/layout/ResizeHandle.js +66 -0
- package/dist/mui/src/menus/columnMenu.d.ts +45 -0
- package/dist/mui/src/menus/columnMenu.js +144 -0
- package/dist/mui/src/menus/copy.d.ts +5 -0
- package/dist/mui/src/menus/copy.js +25 -0
- package/dist/mui/src/menus/index.d.ts +6 -0
- package/dist/mui/src/menus/index.js +4 -0
- package/dist/mui/src/menus/resolve.d.ts +15 -0
- package/dist/mui/src/menus/resolve.js +60 -0
- package/dist/mui/src/menus/rowMenu.d.ts +46 -0
- package/dist/mui/src/menus/rowMenu.js +76 -0
- package/dist/mui/src/pagination/PaginationArea.d.ts +16 -0
- package/dist/mui/src/pagination/PaginationArea.js +23 -0
- package/dist/mui/src/pivot/PivotTable.d.ts +18 -0
- package/dist/mui/src/pivot/PivotTable.js +100 -0
- package/dist/mui/src/provider/KokoTableProvider.d.ts +184 -0
- package/dist/mui/src/provider/KokoTableProvider.js +118 -0
- package/dist/mui/src/provider/labels.d.ts +85 -0
- package/dist/mui/src/provider/labels.js +104 -0
- package/dist/mui/src/registry.d.ts +30 -0
- package/dist/mui/src/registry.js +5 -0
- package/dist/mui/src/toolbar/GlobalFilterField.d.ts +36 -0
- package/dist/mui/src/toolbar/GlobalFilterField.js +78 -0
- package/dist/mui/src/virtual/VirtualTableContent.d.ts +41 -0
- package/dist/mui/src/virtual/VirtualTableContent.js +58 -0
- package/dist/mui/src/virtual/VirtualTableScreen.d.ts +13 -0
- package/dist/mui/src/virtual/VirtualTableScreen.js +23 -0
- package/dist/mui/src/virtual.d.ts +4 -0
- package/dist/mui/src/virtual.js +4 -0
- package/dist/mui/src/virtualColumns/VirtualColumnCard.d.ts +26 -0
- package/dist/mui/src/virtualColumns/VirtualColumnCard.js +29 -0
- package/dist/mui/src/virtualColumns/VirtualColumnDialog.d.ts +27 -0
- package/dist/mui/src/virtualColumns/VirtualColumnDialog.js +154 -0
- package/dist/mui/src/virtualColumns/VirtualColumnSection.d.ts +31 -0
- package/dist/mui/src/virtualColumns/VirtualColumnSection.js +55 -0
- package/dist/mui/src/virtualColumns/expression.d.ts +68 -0
- package/dist/mui/src/virtualColumns/expression.js +489 -0
- package/dist/mui/src/virtualColumns/useVirtualColumns.d.ts +67 -0
- package/dist/mui/src/virtualColumns/useVirtualColumns.js +111 -0
- package/dist/mui/src/virtualColumns/virtualColumnDefs.d.ts +29 -0
- package/dist/mui/src/virtualColumns/virtualColumnDefs.js +51 -0
- package/dist/mui-virtual.d.ts +1 -0
- package/dist/mui-virtual.js +2 -0
- package/dist/mui.d.ts +1 -0
- package/dist/mui.js +2 -0
- package/dist/query/src/index.d.ts +6 -0
- package/dist/query/src/index.js +7 -0
- package/dist/query/src/serverPivot.d.ts +42 -0
- package/dist/query/src/serverPivot.js +153 -0
- package/dist/query/src/useServerGrouping.d.ts +52 -0
- package/dist/query/src/useServerGrouping.js +186 -0
- package/dist/query/src/useServerTable.d.ts +20 -0
- package/dist/query/src/useServerTable.js +106 -0
- package/dist/query/src/useServerTableData.d.ts +48 -0
- package/dist/query/src/useServerTableData.js +64 -0
- package/dist/query/src/wire.d.ts +16 -0
- package/dist/query/src/wire.js +32 -0
- package/dist/query.d.ts +1 -0
- package/dist/query.js +2 -0
- package/package.json +125 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { resolveOperatorOptions, resolveVariant } from "../columns/deriveOperators";
|
|
2
|
+
import { resolveFilterKind } from "../filters/kind";
|
|
3
|
+
/**
|
|
4
|
+
* 어댑터 — 테이블 인스턴스를 좁은 계약으로 좁힌다.
|
|
5
|
+
*
|
|
6
|
+
* MRT 를 걷어내면 `MRT_TableHeadCellFilterContainer` 가 하던
|
|
7
|
+
* "헤더 -> { value, onChange, operator }" 변환을 어차피 직접 짜야 한다.
|
|
8
|
+
* 그 변환의 결과 타입을 `FilterSource` 로 고정한 것이 이 어댑터고,
|
|
9
|
+
* 그래서 `TableSearch` 도 같은 자리에 꽂힌다 — 추가 비용이 거의 없다.
|
|
10
|
+
*/
|
|
11
|
+
export function isFilterSource(value) {
|
|
12
|
+
return typeof value?.getFilterFields === "function";
|
|
13
|
+
}
|
|
14
|
+
export function isPaginationSource(value) {
|
|
15
|
+
return typeof value?.getPagination === "function";
|
|
16
|
+
}
|
|
17
|
+
export function isSelectionSource(value) {
|
|
18
|
+
return typeof value?.getSelectedRows === "function";
|
|
19
|
+
}
|
|
20
|
+
function hasSearch(value) {
|
|
21
|
+
return isFilterSource(value?.search);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* TanStack 인스턴스에서 필터 필드를 뽑는다.
|
|
25
|
+
*
|
|
26
|
+
* 대상 컬럼은 `getAllLeafColumns()` 기준이다 — 숨김·그룹핑으로
|
|
27
|
+
* `getLeafHeaders()` 에서 빠진 컬럼도 필터는 살아 있어야 하기 때문.
|
|
28
|
+
*
|
|
29
|
+
* `useDataTable` 로 만든 인스턴스는 `search` 를 들고 있으므로 그쪽에 위임한다.
|
|
30
|
+
* 그래야 값이 draft 한 곳에서만 관리된다 (이중 소스 방지).
|
|
31
|
+
*/
|
|
32
|
+
export function filterSourceFromTable(table) {
|
|
33
|
+
if (hasSearch(table))
|
|
34
|
+
return table.search;
|
|
35
|
+
return {
|
|
36
|
+
getFilterFields() {
|
|
37
|
+
const fields = [];
|
|
38
|
+
for (const column of table.getAllLeafColumns()) {
|
|
39
|
+
if (!column.getCanFilter())
|
|
40
|
+
continue;
|
|
41
|
+
const def = column.columnDef;
|
|
42
|
+
if (def.filter?.hidden || def.meta?.forceHidden)
|
|
43
|
+
continue;
|
|
44
|
+
fields.push({
|
|
45
|
+
id: column.id,
|
|
46
|
+
header: def.filterLabel ?? def.header,
|
|
47
|
+
variant: resolveVariant(def),
|
|
48
|
+
kind: resolveFilterKind(def),
|
|
49
|
+
meta: def.meta,
|
|
50
|
+
value: column.getFilterValue(),
|
|
51
|
+
onChange: (value) => column.setFilterValue(value),
|
|
52
|
+
operator: (def.filter?.defaultOperator ?? "fuzzy"),
|
|
53
|
+
// 제어형 테이블만으로는 연산자를 바꿀 곳이 없다 — 연산자 변경이
|
|
54
|
+
// 필요하면 `useTableSearch` 를 쓰거나 `search` 를 얹은 인스턴스를 넘긴다.
|
|
55
|
+
onOperatorChange: () => { },
|
|
56
|
+
operatorOptions: resolveOperatorOptions(def),
|
|
57
|
+
// kind 별 값 타입으로 좁혀진 render 를 필드 계약(unknown) 으로 넘긴다
|
|
58
|
+
render: def.filter?.render,
|
|
59
|
+
detail: def.filter?.detail,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return fields;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 페이지네이션 계약 — 현재 페이지는 검색 상태가, 총 건수는 데이터가 안다.
|
|
68
|
+
* 둘을 합쳐야 비로소 `PaginationSource` 가 성립한다.
|
|
69
|
+
*/
|
|
70
|
+
export function paginationSource(search, data) {
|
|
71
|
+
return {
|
|
72
|
+
getPagination() {
|
|
73
|
+
const { pageIndex, pageSize } = search.params.pagination;
|
|
74
|
+
return {
|
|
75
|
+
pageIndex,
|
|
76
|
+
pageSize,
|
|
77
|
+
total: data.total,
|
|
78
|
+
pageCount: pageSize > 0 ? Math.ceil(data.total / pageSize) : 0,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
setPageIndex: search.setPageIndex,
|
|
82
|
+
setPageSize: search.setPageSize,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function paginationSourceFromTable(table) {
|
|
86
|
+
return {
|
|
87
|
+
getPagination() {
|
|
88
|
+
const { pageIndex, pageSize } = table.getState().pagination;
|
|
89
|
+
const total = table.getRowCount();
|
|
90
|
+
return {
|
|
91
|
+
pageIndex,
|
|
92
|
+
pageSize,
|
|
93
|
+
total,
|
|
94
|
+
pageCount: pageSize > 0 ? Math.ceil(total / pageSize) : 0,
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
setPageIndex: (pageIndex) => table.setPageIndex(pageIndex),
|
|
98
|
+
setPageSize: (pageSize) => table.setPageSize(pageSize),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export function selectionSourceFromTable(table) {
|
|
102
|
+
return {
|
|
103
|
+
getSelectedRows: () => table.getSelectedRowModel().rows.map((row) => row.original),
|
|
104
|
+
selectAll: () => table.toggleAllRowsSelected(true),
|
|
105
|
+
deselectAll: () => table.toggleAllRowsSelected(false),
|
|
106
|
+
isAllSelected: () => table.getIsAllRowsSelected(),
|
|
107
|
+
isSomeSelected: () => table.getIsSomeRowsSelected(),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** 컴포넌트 진입점에서 한 번 정규화한다 */
|
|
111
|
+
export function toFilterSource(src) {
|
|
112
|
+
return isFilterSource(src) ? src : filterSourceFromTable(src);
|
|
113
|
+
}
|
|
114
|
+
export function toPaginationSource(src) {
|
|
115
|
+
return isPaginationSource(src) ? src : paginationSourceFromTable(src);
|
|
116
|
+
}
|
|
117
|
+
export function toSelectionSource(src) {
|
|
118
|
+
return isSelectionSource(src) ? src : selectionSourceFromTable(src);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 콘텐츠 구역 — 데이터에서 직접.
|
|
122
|
+
* 카드 그리드·차트처럼 테이블 인스턴스가 없는 화면이 쓰는 경로다.
|
|
123
|
+
*/
|
|
124
|
+
export function contentSource(data) {
|
|
125
|
+
return {
|
|
126
|
+
getRows: () => data.rows,
|
|
127
|
+
getStatus: () => ({
|
|
128
|
+
isLoading: data.isLoading,
|
|
129
|
+
isInitialLoading: data.isInitialLoading,
|
|
130
|
+
isError: data.isError,
|
|
131
|
+
error: data.error,
|
|
132
|
+
isEmpty: data.rows.length === 0,
|
|
133
|
+
}),
|
|
134
|
+
refetch: data.refetch,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 콘텐츠 구역 — 테이블 인스턴스에서.
|
|
139
|
+
* `useDataTable` 로 만든 인스턴스는 `data` 를 들고 있으므로 상태를 그대로 쓴다.
|
|
140
|
+
*/
|
|
141
|
+
export function contentSourceFromTable(table) {
|
|
142
|
+
const attached = table.data;
|
|
143
|
+
return {
|
|
144
|
+
getRows: () => table.getRowModel().rows.map((row) => row.original),
|
|
145
|
+
getStatus: () => ({
|
|
146
|
+
isLoading: attached?.isLoading ?? false,
|
|
147
|
+
isInitialLoading: attached?.isInitialLoading ?? false,
|
|
148
|
+
isError: attached?.isError ?? false,
|
|
149
|
+
error: attached?.error,
|
|
150
|
+
isEmpty: table.getRowModel().rows.length === 0,
|
|
151
|
+
}),
|
|
152
|
+
refetch: () => attached?.refetch(),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
export function isContentSource(value) {
|
|
156
|
+
return typeof value?.getRows === "function";
|
|
157
|
+
}
|
|
158
|
+
export function toContentSource(src) {
|
|
159
|
+
return isContentSource(src) ? src : contentSourceFromTable(src);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* 정렬 계약 — 검색 상태에서.
|
|
163
|
+
*
|
|
164
|
+
* 토글 순환은 TanStack 기본과 같다: 해제 -> 오름차순 -> 내림차순 -> 해제.
|
|
165
|
+
* `multi` 를 주면 기존 정렬을 유지한 채 뒤에 붙인다.
|
|
166
|
+
*/
|
|
167
|
+
export function sortSource(search) {
|
|
168
|
+
const current = () => search.params.sorting;
|
|
169
|
+
const columnSort = (columnId) => {
|
|
170
|
+
const index = current().findIndex((s) => s.id === columnId);
|
|
171
|
+
if (index < 0)
|
|
172
|
+
return null;
|
|
173
|
+
return { desc: current()[index].desc, index };
|
|
174
|
+
};
|
|
175
|
+
return {
|
|
176
|
+
getSort: current,
|
|
177
|
+
getColumnSort: columnSort,
|
|
178
|
+
toggleSort(columnId, options = {}) {
|
|
179
|
+
const existing = columnSort(columnId);
|
|
180
|
+
const others = options.multi ? current().filter((s) => s.id !== columnId) : [];
|
|
181
|
+
let next;
|
|
182
|
+
if (options.desc !== undefined) {
|
|
183
|
+
next = [...others, { id: columnId, desc: options.desc }];
|
|
184
|
+
}
|
|
185
|
+
else if (existing === null) {
|
|
186
|
+
next = [...others, { id: columnId, desc: false }];
|
|
187
|
+
}
|
|
188
|
+
else if (!existing.desc) {
|
|
189
|
+
next = [...others, { id: columnId, desc: true }];
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
next = others; // 내림차순 다음은 해제
|
|
193
|
+
}
|
|
194
|
+
search.setSorting(next);
|
|
195
|
+
},
|
|
196
|
+
clearSort(columnId) {
|
|
197
|
+
search.setSorting(columnId ? current().filter((s) => s.id !== columnId) : []);
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
export function sortSourceFromTable(table) {
|
|
202
|
+
const attached = table.search;
|
|
203
|
+
if (attached)
|
|
204
|
+
return sortSource(attached);
|
|
205
|
+
return {
|
|
206
|
+
getSort: () => table.getState().sorting.map((s) => ({ id: s.id, desc: s.desc })),
|
|
207
|
+
getColumnSort(columnId) {
|
|
208
|
+
const index = table.getState().sorting.findIndex((s) => s.id === columnId);
|
|
209
|
+
if (index < 0)
|
|
210
|
+
return null;
|
|
211
|
+
return { desc: table.getState().sorting[index].desc, index };
|
|
212
|
+
},
|
|
213
|
+
toggleSort(columnId, options = {}) {
|
|
214
|
+
const column = table.getColumn(columnId);
|
|
215
|
+
if (!column)
|
|
216
|
+
return;
|
|
217
|
+
if (options.desc !== undefined)
|
|
218
|
+
column.toggleSorting(options.desc, options.multi);
|
|
219
|
+
else
|
|
220
|
+
column.toggleSorting(undefined, options.multi);
|
|
221
|
+
},
|
|
222
|
+
clearSort(columnId) {
|
|
223
|
+
if (columnId)
|
|
224
|
+
table.getColumn(columnId)?.clearSorting();
|
|
225
|
+
else
|
|
226
|
+
table.resetSorting(true);
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
export function isSortSource(value) {
|
|
231
|
+
return typeof value?.toggleSort === "function";
|
|
232
|
+
}
|
|
233
|
+
export function toSortSource(src) {
|
|
234
|
+
return isSortSource(src) ? src : sortSourceFromTable(src);
|
|
235
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { RowData } from "@tanstack/react-table";
|
|
2
|
+
import type { ColumnDataType, TableColumnDefs } from "../types/column";
|
|
3
|
+
import { type Registry } from "../registry";
|
|
4
|
+
/**
|
|
5
|
+
* 내장 집계 함수 키 — 백엔드 `AGG_FUNCS` 와 이름을 맞춘다.
|
|
6
|
+
* 서버에 없는 것(`median`·`stddev`·`ratio`·`mode`)은 클라이언트 전용이다.
|
|
7
|
+
* `Record<AggFnKey, …>`(라벨·칩 색) 는 내장 키만 가진다.
|
|
8
|
+
*/
|
|
9
|
+
export type AggFnKey = "sum" | "avg" | "median" | "min" | "max" | "stddev" | "count_distinct" | "count_true" | "ratio" | "mode";
|
|
10
|
+
/** `AggFnKey` 의 다른 이름 — 사용자 집계와 구분할 때 */
|
|
11
|
+
export type BuiltinAggFnKey = AggFnKey;
|
|
12
|
+
/**
|
|
13
|
+
* 집계 함수 id — 내장 키는 자동완성되고, 레지스트리에 등록한 사용자 집계(`p90` 등)는 문자열로 들어온다.
|
|
14
|
+
* 명세(`AggregationSpec`)·결과(`AggregateResult`)·칩은 이 타입을 쓴다.
|
|
15
|
+
*/
|
|
16
|
+
export type AggregationId = AggFnKey | (string & {});
|
|
17
|
+
export interface AggregateContext {
|
|
18
|
+
/** `ratio` 계산 기준 — 전체 건수 */
|
|
19
|
+
totalCount?: number;
|
|
20
|
+
/** 집계 대상 컬럼 (`aggregateRows` 가 채운다) */
|
|
21
|
+
columnId?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 집계 정의 — 설계 문서 4절. core 가 계산·적용 조건·라벨을 알고, mui 는 같은 id 로 칩·아이콘을 붙인다.
|
|
25
|
+
*
|
|
26
|
+
* - `applies(dataType)` — 어떤 컬럼 타입에 후보로 뜨는가 (`AggregationChips`·그룹 행 칩)
|
|
27
|
+
* - `compute(values, ctx)` — 클라이언트 모드 계산. NULL 은 SQL 처럼 무시하는 것이 규약
|
|
28
|
+
* - `server` — 백엔드가 계산할 수 있으면 true. 서버 그룹핑 화면은 이것만 고르게 한다
|
|
29
|
+
* - `format` — 칩에 찍을 문자열 (미지정 시 숫자 규칙: 정수 천 단위, 소수 2자리)
|
|
30
|
+
*/
|
|
31
|
+
export interface AggregationDef {
|
|
32
|
+
id: AggregationId;
|
|
33
|
+
label: string;
|
|
34
|
+
shortLabel: string;
|
|
35
|
+
applies: (dataType?: ColumnDataType) => boolean;
|
|
36
|
+
compute: (values: unknown[], ctx: AggregateContext) => unknown;
|
|
37
|
+
server?: boolean;
|
|
38
|
+
format?: (value: unknown) => string;
|
|
39
|
+
}
|
|
40
|
+
/** 정의 헬퍼 — 타입만 고정한다 (`as const` 불필요) */
|
|
41
|
+
export declare function defineAggregation(def: AggregationDef): AggregationDef;
|
|
42
|
+
/**
|
|
43
|
+
* 내장 집계 정의. 배열 순서가 칩·메뉴의 기본 순서다.
|
|
44
|
+
* `applies` 는 예전 `NUMERIC_AGG_KEYS`(number) / `ENUM_AGG_KEYS`(그 외) / boolean 후보와 같은 결과를 낸다.
|
|
45
|
+
*/
|
|
46
|
+
export declare const BUILTIN_AGGREGATIONS: readonly AggregationDef[];
|
|
47
|
+
/**
|
|
48
|
+
* 전역 기본 레지스트리. 내장 세트가 들어 있고, `aggregations.register(def)` 로 앱 전체에 추가한다.
|
|
49
|
+
* 화면 단위로 바꾸려면 `aggregations.with({ add: [...] })` 결과를 `groupRows`/`useGrouping`/칩에 넘긴다
|
|
50
|
+
* (mui 는 `KokoTableProvider aggregations` 로 같은 일을 한다).
|
|
51
|
+
*/
|
|
52
|
+
export declare const aggregations: Registry<AggregationDef>;
|
|
53
|
+
/** 내장 키인가 */
|
|
54
|
+
export declare function isBuiltinAggregation(id: string): id is AggFnKey;
|
|
55
|
+
/** `dataType` 에 뜨는 집계 키. `serverOnly` 면 서버가 계산할 수 있는 것만 */
|
|
56
|
+
export declare function aggregationKeysFor(dataType: ColumnDataType | undefined, registry?: Registry<AggregationDef>, options?: {
|
|
57
|
+
serverOnly?: boolean;
|
|
58
|
+
}): AggregationId[];
|
|
59
|
+
/** 백엔드가 계산할 수 있는 집계 키 */
|
|
60
|
+
export declare function serverAggregationKeys(registry?: Registry<AggregationDef>): AggregationId[];
|
|
61
|
+
export declare function isServerAggregation(id: AggregationId, registry?: Registry<AggregationDef>): boolean;
|
|
62
|
+
/** `{ id: label }` — `kind` 로 짧은 라벨을 고른다 */
|
|
63
|
+
export declare function aggregationLabels(registry?: Registry<AggregationDef>, kind?: "label" | "shortLabel"): Record<AggregationId, string>;
|
|
64
|
+
/** 라벨 하나 — 레지스트리에 없으면 id 그대로 */
|
|
65
|
+
export declare function aggregationLabel(id: AggregationId, registry?: Registry<AggregationDef>, kind?: "label" | "shortLabel"): string;
|
|
66
|
+
/** 숫자 컬럼에 뜨는 집계 (내장 세트 기준 — 동적 조회는 `aggregationKeysFor("number", registry)`) */
|
|
67
|
+
export declare const NUMERIC_AGG_KEYS: AggFnKey[];
|
|
68
|
+
/** enum/텍스트 컬럼에 뜨는 집계 (내장 세트 기준) */
|
|
69
|
+
export declare const ENUM_AGG_KEYS: AggFnKey[];
|
|
70
|
+
/** boolean 컬럼에 뜨는 집계 (내장 세트 기준) — "참인 개수" 가 본래 의미라 앞에 둔다 */
|
|
71
|
+
export declare const BOOLEAN_AGG_KEYS: AggFnKey[];
|
|
72
|
+
/** 백엔드가 실제로 계산할 수 있는 집계 (내장 세트 기준 — 동적 조회는 `serverAggregationKeys(registry)`) */
|
|
73
|
+
export declare const SERVER_AGG_KEYS: AggFnKey[];
|
|
74
|
+
export declare const AGG_LABELS: Record<AggFnKey, string>;
|
|
75
|
+
export declare const AGG_SHORT_LABELS: Record<AggFnKey, string>;
|
|
76
|
+
/** 컬럼 → 적용할 집계 함수들 (사용자 집계 id 포함) */
|
|
77
|
+
export type AggregationSpec = Record<string, AggregationId[]>;
|
|
78
|
+
/** 컬럼 하나의 `{ 함수: 값 }` — 내장 키는 자동완성, 사용자 집계는 문자열 키 */
|
|
79
|
+
export type AggregateValues = Partial<Record<AggFnKey, unknown>> & Record<string, unknown>;
|
|
80
|
+
/** `{ 컬럼: { 함수: 값 } }` — 서버 응답 `aggregates` 와 같은 모양 */
|
|
81
|
+
export type AggregateResult = Record<string, AggregateValues>;
|
|
82
|
+
/** 값 배열 하나에 집계 함수 하나를 적용한다. 레지스트리에 없는 함수는 `null` */
|
|
83
|
+
export declare function aggregateValues(values: unknown[], fn: AggregationId, ctx?: AggregateContext, registry?: Registry<AggregationDef>): unknown;
|
|
84
|
+
/**
|
|
85
|
+
* 컬럼 선언(`meta.enableAgg` + `meta.aggregations`)에서 집계 명세를 만든다.
|
|
86
|
+
*
|
|
87
|
+
* 그룹핑 요청의 `aggregation` 은 기존 `useTable` 이 컬럼의 `aggregationFn` 을 모아
|
|
88
|
+
* `{ 컬럼: [함수, …] }` 로 실었던 것과 같은 모양이다. `useTableSearch.request` 가
|
|
89
|
+
* `grouping` 이 있을 때 이 값을 싣고, `SearchState.aggregation` 이 명시돼 있으면
|
|
90
|
+
* 그쪽을 우선한다. 빈 명세면 `undefined` (요청에 키가 실리지 않는다).
|
|
91
|
+
*/
|
|
92
|
+
export declare function deriveAggregation<T extends RowData>(columns: TableColumnDefs<T>): AggregationSpec | undefined;
|
|
93
|
+
/** 행 묶음에 집계 명세를 적용한다 */
|
|
94
|
+
export declare function aggregateRows<T>(rows: T[], spec: AggregationSpec, ctx?: AggregateContext, registry?: Registry<AggregationDef>): AggregateResult;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { columnKey } from "../columns/deriveOperators";
|
|
2
|
+
import { getValue } from "../utils/getValue";
|
|
3
|
+
import { createRegistry } from "../registry";
|
|
4
|
+
/** 정의 헬퍼 — 타입만 고정한다 (`as const` 불필요) */
|
|
5
|
+
export function defineAggregation(def) {
|
|
6
|
+
return def;
|
|
7
|
+
}
|
|
8
|
+
// ─────────────────────────────────────────────────────────────
|
|
9
|
+
// 내장 세트 — 같은 API 로 등록한다 (dogfooding)
|
|
10
|
+
// ─────────────────────────────────────────────────────────────
|
|
11
|
+
const numbers = (values) => {
|
|
12
|
+
const result = [];
|
|
13
|
+
for (const value of values) {
|
|
14
|
+
if (value === null || value === undefined || value === "")
|
|
15
|
+
continue;
|
|
16
|
+
const num = Number(value);
|
|
17
|
+
if (!Number.isNaN(num))
|
|
18
|
+
result.push(num);
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
};
|
|
22
|
+
/** NULL 은 SQL 집계와 마찬가지로 전부 무시한다 */
|
|
23
|
+
const present = (values) => values.filter((v) => v !== null && v !== undefined);
|
|
24
|
+
function median(values) {
|
|
25
|
+
if (values.length === 0)
|
|
26
|
+
return null;
|
|
27
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
28
|
+
const mid = Math.floor(sorted.length / 2);
|
|
29
|
+
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
30
|
+
}
|
|
31
|
+
/** 표본 표준편차 (n-1). PostgreSQL `stddev` 와 같은 정의 */
|
|
32
|
+
function stddev(values) {
|
|
33
|
+
if (values.length < 2)
|
|
34
|
+
return null;
|
|
35
|
+
const mean = values.reduce((a, b) => a + b, 0) / values.length;
|
|
36
|
+
const variance = values.reduce((acc, v) => acc + (v - mean) ** 2, 0) / (values.length - 1);
|
|
37
|
+
return Math.sqrt(variance);
|
|
38
|
+
}
|
|
39
|
+
function mode(values) {
|
|
40
|
+
const counts = new Map();
|
|
41
|
+
for (const value of values) {
|
|
42
|
+
const key = String(value);
|
|
43
|
+
const entry = counts.get(key);
|
|
44
|
+
if (entry)
|
|
45
|
+
entry.count += 1;
|
|
46
|
+
else
|
|
47
|
+
counts.set(key, { value, count: 1 });
|
|
48
|
+
}
|
|
49
|
+
let best;
|
|
50
|
+
for (const entry of counts.values()) {
|
|
51
|
+
if (!best || entry.count > best.count)
|
|
52
|
+
best = entry;
|
|
53
|
+
}
|
|
54
|
+
return best?.value ?? null;
|
|
55
|
+
}
|
|
56
|
+
const isNumeric = (dataType) => dataType === "number";
|
|
57
|
+
const isBoolean = (dataType) => dataType === "boolean";
|
|
58
|
+
/** 숫자·boolean 이 아닌 것 — enum/select/text/date 와 타입 미지정 */
|
|
59
|
+
const isCategorical = (dataType) => !isNumeric(dataType) && !isBoolean(dataType);
|
|
60
|
+
const any = () => true;
|
|
61
|
+
/**
|
|
62
|
+
* 내장 집계 정의. 배열 순서가 칩·메뉴의 기본 순서다.
|
|
63
|
+
* `applies` 는 예전 `NUMERIC_AGG_KEYS`(number) / `ENUM_AGG_KEYS`(그 외) / boolean 후보와 같은 결과를 낸다.
|
|
64
|
+
*/
|
|
65
|
+
export const BUILTIN_AGGREGATIONS = [
|
|
66
|
+
defineAggregation({
|
|
67
|
+
id: "sum", label: "합계", shortLabel: "합", applies: isNumeric, server: true,
|
|
68
|
+
compute: (values) => {
|
|
69
|
+
const nums = numbers(values);
|
|
70
|
+
return nums.length === 0 ? null : nums.reduce((a, b) => a + b, 0);
|
|
71
|
+
},
|
|
72
|
+
}),
|
|
73
|
+
defineAggregation({
|
|
74
|
+
id: "avg", label: "평균", shortLabel: "평균", applies: isNumeric, server: true,
|
|
75
|
+
compute: (values) => {
|
|
76
|
+
const nums = numbers(values);
|
|
77
|
+
return nums.length === 0 ? null : nums.reduce((a, b) => a + b, 0) / nums.length;
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
defineAggregation({
|
|
81
|
+
id: "median", label: "중간값", shortLabel: "중앙", applies: isNumeric,
|
|
82
|
+
compute: (values) => median(numbers(values)),
|
|
83
|
+
}),
|
|
84
|
+
defineAggregation({
|
|
85
|
+
id: "min", label: "최소", shortLabel: "최소", applies: isNumeric, server: true,
|
|
86
|
+
compute: (values) => {
|
|
87
|
+
const nums = numbers(values);
|
|
88
|
+
if (nums.length > 0)
|
|
89
|
+
return Math.min(...nums);
|
|
90
|
+
const rest = present(values).map(String).sort();
|
|
91
|
+
return rest[0] ?? null;
|
|
92
|
+
},
|
|
93
|
+
}),
|
|
94
|
+
defineAggregation({
|
|
95
|
+
id: "max", label: "최대", shortLabel: "최대", applies: isNumeric, server: true,
|
|
96
|
+
compute: (values) => {
|
|
97
|
+
const nums = numbers(values);
|
|
98
|
+
if (nums.length > 0)
|
|
99
|
+
return Math.max(...nums);
|
|
100
|
+
const rest = present(values).map(String).sort();
|
|
101
|
+
return rest[rest.length - 1] ?? null;
|
|
102
|
+
},
|
|
103
|
+
}),
|
|
104
|
+
defineAggregation({
|
|
105
|
+
id: "stddev", label: "표준편차", shortLabel: "편차", applies: isNumeric,
|
|
106
|
+
compute: (values) => stddev(numbers(values)),
|
|
107
|
+
}),
|
|
108
|
+
defineAggregation({
|
|
109
|
+
id: "count_distinct", label: "고유", shortLabel: "고유", applies: any, server: true,
|
|
110
|
+
compute: (values) => new Set(present(values).map(String)).size,
|
|
111
|
+
}),
|
|
112
|
+
defineAggregation({
|
|
113
|
+
id: "count_true", label: "개수", shortLabel: "개수", applies: any, server: true,
|
|
114
|
+
// 백엔드는 boolean 을 정수로 캐스팅해 sum 한다 — 참인 행의 개수
|
|
115
|
+
compute: (values) => present(values).filter((v) => v === true || v === "true" || v === 1 || v === "1").length,
|
|
116
|
+
}),
|
|
117
|
+
defineAggregation({
|
|
118
|
+
id: "ratio", label: "점유율", shortLabel: "점유", applies: any,
|
|
119
|
+
compute: (values, ctx) => {
|
|
120
|
+
const total = ctx.totalCount ?? 0;
|
|
121
|
+
return total === 0 ? null : present(values).length / total;
|
|
122
|
+
},
|
|
123
|
+
format: (value) => typeof value === "number" && Number.isFinite(value)
|
|
124
|
+
? `${(value * 100).toLocaleString("ko-KR", { maximumFractionDigits: 1 })}%`
|
|
125
|
+
: value === null || value === undefined ? "" : String(value),
|
|
126
|
+
}),
|
|
127
|
+
defineAggregation({
|
|
128
|
+
id: "mode", label: "최빈값", shortLabel: "최빈", applies: isCategorical,
|
|
129
|
+
compute: (values) => mode(present(values)),
|
|
130
|
+
}),
|
|
131
|
+
];
|
|
132
|
+
/**
|
|
133
|
+
* 전역 기본 레지스트리. 내장 세트가 들어 있고, `aggregations.register(def)` 로 앱 전체에 추가한다.
|
|
134
|
+
* 화면 단위로 바꾸려면 `aggregations.with({ add: [...] })` 결과를 `groupRows`/`useGrouping`/칩에 넘긴다
|
|
135
|
+
* (mui 는 `KokoTableProvider aggregations` 로 같은 일을 한다).
|
|
136
|
+
*/
|
|
137
|
+
export const aggregations = createRegistry([...BUILTIN_AGGREGATIONS]);
|
|
138
|
+
/** 내장 키인가 */
|
|
139
|
+
export function isBuiltinAggregation(id) {
|
|
140
|
+
return BUILTIN_AGGREGATIONS.some((def) => def.id === id);
|
|
141
|
+
}
|
|
142
|
+
// ─────────────────────────────────────────────────────────────
|
|
143
|
+
// 레지스트리에서 파생하는 조회 — 이름은 예전 상수와 호환
|
|
144
|
+
// ─────────────────────────────────────────────────────────────
|
|
145
|
+
/** `dataType` 에 뜨는 집계 키. `serverOnly` 면 서버가 계산할 수 있는 것만 */
|
|
146
|
+
export function aggregationKeysFor(dataType, registry = aggregations, options = {}) {
|
|
147
|
+
return registry.list()
|
|
148
|
+
.filter((def) => def.applies(dataType) && (!options.serverOnly || def.server))
|
|
149
|
+
.map((def) => def.id);
|
|
150
|
+
}
|
|
151
|
+
/** 백엔드가 계산할 수 있는 집계 키 */
|
|
152
|
+
export function serverAggregationKeys(registry = aggregations) {
|
|
153
|
+
return registry.list().filter((def) => def.server).map((def) => def.id);
|
|
154
|
+
}
|
|
155
|
+
export function isServerAggregation(id, registry = aggregations) {
|
|
156
|
+
return !!registry.get(id)?.server;
|
|
157
|
+
}
|
|
158
|
+
/** `{ id: label }` — `kind` 로 짧은 라벨을 고른다 */
|
|
159
|
+
export function aggregationLabels(registry = aggregations, kind = "label") {
|
|
160
|
+
const result = {};
|
|
161
|
+
for (const def of registry.list())
|
|
162
|
+
result[def.id] = def[kind];
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
/** 라벨 하나 — 레지스트리에 없으면 id 그대로 */
|
|
166
|
+
export function aggregationLabel(id, registry = aggregations, kind = "label") {
|
|
167
|
+
return registry.get(id)?.[kind] ?? id;
|
|
168
|
+
}
|
|
169
|
+
const builtins = createRegistry([...BUILTIN_AGGREGATIONS]);
|
|
170
|
+
/** 숫자 컬럼에 뜨는 집계 (내장 세트 기준 — 동적 조회는 `aggregationKeysFor("number", registry)`) */
|
|
171
|
+
export const NUMERIC_AGG_KEYS = aggregationKeysFor("number", builtins);
|
|
172
|
+
/** enum/텍스트 컬럼에 뜨는 집계 (내장 세트 기준) */
|
|
173
|
+
export const ENUM_AGG_KEYS = aggregationKeysFor("enum", builtins);
|
|
174
|
+
/** boolean 컬럼에 뜨는 집계 (내장 세트 기준) — "참인 개수" 가 본래 의미라 앞에 둔다 */
|
|
175
|
+
export const BOOLEAN_AGG_KEYS = ["count_true", "count_distinct", "ratio"];
|
|
176
|
+
/** 백엔드가 실제로 계산할 수 있는 집계 (내장 세트 기준 — 동적 조회는 `serverAggregationKeys(registry)`) */
|
|
177
|
+
export const SERVER_AGG_KEYS = serverAggregationKeys(builtins);
|
|
178
|
+
export const AGG_LABELS = aggregationLabels(builtins, "label");
|
|
179
|
+
export const AGG_SHORT_LABELS = aggregationLabels(builtins, "shortLabel");
|
|
180
|
+
/** 값 배열 하나에 집계 함수 하나를 적용한다. 레지스트리에 없는 함수는 `null` */
|
|
181
|
+
export function aggregateValues(values, fn, ctx = {}, registry = aggregations) {
|
|
182
|
+
const def = registry.get(fn);
|
|
183
|
+
return def ? def.compute(values, ctx) : null;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 컬럼 선언(`meta.enableAgg` + `meta.aggregations`)에서 집계 명세를 만든다.
|
|
187
|
+
*
|
|
188
|
+
* 그룹핑 요청의 `aggregation` 은 기존 `useTable` 이 컬럼의 `aggregationFn` 을 모아
|
|
189
|
+
* `{ 컬럼: [함수, …] }` 로 실었던 것과 같은 모양이다. `useTableSearch.request` 가
|
|
190
|
+
* `grouping` 이 있을 때 이 값을 싣고, `SearchState.aggregation` 이 명시돼 있으면
|
|
191
|
+
* 그쪽을 우선한다. 빈 명세면 `undefined` (요청에 키가 실리지 않는다).
|
|
192
|
+
*/
|
|
193
|
+
export function deriveAggregation(columns) {
|
|
194
|
+
const result = {};
|
|
195
|
+
for (const column of columns) {
|
|
196
|
+
const meta = column.meta;
|
|
197
|
+
if (!meta?.enableAgg)
|
|
198
|
+
continue;
|
|
199
|
+
const key = columnKey(column);
|
|
200
|
+
if (!key)
|
|
201
|
+
continue;
|
|
202
|
+
const fns = (meta.aggregations ?? []).filter((fn) => typeof fn === "string" && fn !== "");
|
|
203
|
+
if (fns.length === 0)
|
|
204
|
+
continue;
|
|
205
|
+
result[key] = fns;
|
|
206
|
+
}
|
|
207
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
208
|
+
}
|
|
209
|
+
/** 행 묶음에 집계 명세를 적용한다 */
|
|
210
|
+
export function aggregateRows(rows, spec, ctx = {}, registry = aggregations) {
|
|
211
|
+
const result = {};
|
|
212
|
+
for (const [columnId, fns] of Object.entries(spec)) {
|
|
213
|
+
if (!fns || fns.length === 0)
|
|
214
|
+
continue;
|
|
215
|
+
const values = rows.map((row) => getValue(row, columnId));
|
|
216
|
+
const perColumn = {};
|
|
217
|
+
for (const fn of fns)
|
|
218
|
+
perColumn[fn] = aggregateValues(values, fn, { ...ctx, columnId }, registry);
|
|
219
|
+
result[columnId] = perColumn;
|
|
220
|
+
}
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { RowData } from "@tanstack/react-table";
|
|
2
|
+
import type { TableColumnDefs } from "../types/column";
|
|
3
|
+
import type { FlatItem, GroupTreeNode } from "../types/grouping";
|
|
4
|
+
import { type LabelResolver } from "../columns/labels";
|
|
5
|
+
import { type AggregationDef, type AggregationSpec } from "./aggregate";
|
|
6
|
+
import type { Registry } from "../registry";
|
|
7
|
+
export interface GroupOptions<T extends RowData> {
|
|
8
|
+
/** 상위 → 하위 순서의 그룹 컬럼 */
|
|
9
|
+
grouping: string[];
|
|
10
|
+
columns?: TableColumnDefs<T>;
|
|
11
|
+
aggregation?: AggregationSpec;
|
|
12
|
+
/** 그룹 값 → 라벨. 미지정 시 컬럼의 `selectOptions` 에서 찾는다 */
|
|
13
|
+
labelOf?: LabelResolver;
|
|
14
|
+
/** 집계 레지스트리 — 미지정 시 전역 `aggregations` (Provider 로 등록한 것은 여기로 넘긴다) */
|
|
15
|
+
aggregations?: Registry<AggregationDef>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 행을 그룹 트리로 묶는다.
|
|
19
|
+
*
|
|
20
|
+
* 그룹 순서는 **값 오름차순**이고 NULL 이 뒤로 간다 — 정렬 규칙(`applySorting`)과
|
|
21
|
+
* 같은 규약이라 그룹을 켰다 껐을 때 순서가 뒤집히지 않는다.
|
|
22
|
+
*/
|
|
23
|
+
export declare function groupRows<T extends RowData>(rows: T[], options: GroupOptions<T>): Array<GroupTreeNode<T>>;
|
|
24
|
+
/** 트리의 최대 깊이 (그룹 레벨 수) */
|
|
25
|
+
export declare function groupDepth<T>(nodes: Array<GroupTreeNode<T>>): number;
|
|
26
|
+
export interface FlattenOptions {
|
|
27
|
+
/** 펼쳐진 경로 키 집합 */
|
|
28
|
+
expanded: Set<string>;
|
|
29
|
+
/** 말단 행까지 그릴지 — 그룹 헤더만 필요한 화면은 false */
|
|
30
|
+
includeLeaves?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 트리를 화면 순서대로 평탄화한다. 접힌 그룹의 하위는 건너뛴다.
|
|
34
|
+
* 가상화 리스트가 그대로 소비할 수 있는 형태다.
|
|
35
|
+
*/
|
|
36
|
+
export declare function flattenGroups<T extends RowData>(nodes: Array<GroupTreeNode<T>>, options: FlattenOptions): Array<FlatItem<T>>;
|
|
37
|
+
/** 트리 전체의 경로 키 — expandAll 용 */
|
|
38
|
+
export declare function allGroupPaths<T>(nodes: Array<GroupTreeNode<T>>): string[];
|
|
39
|
+
/** 특정 깊이까지의 경로 키 — 단계 펼치기 용 */
|
|
40
|
+
export declare function pathsToDepth<T>(nodes: Array<GroupTreeNode<T>>, depth: number): string[];
|