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,43 @@
|
|
|
1
|
+
export * from "./types";
|
|
2
|
+
export * from "./defaults";
|
|
3
|
+
export * from "./utils/getValue";
|
|
4
|
+
export * from "./utils/compare";
|
|
5
|
+
export * from "./filters/text";
|
|
6
|
+
export * from "./filters/number";
|
|
7
|
+
export * from "./filters/date";
|
|
8
|
+
export * from "./filters/list";
|
|
9
|
+
export * from "./filters/relation";
|
|
10
|
+
export * from "./filters/linear";
|
|
11
|
+
export * from "./filters/schedule";
|
|
12
|
+
export * from "./filters/registry";
|
|
13
|
+
// ── 필터 kind ──
|
|
14
|
+
export * from "./filters/kind";
|
|
15
|
+
export * from "./filters/kinds";
|
|
16
|
+
export * from "./filters/describe";
|
|
17
|
+
export * from "./filters/codec";
|
|
18
|
+
export { formatLocalDate, toDateOnly, ISO_DATETIME } from "./utils/dateText";
|
|
19
|
+
export * from "./client/runClientQuery";
|
|
20
|
+
export * from "./client/aggregate";
|
|
21
|
+
export * from "./client/groupRows";
|
|
22
|
+
export * from "./types/grouping";
|
|
23
|
+
export * from "./pivot/transforms";
|
|
24
|
+
export * from "./pivot/runClientPivot";
|
|
25
|
+
export * from "./adapters/sources";
|
|
26
|
+
export * from "./columns/deriveOperators";
|
|
27
|
+
export * from "./columns/labels";
|
|
28
|
+
export * from "./columns/cascade";
|
|
29
|
+
export * from "./columns/ColumnBuilder";
|
|
30
|
+
export * from "./columns/expandSorting";
|
|
31
|
+
export { createColumnHelper, } from "./columns/helper";
|
|
32
|
+
export * from "./hooks";
|
|
33
|
+
export * from "./contracts";
|
|
34
|
+
// ── 검색 debounce·프리셋 ──
|
|
35
|
+
export { createLocalStoragePresetStore, createMemoryPresetStore, loadDefaultPreset, newPresetId, } from "./contracts/presetStore";
|
|
36
|
+
export { usePresets, } from "./contracts/usePresets";
|
|
37
|
+
// ── 테이블 정의 ──
|
|
38
|
+
export * from "./types/definition";
|
|
39
|
+
export * from "./hooks/useTableFromDefinition";
|
|
40
|
+
// ── 레지스트리 ──
|
|
41
|
+
export * from "./registry";
|
|
42
|
+
// ── 백엔드 계약 ──
|
|
43
|
+
export * from "./contract";
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { RowData } from "@tanstack/react-table";
|
|
2
|
+
import type { TableColumnDefs } from "../types/column";
|
|
3
|
+
import { type LabelResolver } from "../columns/labels";
|
|
4
|
+
import { type AggregationDef } from "../client/aggregate";
|
|
5
|
+
import type { Registry } from "../registry";
|
|
6
|
+
import { type ColumnTransform } from "./transforms";
|
|
7
|
+
/** 피벗 집계 함수 — 백엔드 `AggFunc` 와 같은 이름(대문자) */
|
|
8
|
+
export type PivotAggFunc = "SUM" | "COUNT" | "AVG" | "MIN" | "MAX" | "COUNT_DISTINCT";
|
|
9
|
+
/** 컬럼 타입별 사용 가능한 집계 — 백엔드 `COLUMN_TYPE_AGG_FUNCS` */
|
|
10
|
+
export declare const AGG_FUNCS_BY_TYPE: Record<string, PivotAggFunc[]>;
|
|
11
|
+
export interface PivotColumnSpec {
|
|
12
|
+
source: string;
|
|
13
|
+
transform?: ColumnTransform;
|
|
14
|
+
alias?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface PivotValueSpec {
|
|
17
|
+
agg: PivotAggFunc;
|
|
18
|
+
source: string;
|
|
19
|
+
alias?: string;
|
|
20
|
+
}
|
|
21
|
+
export type HavingOperator = "eq" | "ne" | "gt" | "gte" | "lt" | "lte";
|
|
22
|
+
export interface PivotHaving {
|
|
23
|
+
agg: PivotAggFunc;
|
|
24
|
+
source: string;
|
|
25
|
+
operator: HavingOperator;
|
|
26
|
+
value: number;
|
|
27
|
+
}
|
|
28
|
+
export interface PivotSpec {
|
|
29
|
+
/**
|
|
30
|
+
* 정확히 2 개의 **동등한** 축. `axes[0]` 이 행 자리, `axes[1]` 이 열 자리다.
|
|
31
|
+
* transpose 는 두 축을 swap 하면 되므로 별도 옵션이 없다.
|
|
32
|
+
*/
|
|
33
|
+
axes: [PivotColumnSpec[], PivotColumnSpec[]];
|
|
34
|
+
values: PivotValueSpec[];
|
|
35
|
+
having?: PivotHaving[];
|
|
36
|
+
/** 열 축 leaf 의 distinct 값 상한 — 초과분은 버린다 (성능·UI 보호) */
|
|
37
|
+
maxPivotValues?: number;
|
|
38
|
+
}
|
|
39
|
+
export interface PivotAxisNode {
|
|
40
|
+
key: string;
|
|
41
|
+
label: string;
|
|
42
|
+
value: unknown;
|
|
43
|
+
source: string;
|
|
44
|
+
depth: number;
|
|
45
|
+
children?: PivotAxisNode[];
|
|
46
|
+
}
|
|
47
|
+
export interface PivotRowNode extends PivotAxisNode {
|
|
48
|
+
/** 열 leaf 키 → 값 키 → 집계값 */
|
|
49
|
+
cells: Record<string, Record<string, unknown>>;
|
|
50
|
+
/** 이 행 전체(모든 열)의 집계 */
|
|
51
|
+
total: Record<string, unknown>;
|
|
52
|
+
/** 이 행에 속한 원본 행 수 */
|
|
53
|
+
count: number;
|
|
54
|
+
children?: PivotRowNode[];
|
|
55
|
+
}
|
|
56
|
+
export interface PivotResult {
|
|
57
|
+
rows: PivotRowNode[];
|
|
58
|
+
/** 중첩 열 헤더 */
|
|
59
|
+
columns: PivotAxisNode[];
|
|
60
|
+
/** 열 leaf 키 (화면 왼→오 순서) */
|
|
61
|
+
leafKeys: string[];
|
|
62
|
+
/** leaf 키 → 라벨 경로 */
|
|
63
|
+
leafLabels: Record<string, string[]>;
|
|
64
|
+
/** 값 키 목록 (`source_AGG` 또는 alias) */
|
|
65
|
+
valueKeys: string[];
|
|
66
|
+
/** 전체 총계 */
|
|
67
|
+
grandTotal: Record<string, unknown>;
|
|
68
|
+
/**
|
|
69
|
+
* 열 합계 — 열 leaf 키 → 값 키 → 집계값. 행 `total`·`grandTotal` 과 같은 규칙으로
|
|
70
|
+
* **원본 행 기준**으로 계산한다 (행별 셀 값을 다시 집계하지 않는다 — AVG 를 셀
|
|
71
|
+
* 평균의 평균으로 내면 가중치가 사라진다). 원본 행이 없는 열은 키가 없다.
|
|
72
|
+
*/
|
|
73
|
+
colTotals: Record<string, Record<string, unknown>>;
|
|
74
|
+
/** `maxPivotValues` 로 잘려나간 열 leaf 수 — 0 이 아니면 화면에 알려야 한다 */
|
|
75
|
+
droppedColumns: number;
|
|
76
|
+
}
|
|
77
|
+
export interface PivotOptions<T extends RowData> {
|
|
78
|
+
/**
|
|
79
|
+
* 축 라벨을 찾을 컬럼 정의.
|
|
80
|
+
*
|
|
81
|
+
* CommonCode 축은 셀에 idx 가 들어 있어서 그대로 쓰면 "완료" 대신 "3" 이 뜬다.
|
|
82
|
+
* 그룹핑(`groupRows`)과 **같은 해석기**를 쓴다 — 둘이 다르면 같은 컬럼이
|
|
83
|
+
* 그룹 헤더에서는 이름으로, 피벗 축에서는 숫자로 나온다.
|
|
84
|
+
*/
|
|
85
|
+
columns?: TableColumnDefs<T>;
|
|
86
|
+
/** 직접 지정하는 라벨 해석기. 주면 `columns` 보다 우선한다 */
|
|
87
|
+
labelOf?: LabelResolver;
|
|
88
|
+
/** 집계 레지스트리 — 미지정 시 전역 `aggregations`. 피벗 함수(`SUM` 등)는 같은 id 의 정의로 계산한다 */
|
|
89
|
+
aggregations?: Registry<AggregationDef>;
|
|
90
|
+
}
|
|
91
|
+
export declare const PIVOT_SEPARATOR = "|";
|
|
92
|
+
export declare function valueKeyOf(spec: PivotValueSpec): string;
|
|
93
|
+
export declare function axisKeyOf(spec: PivotColumnSpec): string;
|
|
94
|
+
/**
|
|
95
|
+
* 클라이언트 피벗 — 백엔드 `PivotBuilder` 와 같은 계약으로 계산한다.
|
|
96
|
+
*
|
|
97
|
+
* 두 축은 **동등**하다. 행/열 전환은 `axes` 를 뒤집으면 되고 별도 경로가 없다.
|
|
98
|
+
*/
|
|
99
|
+
export declare function runClientPivot<T extends RowData>(rows: T[], spec: PivotSpec, options?: PivotOptions<T>): PivotResult;
|
|
100
|
+
/** 행/열 축 전환 — 두 축이 동등하므로 swap 이면 끝난다 */
|
|
101
|
+
export declare function transposePivot(spec: PivotSpec): PivotSpec;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { createLabelResolver } from "../columns/labels";
|
|
2
|
+
import { getValue } from "../utils/getValue";
|
|
3
|
+
import { compareValues } from "../utils/compare";
|
|
4
|
+
import { aggregateValues } from "../client/aggregate";
|
|
5
|
+
import { applyTransform } from "./transforms";
|
|
6
|
+
/** 컬럼 타입별 사용 가능한 집계 — 백엔드 `COLUMN_TYPE_AGG_FUNCS` */
|
|
7
|
+
export const AGG_FUNCS_BY_TYPE = {
|
|
8
|
+
number: ["SUM", "COUNT", "AVG", "MIN", "MAX", "COUNT_DISTINCT"],
|
|
9
|
+
date: ["COUNT", "MIN", "MAX", "COUNT_DISTINCT"],
|
|
10
|
+
datetime: ["COUNT", "MIN", "MAX", "COUNT_DISTINCT"],
|
|
11
|
+
text: ["COUNT", "COUNT_DISTINCT"],
|
|
12
|
+
boolean: ["COUNT", "COUNT_DISTINCT"],
|
|
13
|
+
enum: ["COUNT", "COUNT_DISTINCT"],
|
|
14
|
+
common_code: ["COUNT", "COUNT_DISTINCT"],
|
|
15
|
+
other: ["COUNT", "COUNT_DISTINCT"],
|
|
16
|
+
};
|
|
17
|
+
/** 피벗 집계 이름을 클라이언트 집계 키로 */
|
|
18
|
+
const AGG_TO_FN = {
|
|
19
|
+
SUM: "sum", AVG: "avg", MIN: "min", MAX: "max",
|
|
20
|
+
COUNT_DISTINCT: "count_distinct", COUNT: "count",
|
|
21
|
+
};
|
|
22
|
+
export const PIVOT_SEPARATOR = "|";
|
|
23
|
+
export function valueKeyOf(spec) {
|
|
24
|
+
return spec.alias ?? `${spec.source}_${spec.agg}`;
|
|
25
|
+
}
|
|
26
|
+
export function axisKeyOf(spec) {
|
|
27
|
+
if (spec.alias)
|
|
28
|
+
return spec.alias;
|
|
29
|
+
const transform = spec.transform ?? "none";
|
|
30
|
+
return transform === "none" ? spec.source : `${spec.source}_${transform}`;
|
|
31
|
+
}
|
|
32
|
+
function aggregate(rows, source, agg, registry) {
|
|
33
|
+
if (agg === "COUNT") {
|
|
34
|
+
// 백엔드 COUNT 는 NULL 이 아닌 값의 개수다
|
|
35
|
+
return rows.filter((row) => {
|
|
36
|
+
const value = getValue(row, source);
|
|
37
|
+
return value !== null && value !== undefined;
|
|
38
|
+
}).length;
|
|
39
|
+
}
|
|
40
|
+
const fn = AGG_TO_FN[agg];
|
|
41
|
+
return aggregateValues(rows.map((row) => getValue(row, source)), fn, {}, registry);
|
|
42
|
+
}
|
|
43
|
+
function passesHaving(rows, having, registry) {
|
|
44
|
+
for (const condition of having) {
|
|
45
|
+
const actual = Number(aggregate(rows, condition.source, condition.agg, registry));
|
|
46
|
+
if (Number.isNaN(actual))
|
|
47
|
+
return false;
|
|
48
|
+
const target = condition.value;
|
|
49
|
+
const ok = condition.operator === "eq" ? actual === target
|
|
50
|
+
: condition.operator === "ne" ? actual !== target
|
|
51
|
+
: condition.operator === "gt" ? actual > target
|
|
52
|
+
: condition.operator === "gte" ? actual >= target
|
|
53
|
+
: condition.operator === "lt" ? actual < target
|
|
54
|
+
: actual <= target;
|
|
55
|
+
if (!ok)
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
/** 한 축 레벨로 행을 나눈다. 순서는 값 오름차순, NULL 은 뒤 */
|
|
61
|
+
function bucketize(rows, spec, resolveLabel) {
|
|
62
|
+
const map = new Map();
|
|
63
|
+
const transform = spec.transform ?? "none";
|
|
64
|
+
for (const row of rows) {
|
|
65
|
+
const { value, label } = applyTransform(getValue(row, spec.source), transform);
|
|
66
|
+
// 변환이 걸린 축은 변환 라벨("1월", "10만~20만")이 이미 의미를 갖는다.
|
|
67
|
+
// 변환 없는 축만 컬럼 선택지에서 이름을 찾는다.
|
|
68
|
+
const resolved = transform === "none" ? resolveLabel(spec.source, value) ?? label : label;
|
|
69
|
+
const key = String(value);
|
|
70
|
+
const bucket = map.get(key);
|
|
71
|
+
if (bucket)
|
|
72
|
+
bucket.rows.push(row);
|
|
73
|
+
else
|
|
74
|
+
map.set(key, { value, label: resolved, rows: [row] });
|
|
75
|
+
}
|
|
76
|
+
return [...map.values()].sort((a, b) => {
|
|
77
|
+
const aNull = a.value === null || a.value === undefined;
|
|
78
|
+
const bNull = b.value === null || b.value === undefined;
|
|
79
|
+
if (aNull || bNull)
|
|
80
|
+
return aNull && bNull ? 0 : aNull ? 1 : -1;
|
|
81
|
+
return compareValues(a.value, b.value);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/** 열 축 트리를 만들고 leaf 키 순서를 함께 낸다 */
|
|
85
|
+
function buildColumnAxis(rows, specs, maxPivotValues, resolveLabel) {
|
|
86
|
+
const leafKeys = [];
|
|
87
|
+
const leafLabels = {};
|
|
88
|
+
let dropped = 0;
|
|
89
|
+
const build = (subset, depth, parentKey, parentLabels) => {
|
|
90
|
+
const spec = specs[depth];
|
|
91
|
+
const buckets = bucketize(subset, spec, resolveLabel);
|
|
92
|
+
const nodes = [];
|
|
93
|
+
for (const bucket of buckets) {
|
|
94
|
+
const key = parentKey ? `${parentKey}${PIVOT_SEPARATOR}${String(bucket.value)}` : String(bucket.value);
|
|
95
|
+
const labels = [...parentLabels, bucket.label];
|
|
96
|
+
const isLeaf = depth === specs.length - 1;
|
|
97
|
+
if (isLeaf) {
|
|
98
|
+
if (leafKeys.length >= maxPivotValues) {
|
|
99
|
+
dropped += 1;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
leafKeys.push(key);
|
|
103
|
+
leafLabels[key] = labels;
|
|
104
|
+
}
|
|
105
|
+
nodes.push({
|
|
106
|
+
key,
|
|
107
|
+
label: bucket.label,
|
|
108
|
+
value: bucket.value,
|
|
109
|
+
source: spec.source,
|
|
110
|
+
depth,
|
|
111
|
+
children: isLeaf ? undefined : build(bucket.rows, depth + 1, key, labels),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
// 하위가 전부 잘려나간 중간 노드는 헤더만 남으므로 제거한다
|
|
115
|
+
return nodes.filter((node) => node.children === undefined || node.children.length > 0);
|
|
116
|
+
};
|
|
117
|
+
const nodes = build(rows, 0, "", []);
|
|
118
|
+
return { nodes, leafKeys, leafLabels, dropped };
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* 클라이언트 피벗 — 백엔드 `PivotBuilder` 와 같은 계약으로 계산한다.
|
|
122
|
+
*
|
|
123
|
+
* 두 축은 **동등**하다. 행/열 전환은 `axes` 를 뒤집으면 되고 별도 경로가 없다.
|
|
124
|
+
*/
|
|
125
|
+
export function runClientPivot(rows, spec, options = {}) {
|
|
126
|
+
const [rowSpecs, colSpecs] = spec.axes;
|
|
127
|
+
if (rowSpecs.length === 0)
|
|
128
|
+
throw new Error("[koko-table] pivot: axes[0] 은 최소 1개 필요합니다");
|
|
129
|
+
if (colSpecs.length === 0)
|
|
130
|
+
throw new Error("[koko-table] pivot: axes[1] 은 최소 1개 필요합니다");
|
|
131
|
+
if (spec.values.length === 0)
|
|
132
|
+
throw new Error("[koko-table] pivot: values 는 최소 1개 필요합니다");
|
|
133
|
+
const maxPivotValues = spec.maxPivotValues ?? 50;
|
|
134
|
+
const having = spec.having ?? [];
|
|
135
|
+
const valueKeys = spec.values.map(valueKeyOf);
|
|
136
|
+
const resolveLabel = createLabelResolver(options.columns, options.labelOf);
|
|
137
|
+
const { nodes: columns, leafKeys, leafLabels, dropped } = buildColumnAxis(rows, colSpecs, maxPivotValues, resolveLabel);
|
|
138
|
+
/**
|
|
139
|
+
* 행 → 열 leaf 키를 **한 번만** 계산해 둔다.
|
|
140
|
+
*
|
|
141
|
+
* 이걸 안 해 두면 셀 하나를 만들 때마다 subset 전체를 다시 훑으며
|
|
142
|
+
* `applyTransform`(날짜 파싱 포함)을 부르게 된다 — 행 × leaf × 축 이라
|
|
143
|
+
* 실측에서 10,000 행 2x2 피벗이 67ms 였다. 미리 계산하면 행 × 축 로 떨어진다.
|
|
144
|
+
*/
|
|
145
|
+
const leafKeyOf = new Map();
|
|
146
|
+
for (const row of rows) {
|
|
147
|
+
let key = "";
|
|
148
|
+
for (const colSpec of colSpecs) {
|
|
149
|
+
const { value } = applyTransform(getValue(row, colSpec.source), colSpec.transform ?? "none");
|
|
150
|
+
key = key ? `${key}${PIVOT_SEPARATOR}${String(value)}` : String(value);
|
|
151
|
+
}
|
|
152
|
+
leafKeyOf.set(row, key);
|
|
153
|
+
}
|
|
154
|
+
const shownLeafKeys = new Set(leafKeys);
|
|
155
|
+
/** 이 행 묶음에서 열 leaf 별 셀을 만든다 — subset 을 한 번만 훑는다 */
|
|
156
|
+
const cellsFor = (subset) => {
|
|
157
|
+
const byLeaf = new Map();
|
|
158
|
+
for (const row of subset) {
|
|
159
|
+
const key = leafKeyOf.get(row);
|
|
160
|
+
// 상한(maxPivotValues)으로 잘려나간 열은 셀도 만들지 않는다
|
|
161
|
+
if (!shownLeafKeys.has(key))
|
|
162
|
+
continue;
|
|
163
|
+
const bucket = byLeaf.get(key);
|
|
164
|
+
if (bucket)
|
|
165
|
+
bucket.push(row);
|
|
166
|
+
else
|
|
167
|
+
byLeaf.set(key, [row]);
|
|
168
|
+
}
|
|
169
|
+
const cells = {};
|
|
170
|
+
for (const [leafKey, matched] of byLeaf) {
|
|
171
|
+
const perValue = {};
|
|
172
|
+
spec.values.forEach((valueSpec, index) => {
|
|
173
|
+
perValue[valueKeys[index]] = aggregate(matched, valueSpec.source, valueSpec.agg, options.aggregations);
|
|
174
|
+
});
|
|
175
|
+
cells[leafKey] = perValue;
|
|
176
|
+
}
|
|
177
|
+
return cells;
|
|
178
|
+
};
|
|
179
|
+
const totalsFor = (subset) => {
|
|
180
|
+
const totals = {};
|
|
181
|
+
spec.values.forEach((valueSpec, index) => {
|
|
182
|
+
totals[valueKeys[index]] = aggregate(subset, valueSpec.source, valueSpec.agg, options.aggregations);
|
|
183
|
+
});
|
|
184
|
+
return totals;
|
|
185
|
+
};
|
|
186
|
+
const buildRows = (subset, depth, parentKey) => {
|
|
187
|
+
const rowSpec = rowSpecs[depth];
|
|
188
|
+
const buckets = bucketize(subset, rowSpec, resolveLabel);
|
|
189
|
+
const nodes = [];
|
|
190
|
+
for (const bucket of buckets) {
|
|
191
|
+
// HAVING 은 말단 행에만 적용한다 — 상위 그룹에 걸면 하위가 통째로 사라진다
|
|
192
|
+
const isLeaf = depth === rowSpecs.length - 1;
|
|
193
|
+
if (isLeaf && having.length > 0 && !passesHaving(bucket.rows, having, options.aggregations))
|
|
194
|
+
continue;
|
|
195
|
+
const key = parentKey ? `${parentKey}${PIVOT_SEPARATOR}${String(bucket.value)}` : String(bucket.value);
|
|
196
|
+
const children = isLeaf ? undefined : buildRows(bucket.rows, depth + 1, key);
|
|
197
|
+
if (children && children.length === 0)
|
|
198
|
+
continue; // 하위가 전부 걸러지면 상위도 뺀다
|
|
199
|
+
nodes.push({
|
|
200
|
+
key,
|
|
201
|
+
label: bucket.label,
|
|
202
|
+
value: bucket.value,
|
|
203
|
+
source: rowSpec.source,
|
|
204
|
+
depth,
|
|
205
|
+
cells: cellsFor(bucket.rows),
|
|
206
|
+
total: totalsFor(bucket.rows),
|
|
207
|
+
count: bucket.rows.length,
|
|
208
|
+
children,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return nodes;
|
|
212
|
+
};
|
|
213
|
+
// 열 합계 — 전체 원본 행을 열 leaf 로 나눠 행 total 과 같은 규칙으로 집계한다.
|
|
214
|
+
// (cellsFor(rows) 와 같은 계산이지만 뜻이 다르므로 이름을 따로 둔다)
|
|
215
|
+
const colTotals = cellsFor(rows);
|
|
216
|
+
return {
|
|
217
|
+
rows: buildRows(rows, 0, ""),
|
|
218
|
+
columns,
|
|
219
|
+
leafKeys,
|
|
220
|
+
leafLabels,
|
|
221
|
+
valueKeys,
|
|
222
|
+
grandTotal: totalsFor(rows),
|
|
223
|
+
colTotals,
|
|
224
|
+
droppedColumns: dropped,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/** 행/열 축 전환 — 두 축이 동등하므로 swap 이면 끝난다 */
|
|
228
|
+
export function transposePivot(spec) {
|
|
229
|
+
return { ...spec, axes: [spec.axes[1], spec.axes[0]] };
|
|
230
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 피벗 축 변환 — 백엔드 `PivotBuilder/transforms` 와 값·라벨을 맞춘다.
|
|
3
|
+
* 변환된 **값**이 같은 그룹으로 묶이므로, 값이 어긋나면 축이 쪼개진다.
|
|
4
|
+
*/
|
|
5
|
+
export type ColumnTransform = "none" | "year" | "quarter" | "month" | "week" | "day_of_week" | "date" | "range_10k" | "range_100k" | "range_1m" | "range_10m" | "range_100m";
|
|
6
|
+
export declare const TRANSFORM_LABELS: Record<ColumnTransform, string>;
|
|
7
|
+
/** 컬럼 타입별 사용 가능한 변환 — 백엔드 `COLUMN_TYPE_TRANSFORMS` 와 같다 */
|
|
8
|
+
export declare const TRANSFORMS_BY_TYPE: Record<string, ColumnTransform[]>;
|
|
9
|
+
/** 한국식 금액 표기 — 백엔드 `_format_korean` 과 같은 규칙 */
|
|
10
|
+
export declare function formatKoreanAmount(value: number): string;
|
|
11
|
+
/** ISO 주차 (월요일 시작) */
|
|
12
|
+
export declare function isoWeek(date: Date): number;
|
|
13
|
+
export interface TransformedValue {
|
|
14
|
+
/** 그룹 키가 되는 값 */
|
|
15
|
+
value: unknown;
|
|
16
|
+
label: string;
|
|
17
|
+
}
|
|
18
|
+
/** 값 하나에 변환을 적용한다 */
|
|
19
|
+
export declare function applyTransform(raw: unknown, transform: ColumnTransform): TransformedValue;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export const TRANSFORM_LABELS = {
|
|
2
|
+
none: "변환 없음",
|
|
3
|
+
month: "월", quarter: "분기", year: "연도", week: "주차",
|
|
4
|
+
day_of_week: "요일", date: "날짜",
|
|
5
|
+
range_10k: "1만 단위", range_100k: "10만 단위", range_1m: "100만 단위",
|
|
6
|
+
range_10m: "1000만 단위", range_100m: "1억 단위",
|
|
7
|
+
};
|
|
8
|
+
/** 컬럼 타입별 사용 가능한 변환 — 백엔드 `COLUMN_TYPE_TRANSFORMS` 와 같다 */
|
|
9
|
+
export const TRANSFORMS_BY_TYPE = {
|
|
10
|
+
date: ["month", "quarter", "year", "week", "day_of_week", "date"],
|
|
11
|
+
datetime: ["month", "quarter", "year", "week", "day_of_week", "date"],
|
|
12
|
+
number: ["range_10k", "range_100k", "range_1m", "range_10m", "range_100m"],
|
|
13
|
+
text: [], boolean: [], enum: [], common_code: [], other: [],
|
|
14
|
+
};
|
|
15
|
+
const RANGE_UNITS = {
|
|
16
|
+
range_10k: 10_000,
|
|
17
|
+
range_100k: 100_000,
|
|
18
|
+
range_1m: 1_000_000,
|
|
19
|
+
range_10m: 10_000_000,
|
|
20
|
+
range_100m: 100_000_000,
|
|
21
|
+
};
|
|
22
|
+
const DAY_LABELS = ["일", "월", "화", "수", "목", "금", "토"];
|
|
23
|
+
/** 한국식 금액 표기 — 백엔드 `_format_korean` 과 같은 규칙 */
|
|
24
|
+
export function formatKoreanAmount(value) {
|
|
25
|
+
if (value === 0)
|
|
26
|
+
return "0";
|
|
27
|
+
const negative = value < 0;
|
|
28
|
+
const abs = Math.abs(value);
|
|
29
|
+
const eok = Math.floor(abs / 100_000_000);
|
|
30
|
+
const man = Math.floor((abs % 100_000_000) / 10_000);
|
|
31
|
+
const parts = [];
|
|
32
|
+
if (eok > 0)
|
|
33
|
+
parts.push(`${eok}억`);
|
|
34
|
+
if (man > 0)
|
|
35
|
+
parts.push(`${man}만`);
|
|
36
|
+
const body = parts.length > 0 ? parts.join("") : String(abs);
|
|
37
|
+
return negative ? `-${body}` : body;
|
|
38
|
+
}
|
|
39
|
+
/** ISO 주차 (월요일 시작) */
|
|
40
|
+
export function isoWeek(date) {
|
|
41
|
+
const target = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
42
|
+
// 목요일 기준 — ISO 8601 은 그 주의 목요일이 속한 해를 그 주의 해로 본다
|
|
43
|
+
const dayOfWeek = (target.getDay() + 6) % 7;
|
|
44
|
+
target.setDate(target.getDate() - dayOfWeek + 3);
|
|
45
|
+
const firstThursday = new Date(target.getFullYear(), 0, 4);
|
|
46
|
+
const firstDayOfWeek = (firstThursday.getDay() + 6) % 7;
|
|
47
|
+
firstThursday.setDate(firstThursday.getDate() - firstDayOfWeek + 3);
|
|
48
|
+
return 1 + Math.round((target.getTime() - firstThursday.getTime()) / (7 * 24 * 3600 * 1000));
|
|
49
|
+
}
|
|
50
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
51
|
+
function toDate(raw) {
|
|
52
|
+
if (raw instanceof Date)
|
|
53
|
+
return Number.isNaN(raw.getTime()) ? null : raw;
|
|
54
|
+
if (raw === null || raw === undefined || raw === "")
|
|
55
|
+
return null;
|
|
56
|
+
const date = new Date(String(raw));
|
|
57
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
58
|
+
}
|
|
59
|
+
/** 값 하나에 변환을 적용한다 */
|
|
60
|
+
export function applyTransform(raw, transform) {
|
|
61
|
+
if (transform === "none") {
|
|
62
|
+
return { value: raw, label: raw === null || raw === undefined ? "(비어 있음)" : String(raw) };
|
|
63
|
+
}
|
|
64
|
+
const unit = RANGE_UNITS[transform];
|
|
65
|
+
if (unit !== undefined) {
|
|
66
|
+
if (raw === null || raw === undefined || raw === "")
|
|
67
|
+
return { value: null, label: "(비어 있음)" };
|
|
68
|
+
const num = Number(raw);
|
|
69
|
+
if (Number.isNaN(num))
|
|
70
|
+
return { value: null, label: "(비어 있음)" };
|
|
71
|
+
const floor = Math.floor(num / unit) * unit;
|
|
72
|
+
return { value: floor, label: `${formatKoreanAmount(floor)}~${formatKoreanAmount(floor + unit)}` };
|
|
73
|
+
}
|
|
74
|
+
const date = toDate(raw);
|
|
75
|
+
if (date === null)
|
|
76
|
+
return { value: null, label: "(비어 있음)" };
|
|
77
|
+
switch (transform) {
|
|
78
|
+
case "year": {
|
|
79
|
+
const year = date.getFullYear();
|
|
80
|
+
return { value: year, label: `${year}년` };
|
|
81
|
+
}
|
|
82
|
+
case "quarter": {
|
|
83
|
+
const quarter = Math.floor(date.getMonth() / 3) + 1;
|
|
84
|
+
return { value: quarter, label: `Q${quarter}` };
|
|
85
|
+
}
|
|
86
|
+
case "month": {
|
|
87
|
+
const month = date.getMonth() + 1;
|
|
88
|
+
return { value: month, label: `${month}월` };
|
|
89
|
+
}
|
|
90
|
+
case "week": {
|
|
91
|
+
const week = isoWeek(date);
|
|
92
|
+
return { value: week, label: `${week}주차` };
|
|
93
|
+
}
|
|
94
|
+
case "day_of_week": {
|
|
95
|
+
const day = date.getDay();
|
|
96
|
+
return { value: day, label: DAY_LABELS[day] };
|
|
97
|
+
}
|
|
98
|
+
case "date": {
|
|
99
|
+
const ymd = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
100
|
+
return { value: ymd, label: ymd };
|
|
101
|
+
}
|
|
102
|
+
default:
|
|
103
|
+
return { value: raw, label: String(raw) };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 확장점 공통 레지스트리 (설계 문서 4절).
|
|
3
|
+
*
|
|
4
|
+
* - 모든 확장점(셀 렌더러·집계·export 포맷터·메뉴·헤더·필터 kind)은 `{ id, … }` 항목의 레지스트리다.
|
|
5
|
+
* - `register()` 는 제자리 교체·추가(전역 기본 세트 조립용), `with()` 는 원본을 두고 새 레지스트리를 돌려준다.
|
|
6
|
+
* - core 에 두는 이유: 집계·export 포맷터 레지스트리는 React 를 모르는 core 가 소비한다.
|
|
7
|
+
* mui 는 같은 것을 re-export 한다(`kokotable/mui` 의 `createRegistry` 와 동일 객체).
|
|
8
|
+
*/
|
|
9
|
+
export interface Registry<Def extends {
|
|
10
|
+
id: string;
|
|
11
|
+
}> {
|
|
12
|
+
/** 같은 id 가 있으면 교체, 없으면 뒤에 추가 (mutable — 전역 기본 세트 조립용) */
|
|
13
|
+
register(def: Def): void;
|
|
14
|
+
/** id 로 제거 (mutable). 있었으면 true — 전역 등록을 되돌릴 때(테스트·HMR) */
|
|
15
|
+
unregister(id: string): boolean;
|
|
16
|
+
get(id: string): Def | undefined;
|
|
17
|
+
list(): Def[];
|
|
18
|
+
has(id: string): boolean;
|
|
19
|
+
/** 불변 파생 — add · remove · replace · order 를 적용한 새 레지스트리 */
|
|
20
|
+
with(overrides: RegistryPatch<Def>): Registry<Def>;
|
|
21
|
+
}
|
|
22
|
+
export interface RegistryPatch<Def extends {
|
|
23
|
+
id: string;
|
|
24
|
+
}> {
|
|
25
|
+
/** 뒤에 추가 (같은 id 가 있으면 그 자리에서 교체) */
|
|
26
|
+
add?: Def[];
|
|
27
|
+
/** id 로 제거 */
|
|
28
|
+
remove?: string[];
|
|
29
|
+
/** 같은 id 항목을 제자리에서 교체 (없으면 뒤에 추가) */
|
|
30
|
+
replace?: Def[];
|
|
31
|
+
/** id 배열 순서로 정렬 — 없는 id 는 뒤에 원래 순서 */
|
|
32
|
+
order?: string[];
|
|
33
|
+
}
|
|
34
|
+
export declare function applyPatch<Def extends {
|
|
35
|
+
id: string;
|
|
36
|
+
}>(items: Def[], patch: RegistryPatch<Def> | undefined): Def[];
|
|
37
|
+
export declare function createRegistry<Def extends {
|
|
38
|
+
id: string;
|
|
39
|
+
}>(base?: Def[]): Registry<Def>;
|
|
40
|
+
export declare function isRegistry<Def extends {
|
|
41
|
+
id: string;
|
|
42
|
+
}>(value: unknown): value is Registry<Def>;
|
|
43
|
+
/** 배열 · 레지스트리 · undefined 를 모두 레지스트리로 (배열은 `base` 위에 add) */
|
|
44
|
+
export declare function toRegistry<Def extends {
|
|
45
|
+
id: string;
|
|
46
|
+
}>(value: Def[] | Registry<Def> | undefined, base?: Def[]): Registry<Def>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
function upsert(items, def) {
|
|
2
|
+
const idx = items.findIndex((d) => d.id === def.id);
|
|
3
|
+
if (idx < 0)
|
|
4
|
+
return [...items, def];
|
|
5
|
+
const next = items.slice();
|
|
6
|
+
next[idx] = def;
|
|
7
|
+
return next;
|
|
8
|
+
}
|
|
9
|
+
export function applyPatch(items, patch) {
|
|
10
|
+
if (!patch)
|
|
11
|
+
return items;
|
|
12
|
+
let next = items;
|
|
13
|
+
for (const def of patch.replace ?? [])
|
|
14
|
+
next = upsert(next, def);
|
|
15
|
+
for (const def of patch.add ?? [])
|
|
16
|
+
next = upsert(next, def);
|
|
17
|
+
if (patch.remove?.length) {
|
|
18
|
+
const drop = new Set(patch.remove);
|
|
19
|
+
next = next.filter((d) => !drop.has(d.id));
|
|
20
|
+
}
|
|
21
|
+
if (patch.order?.length) {
|
|
22
|
+
const rank = new Map(patch.order.map((id, i) => [id, i]));
|
|
23
|
+
const ordered = next.filter((d) => rank.has(d.id)).sort((a, b) => rank.get(a.id) - rank.get(b.id));
|
|
24
|
+
const rest = next.filter((d) => !rank.has(d.id));
|
|
25
|
+
next = [...ordered, ...rest];
|
|
26
|
+
}
|
|
27
|
+
return next;
|
|
28
|
+
}
|
|
29
|
+
export function createRegistry(base = []) {
|
|
30
|
+
let items = base.slice();
|
|
31
|
+
const registry = {
|
|
32
|
+
register(def) {
|
|
33
|
+
items = upsert(items, def);
|
|
34
|
+
},
|
|
35
|
+
unregister(id) {
|
|
36
|
+
const next = items.filter((d) => d.id !== id);
|
|
37
|
+
const removed = next.length !== items.length;
|
|
38
|
+
items = next;
|
|
39
|
+
return removed;
|
|
40
|
+
},
|
|
41
|
+
get(id) {
|
|
42
|
+
return items.find((d) => d.id === id);
|
|
43
|
+
},
|
|
44
|
+
has(id) {
|
|
45
|
+
return items.some((d) => d.id === id);
|
|
46
|
+
},
|
|
47
|
+
list() {
|
|
48
|
+
return items.slice();
|
|
49
|
+
},
|
|
50
|
+
with(overrides) {
|
|
51
|
+
return createRegistry(applyPatch(items, overrides));
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
return registry;
|
|
55
|
+
}
|
|
56
|
+
export function isRegistry(value) {
|
|
57
|
+
return !!value && typeof value === "object"
|
|
58
|
+
&& typeof value.list === "function" && typeof value.with === "function";
|
|
59
|
+
}
|
|
60
|
+
/** 배열 · 레지스트리 · undefined 를 모두 레지스트리로 (배열은 `base` 위에 add) */
|
|
61
|
+
export function toRegistry(value, base = []) {
|
|
62
|
+
if (!value)
|
|
63
|
+
return createRegistry(base);
|
|
64
|
+
if (isRegistry(value))
|
|
65
|
+
return value;
|
|
66
|
+
return createRegistry(applyPatch(base, { add: value }));
|
|
67
|
+
}
|