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.
Files changed (308) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +320 -0
  3. package/contract/table-request.schema.json +424 -0
  4. package/dist/core/src/adapters/sources.d.ts +59 -0
  5. package/dist/core/src/adapters/sources.js +235 -0
  6. package/dist/core/src/client/aggregate.d.ts +94 -0
  7. package/dist/core/src/client/aggregate.js +222 -0
  8. package/dist/core/src/client/groupRows.d.ts +40 -0
  9. package/dist/core/src/client/groupRows.js +145 -0
  10. package/dist/core/src/client/runClientQuery.d.ts +67 -0
  11. package/dist/core/src/client/runClientQuery.js +157 -0
  12. package/dist/core/src/columns/ColumnBuilder.d.ts +121 -0
  13. package/dist/core/src/columns/ColumnBuilder.js +273 -0
  14. package/dist/core/src/columns/cascade.d.ts +24 -0
  15. package/dist/core/src/columns/cascade.js +93 -0
  16. package/dist/core/src/columns/deriveOperators.d.ts +38 -0
  17. package/dist/core/src/columns/deriveOperators.js +94 -0
  18. package/dist/core/src/columns/expandSorting.d.ts +19 -0
  19. package/dist/core/src/columns/expandSorting.js +52 -0
  20. package/dist/core/src/columns/helper.d.ts +196 -0
  21. package/dist/core/src/columns/helper.js +250 -0
  22. package/dist/core/src/columns/labels.d.ts +12 -0
  23. package/dist/core/src/columns/labels.js +21 -0
  24. package/dist/core/src/contract.d.ts +12 -0
  25. package/dist/core/src/contract.js +12 -0
  26. package/dist/core/src/contracts/aiColumnMeta.d.ts +102 -0
  27. package/dist/core/src/contracts/aiColumnMeta.js +197 -0
  28. package/dist/core/src/contracts/describeColumns.d.ts +48 -0
  29. package/dist/core/src/contracts/describeColumns.js +40 -0
  30. package/dist/core/src/contracts/exportData.d.ts +150 -0
  31. package/dist/core/src/contracts/exportData.js +228 -0
  32. package/dist/core/src/contracts/index.d.ts +6 -0
  33. package/dist/core/src/contracts/index.js +12 -0
  34. package/dist/core/src/contracts/legacyUrl.d.ts +47 -0
  35. package/dist/core/src/contracts/legacyUrl.js +156 -0
  36. package/dist/core/src/contracts/preset.d.ts +104 -0
  37. package/dist/core/src/contracts/preset.js +141 -0
  38. package/dist/core/src/contracts/presetStore.d.ts +81 -0
  39. package/dist/core/src/contracts/presetStore.js +146 -0
  40. package/dist/core/src/contracts/usePresets.d.ts +51 -0
  41. package/dist/core/src/contracts/usePresets.js +170 -0
  42. package/dist/core/src/defaults.d.ts +7 -0
  43. package/dist/core/src/defaults.js +9 -0
  44. package/dist/core/src/filters/codec.d.ts +20 -0
  45. package/dist/core/src/filters/codec.js +61 -0
  46. package/dist/core/src/filters/date.d.ts +30 -0
  47. package/dist/core/src/filters/date.js +179 -0
  48. package/dist/core/src/filters/describe.d.ts +45 -0
  49. package/dist/core/src/filters/describe.js +189 -0
  50. package/dist/core/src/filters/kind.d.ts +52 -0
  51. package/dist/core/src/filters/kind.js +110 -0
  52. package/dist/core/src/filters/kinds/_list.d.ts +13 -0
  53. package/dist/core/src/filters/kinds/_list.js +42 -0
  54. package/dist/core/src/filters/kinds/boolean.d.ts +3 -0
  55. package/dist/core/src/filters/kinds/boolean.js +21 -0
  56. package/dist/core/src/filters/kinds/cascade.d.ts +6 -0
  57. package/dist/core/src/filters/kinds/cascade.js +16 -0
  58. package/dist/core/src/filters/kinds/commonCode.d.ts +3 -0
  59. package/dist/core/src/filters/kinds/commonCode.js +13 -0
  60. package/dist/core/src/filters/kinds/date.d.ts +9 -0
  61. package/dist/core/src/filters/kinds/date.js +32 -0
  62. package/dist/core/src/filters/kinds/enum.d.ts +4 -0
  63. package/dist/core/src/filters/kinds/enum.js +13 -0
  64. package/dist/core/src/filters/kinds/index.d.ts +49 -0
  65. package/dist/core/src/filters/kinds/index.js +39 -0
  66. package/dist/core/src/filters/kinds/linear.d.ts +5 -0
  67. package/dist/core/src/filters/kinds/linear.js +16 -0
  68. package/dist/core/src/filters/kinds/number.d.ts +5 -0
  69. package/dist/core/src/filters/kinds/number.js +19 -0
  70. package/dist/core/src/filters/kinds/period.d.ts +4 -0
  71. package/dist/core/src/filters/kinds/period.js +17 -0
  72. package/dist/core/src/filters/kinds/relation.d.ts +5 -0
  73. package/dist/core/src/filters/kinds/relation.js +17 -0
  74. package/dist/core/src/filters/kinds/schedule.d.ts +5 -0
  75. package/dist/core/src/filters/kinds/schedule.js +16 -0
  76. package/dist/core/src/filters/kinds/text.d.ts +4 -0
  77. package/dist/core/src/filters/kinds/text.js +18 -0
  78. package/dist/core/src/filters/linear.d.ts +33 -0
  79. package/dist/core/src/filters/linear.js +65 -0
  80. package/dist/core/src/filters/list.d.ts +32 -0
  81. package/dist/core/src/filters/list.js +87 -0
  82. package/dist/core/src/filters/number.d.ts +17 -0
  83. package/dist/core/src/filters/number.js +82 -0
  84. package/dist/core/src/filters/registry.d.ts +23 -0
  85. package/dist/core/src/filters/registry.js +121 -0
  86. package/dist/core/src/filters/relation.d.ts +26 -0
  87. package/dist/core/src/filters/relation.js +75 -0
  88. package/dist/core/src/filters/schedule.d.ts +21 -0
  89. package/dist/core/src/filters/schedule.js +60 -0
  90. package/dist/core/src/filters/text.d.ts +44 -0
  91. package/dist/core/src/filters/text.js +127 -0
  92. package/dist/core/src/hooks/index.d.ts +6 -0
  93. package/dist/core/src/hooks/index.js +6 -0
  94. package/dist/core/src/hooks/useDataTable.d.ts +13 -0
  95. package/dist/core/src/hooks/useDataTable.js +109 -0
  96. package/dist/core/src/hooks/useGrouping.d.ts +33 -0
  97. package/dist/core/src/hooks/useGrouping.js +57 -0
  98. package/dist/core/src/hooks/useSearchHistory.d.ts +20 -0
  99. package/dist/core/src/hooks/useSearchHistory.js +52 -0
  100. package/dist/core/src/hooks/useTableData.d.ts +25 -0
  101. package/dist/core/src/hooks/useTableData.js +49 -0
  102. package/dist/core/src/hooks/useTableFromDefinition.d.ts +47 -0
  103. package/dist/core/src/hooks/useTableFromDefinition.js +174 -0
  104. package/dist/core/src/hooks/useTableSearch.d.ts +10 -0
  105. package/dist/core/src/hooks/useTableSearch.js +434 -0
  106. package/dist/core/src/hooks/useUrlSync.d.ts +64 -0
  107. package/dist/core/src/hooks/useUrlSync.js +145 -0
  108. package/dist/core/src/index.d.ts +38 -0
  109. package/dist/core/src/index.js +43 -0
  110. package/dist/core/src/pivot/runClientPivot.d.ts +101 -0
  111. package/dist/core/src/pivot/runClientPivot.js +230 -0
  112. package/dist/core/src/pivot/transforms.d.ts +19 -0
  113. package/dist/core/src/pivot/transforms.js +105 -0
  114. package/dist/core/src/registry.d.ts +46 -0
  115. package/dist/core/src/registry.js +67 -0
  116. package/dist/core/src/types/column.d.ts +213 -0
  117. package/dist/core/src/types/column.js +1 -0
  118. package/dist/core/src/types/data.d.ts +63 -0
  119. package/dist/core/src/types/data.js +1 -0
  120. package/dist/core/src/types/definition.d.ts +239 -0
  121. package/dist/core/src/types/definition.js +59 -0
  122. package/dist/core/src/types/filter.d.ts +94 -0
  123. package/dist/core/src/types/filter.js +1 -0
  124. package/dist/core/src/types/filterKind.d.ts +204 -0
  125. package/dist/core/src/types/filterKind.js +1 -0
  126. package/dist/core/src/types/grouping.d.ts +80 -0
  127. package/dist/core/src/types/grouping.js +4 -0
  128. package/dist/core/src/types/index.d.ts +8 -0
  129. package/dist/core/src/types/index.js +8 -0
  130. package/dist/core/src/types/operators.d.ts +64 -0
  131. package/dist/core/src/types/operators.js +108 -0
  132. package/dist/core/src/types/request.d.ts +114 -0
  133. package/dist/core/src/types/request.js +1 -0
  134. package/dist/core/src/types/search.d.ts +112 -0
  135. package/dist/core/src/types/search.js +1 -0
  136. package/dist/core/src/types/sources.d.ts +84 -0
  137. package/dist/core/src/types/sources.js +1 -0
  138. package/dist/core/src/types/table.d.ts +40 -0
  139. package/dist/core/src/types/table.js +1 -0
  140. package/dist/core/src/utils/compare.d.ts +13 -0
  141. package/dist/core/src/utils/compare.js +53 -0
  142. package/dist/core/src/utils/dateText.d.ts +12 -0
  143. package/dist/core/src/utils/dateText.js +30 -0
  144. package/dist/core/src/utils/getValue.d.ts +9 -0
  145. package/dist/core/src/utils/getValue.js +32 -0
  146. package/dist/index.d.ts +1 -0
  147. package/dist/index.js +2 -0
  148. package/dist/mui/src/TableLayout.d.ts +75 -0
  149. package/dist/mui/src/TableLayout.js +162 -0
  150. package/dist/mui/src/TableScreen.d.ts +70 -0
  151. package/dist/mui/src/TableScreen.js +174 -0
  152. package/dist/mui/src/addons/FilterModal.d.ts +45 -0
  153. package/dist/mui/src/addons/FilterModal.js +39 -0
  154. package/dist/mui/src/addons/FilterStatusModal.d.ts +40 -0
  155. package/dist/mui/src/addons/FilterStatusModal.js +68 -0
  156. package/dist/mui/src/addons/PresetBar.d.ts +56 -0
  157. package/dist/mui/src/addons/PresetBar.js +145 -0
  158. package/dist/mui/src/addons/ShareDropdown.d.ts +35 -0
  159. package/dist/mui/src/addons/ShareDropdown.js +41 -0
  160. package/dist/mui/src/addons/searchStore.d.ts +44 -0
  161. package/dist/mui/src/addons/searchStore.js +112 -0
  162. package/dist/mui/src/addons/searchSummary.d.ts +106 -0
  163. package/dist/mui/src/addons/searchSummary.js +109 -0
  164. package/dist/mui/src/columns/displayColumns.d.ts +100 -0
  165. package/dist/mui/src/columns/displayColumns.js +174 -0
  166. package/dist/mui/src/content/CardContent.d.ts +24 -0
  167. package/dist/mui/src/content/CardContent.js +25 -0
  168. package/dist/mui/src/content/ColumnMenu.d.ts +43 -0
  169. package/dist/mui/src/content/ColumnMenu.js +40 -0
  170. package/dist/mui/src/content/ColumnVisibilityMenu.d.ts +14 -0
  171. package/dist/mui/src/content/ColumnVisibilityMenu.js +37 -0
  172. package/dist/mui/src/content/EditCell.d.ts +19 -0
  173. package/dist/mui/src/content/EditCell.js +61 -0
  174. package/dist/mui/src/content/HeaderCell.d.ts +59 -0
  175. package/dist/mui/src/content/HeaderCell.js +88 -0
  176. package/dist/mui/src/content/SelectionActionBar.d.ts +17 -0
  177. package/dist/mui/src/content/SelectionActionBar.js +19 -0
  178. package/dist/mui/src/content/SortControl.d.ts +17 -0
  179. package/dist/mui/src/content/SortControl.js +45 -0
  180. package/dist/mui/src/content/TableContent.d.ts +191 -0
  181. package/dist/mui/src/content/TableContent.js +525 -0
  182. package/dist/mui/src/content/TableErrorFallback.d.ts +24 -0
  183. package/dist/mui/src/content/TableErrorFallback.js +29 -0
  184. package/dist/mui/src/content/cellError.d.ts +13 -0
  185. package/dist/mui/src/content/cellError.js +20 -0
  186. package/dist/mui/src/content/cellRenderers.d.ts +71 -0
  187. package/dist/mui/src/content/cellRenderers.js +127 -0
  188. package/dist/mui/src/content/cellValue.d.ts +27 -0
  189. package/dist/mui/src/content/cellValue.js +82 -0
  190. package/dist/mui/src/content/columnOrder.d.ts +7 -0
  191. package/dist/mui/src/content/columnOrder.js +48 -0
  192. package/dist/mui/src/content/density.d.ts +13 -0
  193. package/dist/mui/src/content/density.js +12 -0
  194. package/dist/mui/src/content/menuItems.d.ts +118 -0
  195. package/dist/mui/src/content/menuItems.js +128 -0
  196. package/dist/mui/src/content/pinning.d.ts +17 -0
  197. package/dist/mui/src/content/pinning.js +65 -0
  198. package/dist/mui/src/exporters/index.d.ts +1 -0
  199. package/dist/mui/src/exporters/index.js +1 -0
  200. package/dist/mui/src/exporters/useExportFormatters.d.ts +13 -0
  201. package/dist/mui/src/exporters/useExportFormatters.js +17 -0
  202. package/dist/mui/src/filter/FilterArea.d.ts +40 -0
  203. package/dist/mui/src/filter/FilterArea.js +37 -0
  204. package/dist/mui/src/filter/FilterInput.d.ts +14 -0
  205. package/dist/mui/src/filter/FilterInput.js +18 -0
  206. package/dist/mui/src/filter/inputs/DateFilterInput.d.ts +14 -0
  207. package/dist/mui/src/filter/inputs/DateFilterInput.js +112 -0
  208. package/dist/mui/src/filter/inputs/LinearFilterInput.d.ts +10 -0
  209. package/dist/mui/src/filter/inputs/LinearFilterInput.js +35 -0
  210. package/dist/mui/src/filter/inputs/NumberFilterInput.d.ts +12 -0
  211. package/dist/mui/src/filter/inputs/NumberFilterInput.js +111 -0
  212. package/dist/mui/src/filter/inputs/OperatorMenu.d.ts +14 -0
  213. package/dist/mui/src/filter/inputs/OperatorMenu.js +25 -0
  214. package/dist/mui/src/filter/inputs/RelationFilterInput.d.ts +11 -0
  215. package/dist/mui/src/filter/inputs/RelationFilterInput.js +67 -0
  216. package/dist/mui/src/filter/inputs/ScheduleFilterInput.d.ts +8 -0
  217. package/dist/mui/src/filter/inputs/ScheduleFilterInput.js +17 -0
  218. package/dist/mui/src/filter/inputs/SelectFilterInput.d.ts +16 -0
  219. package/dist/mui/src/filter/inputs/SelectFilterInput.js +57 -0
  220. package/dist/mui/src/filter/inputs/TextFilterInput.d.ts +13 -0
  221. package/dist/mui/src/filter/inputs/TextFilterInput.js +55 -0
  222. package/dist/mui/src/filter/kinds.d.ts +58 -0
  223. package/dist/mui/src/filter/kinds.js +103 -0
  224. package/dist/mui/src/grouping/AggregateChip.d.ts +55 -0
  225. package/dist/mui/src/grouping/AggregateChip.js +128 -0
  226. package/dist/mui/src/grouping/AggregationChips.d.ts +49 -0
  227. package/dist/mui/src/grouping/AggregationChips.js +80 -0
  228. package/dist/mui/src/grouping/AggregationSettingsButton.d.ts +14 -0
  229. package/dist/mui/src/grouping/AggregationSettingsButton.js +19 -0
  230. package/dist/mui/src/grouping/GroupingBar.d.ts +59 -0
  231. package/dist/mui/src/grouping/GroupingBar.js +267 -0
  232. package/dist/mui/src/grouping/StepExpandButton.d.ts +12 -0
  233. package/dist/mui/src/grouping/StepExpandButton.js +18 -0
  234. package/dist/mui/src/grouping/groupRow.d.ts +68 -0
  235. package/dist/mui/src/grouping/groupRow.js +129 -0
  236. package/dist/mui/src/headers/defaultHeader.d.ts +28 -0
  237. package/dist/mui/src/headers/defaultHeader.js +90 -0
  238. package/dist/mui/src/headers/defineHeader.d.ts +21 -0
  239. package/dist/mui/src/headers/defineHeader.js +68 -0
  240. package/dist/mui/src/headers/index.d.ts +5 -0
  241. package/dist/mui/src/headers/index.js +3 -0
  242. package/dist/mui/src/headers/meta.d.ts +18 -0
  243. package/dist/mui/src/headers/meta.js +11 -0
  244. package/dist/mui/src/headers/types.d.ts +84 -0
  245. package/dist/mui/src/headers/types.js +1 -0
  246. package/dist/mui/src/index.d.ts +110 -0
  247. package/dist/mui/src/index.js +79 -0
  248. package/dist/mui/src/layout/ResizeHandle.d.ts +30 -0
  249. package/dist/mui/src/layout/ResizeHandle.js +66 -0
  250. package/dist/mui/src/menus/columnMenu.d.ts +45 -0
  251. package/dist/mui/src/menus/columnMenu.js +144 -0
  252. package/dist/mui/src/menus/copy.d.ts +5 -0
  253. package/dist/mui/src/menus/copy.js +25 -0
  254. package/dist/mui/src/menus/index.d.ts +6 -0
  255. package/dist/mui/src/menus/index.js +4 -0
  256. package/dist/mui/src/menus/resolve.d.ts +15 -0
  257. package/dist/mui/src/menus/resolve.js +60 -0
  258. package/dist/mui/src/menus/rowMenu.d.ts +46 -0
  259. package/dist/mui/src/menus/rowMenu.js +76 -0
  260. package/dist/mui/src/pagination/PaginationArea.d.ts +16 -0
  261. package/dist/mui/src/pagination/PaginationArea.js +23 -0
  262. package/dist/mui/src/pivot/PivotTable.d.ts +18 -0
  263. package/dist/mui/src/pivot/PivotTable.js +100 -0
  264. package/dist/mui/src/provider/KokoTableProvider.d.ts +184 -0
  265. package/dist/mui/src/provider/KokoTableProvider.js +118 -0
  266. package/dist/mui/src/provider/labels.d.ts +85 -0
  267. package/dist/mui/src/provider/labels.js +104 -0
  268. package/dist/mui/src/registry.d.ts +30 -0
  269. package/dist/mui/src/registry.js +5 -0
  270. package/dist/mui/src/toolbar/GlobalFilterField.d.ts +36 -0
  271. package/dist/mui/src/toolbar/GlobalFilterField.js +78 -0
  272. package/dist/mui/src/virtual/VirtualTableContent.d.ts +41 -0
  273. package/dist/mui/src/virtual/VirtualTableContent.js +58 -0
  274. package/dist/mui/src/virtual/VirtualTableScreen.d.ts +13 -0
  275. package/dist/mui/src/virtual/VirtualTableScreen.js +23 -0
  276. package/dist/mui/src/virtual.d.ts +4 -0
  277. package/dist/mui/src/virtual.js +4 -0
  278. package/dist/mui/src/virtualColumns/VirtualColumnCard.d.ts +26 -0
  279. package/dist/mui/src/virtualColumns/VirtualColumnCard.js +29 -0
  280. package/dist/mui/src/virtualColumns/VirtualColumnDialog.d.ts +27 -0
  281. package/dist/mui/src/virtualColumns/VirtualColumnDialog.js +154 -0
  282. package/dist/mui/src/virtualColumns/VirtualColumnSection.d.ts +31 -0
  283. package/dist/mui/src/virtualColumns/VirtualColumnSection.js +55 -0
  284. package/dist/mui/src/virtualColumns/expression.d.ts +68 -0
  285. package/dist/mui/src/virtualColumns/expression.js +489 -0
  286. package/dist/mui/src/virtualColumns/useVirtualColumns.d.ts +67 -0
  287. package/dist/mui/src/virtualColumns/useVirtualColumns.js +111 -0
  288. package/dist/mui/src/virtualColumns/virtualColumnDefs.d.ts +29 -0
  289. package/dist/mui/src/virtualColumns/virtualColumnDefs.js +51 -0
  290. package/dist/mui-virtual.d.ts +1 -0
  291. package/dist/mui-virtual.js +2 -0
  292. package/dist/mui.d.ts +1 -0
  293. package/dist/mui.js +2 -0
  294. package/dist/query/src/index.d.ts +6 -0
  295. package/dist/query/src/index.js +7 -0
  296. package/dist/query/src/serverPivot.d.ts +42 -0
  297. package/dist/query/src/serverPivot.js +153 -0
  298. package/dist/query/src/useServerGrouping.d.ts +52 -0
  299. package/dist/query/src/useServerGrouping.js +186 -0
  300. package/dist/query/src/useServerTable.d.ts +20 -0
  301. package/dist/query/src/useServerTable.js +106 -0
  302. package/dist/query/src/useServerTableData.d.ts +48 -0
  303. package/dist/query/src/useServerTableData.js +64 -0
  304. package/dist/query/src/wire.d.ts +16 -0
  305. package/dist/query/src/wire.js +32 -0
  306. package/dist/query.d.ts +1 -0
  307. package/dist/query.js +2 -0
  308. package/package.json +125 -0
@@ -0,0 +1,56 @@
1
+ import { type ReactNode } from "react";
2
+ import { type SxProps, type Theme } from "@mui/material";
3
+ import type { Presets, PresetScope, TablePreset } from "../../../core/src/index";
4
+ export interface PresetBarMessages {
5
+ empty: string;
6
+ save: string;
7
+ saveTitle: string;
8
+ name: string;
9
+ scope: string;
10
+ scopeUser: string;
11
+ scopeShared: string;
12
+ makeDefault: string;
13
+ clearDefault: string;
14
+ apply: string;
15
+ overwrite: string;
16
+ rename: string;
17
+ renameTitle: string;
18
+ remove: string;
19
+ removeTitle: string;
20
+ removeConfirm: (name: string) => string;
21
+ cancel: string;
22
+ confirm: string;
23
+ defaultBadge: string;
24
+ sharedBadge: string;
25
+ manage: (name: string) => string;
26
+ loadFailed: string;
27
+ }
28
+ export interface PresetBarProps {
29
+ /** `usePresets(...)` 결과 */
30
+ presets: Presets;
31
+ /** 저장 버튼. 끄면 읽기 전용 바가 된다 */
32
+ allowSave?: boolean;
33
+ /** 칩의 관리 메뉴(덮어쓰기·이름 변경·기본 지정·삭제) */
34
+ allowManage?: boolean;
35
+ /**
36
+ * 저장 다이얼로그에 보일 범위. 하나만 주면 선택 UI 가 사라진다.
37
+ * 기본 `["user", "shared"]` — 서버 저장소가 공유를 모르면 `["user"]` 로 줄인다.
38
+ */
39
+ scopes?: PresetScope[];
40
+ /** 공유 프리셋을 수정·삭제할 수 있는가 — 기본 모두 허용. ERP 는 `owner` 비교를 여기에 건다 */
41
+ canModify?: (preset: TablePreset) => boolean;
42
+ size?: "small" | "medium";
43
+ /** 저장 버튼을 아이콘 대신 텍스트 버튼으로 */
44
+ saveVariant?: "icon" | "button";
45
+ messages?: Partial<PresetBarMessages>;
46
+ /** 칩 뒤에 붙는 추가 요소 */
47
+ actions?: ReactNode;
48
+ sx?: SxProps<Theme>;
49
+ }
50
+ /**
51
+ * 프리셋 바 — 기존 ERP `PresetChips` + `SavePresetDialog` + `ManagePresetsDialog` 를 하나로.
52
+ *
53
+ * 칩을 누르면 적용, 칩의 점 세 개 메뉴에서 덮어쓰기·이름 변경·기본 지정·삭제.
54
+ * 저장소·표 상태는 전부 `usePresets` 가 들고 있어 이 컴포넌트는 UI 만 안다.
55
+ */
56
+ export declare function PresetBar({ presets, allowSave, allowManage, scopes, canModify, size, saveVariant, messages: givenMessages, actions, sx, }: PresetBarProps): import("react").JSX.Element;
@@ -0,0 +1,145 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Box, Button, Checkbox, Chip, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, FormControl, FormControlLabel, FormLabel, IconButton, ListItemIcon, ListItemText, Menu, MenuItem, Radio, RadioGroup, Stack, TextField, Tooltip, Typography, } from "@mui/material";
4
+ import AddIcon from "@mui/icons-material/Add";
5
+ import CheckIcon from "@mui/icons-material/Check";
6
+ import DeleteIcon from "@mui/icons-material/Delete";
7
+ import EditIcon from "@mui/icons-material/Edit";
8
+ import MoreVertIcon from "@mui/icons-material/MoreVert";
9
+ import PeopleIcon from "@mui/icons-material/People";
10
+ import SaveIcon from "@mui/icons-material/Save";
11
+ import StarIcon from "@mui/icons-material/Star";
12
+ import StarBorderIcon from "@mui/icons-material/StarBorder";
13
+ const DEFAULT_MESSAGES = {
14
+ empty: "저장된 프리셋 없음",
15
+ save: "프리셋 저장",
16
+ saveTitle: "현재 상태 저장",
17
+ name: "이름",
18
+ scope: "범위",
19
+ scopeUser: "나만",
20
+ scopeShared: "공유",
21
+ makeDefault: "기본 프리셋으로 지정",
22
+ clearDefault: "기본 지정 해제",
23
+ apply: "적용",
24
+ overwrite: "현재 상태를 덮어쓰기",
25
+ rename: "이름 변경",
26
+ renameTitle: "프리셋 이름 변경",
27
+ remove: "삭제",
28
+ removeTitle: "프리셋 삭제",
29
+ removeConfirm: (name) => `"${name}" 프리셋을 삭제할까요?`,
30
+ cancel: "취소",
31
+ confirm: "확인",
32
+ defaultBadge: "기본",
33
+ sharedBadge: "공유",
34
+ manage: (name) => `${name} 관리`,
35
+ loadFailed: "프리셋을 불러오지 못했습니다",
36
+ };
37
+ /**
38
+ * 프리셋 바 — 기존 ERP `PresetChips` + `SavePresetDialog` + `ManagePresetsDialog` 를 하나로.
39
+ *
40
+ * 칩을 누르면 적용, 칩의 점 세 개 메뉴에서 덮어쓰기·이름 변경·기본 지정·삭제.
41
+ * 저장소·표 상태는 전부 `usePresets` 가 들고 있어 이 컴포넌트는 UI 만 안다.
42
+ */
43
+ export function PresetBar({ presets, allowSave = true, allowManage = true, scopes = ["user", "shared"], canModify, size = "small", saveVariant = "icon", messages: givenMessages, actions, sx, }) {
44
+ const messages = { ...DEFAULT_MESSAGES, ...givenMessages };
45
+ const [saveOpen, setSaveOpen] = useState(false);
46
+ const [menu, setMenu] = useState(null);
47
+ const [renameTarget, setRenameTarget] = useState(null);
48
+ const [removeTarget, setRemoveTarget] = useState(null);
49
+ const closeMenu = () => setMenu(null);
50
+ const modifiable = (preset) => (canModify ? canModify(preset) : true);
51
+ const runMenu = (work) => {
52
+ closeMenu();
53
+ void Promise.resolve(work()).catch(() => {
54
+ // 오류는 presets.error / onError 로 간다 — 여기서 다시 던지면 unhandled rejection 이 된다
55
+ });
56
+ };
57
+ const chipIcon = (preset) => {
58
+ if (preset.isDefault)
59
+ return _jsx(StarIcon, { fontSize: "small", "aria-label": messages.defaultBadge });
60
+ if (preset.scope === "shared")
61
+ return _jsx(PeopleIcon, { fontSize: "small", "aria-label": messages.sharedBadge });
62
+ return undefined;
63
+ };
64
+ return (_jsxs(Stack, { direction: "row", alignItems: "center", spacing: 0.5, sx: { minWidth: 0, flexWrap: "wrap", rowGap: 0.5, ...sx }, children: [presets.loading && _jsx(CircularProgress, { size: 16, thickness: 5, "aria-label": "\uD504\uB9AC\uC14B \uBD88\uB7EC\uC624\uB294 \uC911" }), presets.error != null && !presets.loading && (_jsx(Typography, { variant: "caption", color: "error", children: messages.loadFailed })), !presets.loading && presets.error == null && presets.presets.length === 0 && (_jsx(Typography, { variant: "body2", color: "text.disabled", children: messages.empty })), presets.presets.map((preset) => {
65
+ const active = preset.id === presets.activeId;
66
+ return (_jsx(Chip, { label: preset.name, size: size, icon: chipIcon(preset), color: active ? "primary" : "default", variant: active ? "filled" : "outlined", clickable: true, disabled: presets.busy, onClick: () => presets.apply(preset.id), onDelete: allowManage ? (event) => setMenu({ anchor: event.currentTarget, preset }) : undefined, deleteIcon: allowManage ? _jsx(MoreVertIcon, { "aria-label": messages.manage(preset.name) }) : undefined, sx: { fontWeight: active ? 700 : 500, maxWidth: 240 } }, preset.id));
67
+ }), allowSave && (saveVariant === "button" ? (_jsx(Button, { size: size, startIcon: _jsx(SaveIcon, {}), disabled: presets.busy || presets.loading, onClick: () => setSaveOpen(true), children: messages.save })) : (_jsx(Tooltip, { title: messages.save, children: _jsx("span", { children: _jsx(IconButton, { size: "small", "aria-label": messages.save, disabled: presets.busy || presets.loading, onClick: () => setSaveOpen(true), children: _jsx(AddIcon, { fontSize: "small" }) }) }) }))), actions, _jsx(Menu, { anchorEl: menu?.anchor ?? null, open: menu !== null, onClose: closeMenu, children: menu && [
68
+ _jsxs(MenuItem, { onClick: () => runMenu(() => presets.apply(menu.preset.id)), children: [_jsx(ListItemIcon, { children: _jsx(CheckIcon, { fontSize: "small" }) }), _jsx(ListItemText, { children: messages.apply })] }, "apply"),
69
+ _jsxs(MenuItem, { disabled: !modifiable(menu.preset), onClick: () => runMenu(() => presets.overwrite(menu.preset.id)), children: [_jsx(ListItemIcon, { children: _jsx(SaveIcon, { fontSize: "small" }) }), _jsx(ListItemText, { children: messages.overwrite })] }, "overwrite"),
70
+ _jsxs(MenuItem, { disabled: !modifiable(menu.preset), onClick: () => { const target = menu.preset; closeMenu(); setRenameTarget(target); }, children: [_jsx(ListItemIcon, { children: _jsx(EditIcon, { fontSize: "small" }) }), _jsx(ListItemText, { children: messages.rename })] }, "rename"),
71
+ _jsxs(MenuItem, { onClick: () => runMenu(() => presets.setDefault(menu.preset.isDefault ? null : menu.preset.id)), children: [_jsx(ListItemIcon, { children: menu.preset.isDefault ? _jsx(StarIcon, { fontSize: "small" }) : _jsx(StarBorderIcon, { fontSize: "small" }) }), _jsx(ListItemText, { children: menu.preset.isDefault ? messages.clearDefault : messages.makeDefault })] }, "default"),
72
+ _jsxs(MenuItem, { disabled: !modifiable(menu.preset), onClick: () => { const target = menu.preset; closeMenu(); setRemoveTarget(target); }, children: [_jsx(ListItemIcon, { children: _jsx(DeleteIcon, { fontSize: "small", color: "error" }) }), _jsx(ListItemText, { sx: { color: "error.main" }, children: messages.remove })] }, "remove"),
73
+ ] }), _jsx(SavePresetDialog, { open: saveOpen, scopes: scopes, messages: messages, onClose: () => setSaveOpen(false), onSave: (name, options) => presets.save(name, options) }), _jsx(NameDialog, { open: renameTarget !== null, title: messages.renameTitle, label: messages.name, initial: renameTarget?.name ?? "", messages: messages, onClose: () => setRenameTarget(null), onSubmit: (name) => (renameTarget ? presets.update(renameTarget.id, { name }) : Promise.resolve()) }), _jsxs(Dialog, { open: removeTarget !== null, onClose: () => setRemoveTarget(null), maxWidth: "xs", fullWidth: true, children: [_jsx(DialogTitle, { children: messages.removeTitle }), _jsx(DialogContent, { children: _jsx(Typography, { variant: "body2", children: removeTarget ? messages.removeConfirm(removeTarget.name) : "" }) }), _jsxs(DialogActions, { children: [_jsx(Button, { onClick: () => setRemoveTarget(null), children: messages.cancel }), _jsx(Button, { color: "error", variant: "contained", onClick: () => {
74
+ const target = removeTarget;
75
+ setRemoveTarget(null);
76
+ if (target)
77
+ void presets.remove(target.id).catch(() => { });
78
+ }, children: messages.remove })] })] })] }));
79
+ }
80
+ function SavePresetDialog({ open, scopes, messages, onClose, onSave }) {
81
+ const [name, setName] = useState("");
82
+ const [scope, setScope] = useState(scopes[0] ?? "user");
83
+ const [isDefault, setIsDefault] = useState(false);
84
+ const [saving, setSaving] = useState(false);
85
+ const reset = () => {
86
+ setName("");
87
+ setScope(scopes[0] ?? "user");
88
+ setIsDefault(false);
89
+ };
90
+ const close = () => {
91
+ if (saving)
92
+ return;
93
+ reset();
94
+ onClose();
95
+ };
96
+ const submit = async () => {
97
+ const trimmed = name.trim();
98
+ if (!trimmed)
99
+ return;
100
+ setSaving(true);
101
+ try {
102
+ await onSave(trimmed, { scope, isDefault });
103
+ reset();
104
+ onClose();
105
+ }
106
+ catch {
107
+ // 오류 표시는 presets.error 가 맡는다 — 다이얼로그는 열어 둔다
108
+ }
109
+ finally {
110
+ setSaving(false);
111
+ }
112
+ };
113
+ return (_jsxs(Dialog, { open: open, onClose: close, maxWidth: "xs", fullWidth: true, children: [_jsx(DialogTitle, { children: messages.saveTitle }), _jsx(DialogContent, { children: _jsxs(Stack, { spacing: 2, sx: { pt: 1 }, children: [_jsx(TextField, { autoFocus: true, fullWidth: true, size: "small", label: messages.name, value: name, required: true, slotProps: { htmlInput: { maxLength: 50, "aria-label": messages.name } }, onChange: (e) => setName(e.target.value), onKeyDown: (e) => { if (e.key === "Enter") {
114
+ e.preventDefault();
115
+ void submit();
116
+ } } }), scopes.length > 1 && (_jsxs(FormControl, { children: [_jsx(FormLabel, { children: messages.scope }), _jsx(RadioGroup, { row: true, value: scope, onChange: (e) => setScope(e.target.value), children: scopes.map((s) => (_jsx(FormControlLabel, { value: s, control: _jsx(Radio, { size: "small" }), label: s === "shared" ? messages.scopeShared : messages.scopeUser }, s))) })] })), _jsx(FormControlLabel, { control: _jsx(Checkbox, { size: "small", checked: isDefault, onChange: (e) => setIsDefault(e.target.checked) }), label: messages.makeDefault })] }) }), _jsxs(DialogActions, { children: [_jsx(Button, { onClick: close, disabled: saving, children: messages.cancel }), _jsx(Button, { variant: "contained", onClick: () => void submit(), disabled: saving || !name.trim(), children: messages.confirm })] })] }));
117
+ }
118
+ function NameDialog({ open, title, label, initial, messages, onClose, onSubmit }) {
119
+ // 열릴 때마다 초기값을 다시 넣는다 — key 로 상태를 갈아끼운다
120
+ return (_jsx(Dialog, { open: open, onClose: onClose, maxWidth: "xs", fullWidth: true, children: open && (_jsx(NameForm, { title: title, label: label, initial: initial, messages: messages, onClose: onClose, onSubmit: onSubmit }, initial)) }));
121
+ }
122
+ function NameForm({ title, label, initial, messages, onClose, onSubmit }) {
123
+ const [name, setName] = useState(initial);
124
+ const [saving, setSaving] = useState(false);
125
+ const submit = async () => {
126
+ const trimmed = name.trim();
127
+ if (!trimmed)
128
+ return;
129
+ setSaving(true);
130
+ try {
131
+ await onSubmit(trimmed);
132
+ onClose();
133
+ }
134
+ catch {
135
+ // presets.error
136
+ }
137
+ finally {
138
+ setSaving(false);
139
+ }
140
+ };
141
+ return (_jsxs(_Fragment, { children: [_jsx(DialogTitle, { children: title }), _jsx(DialogContent, { children: _jsx(Box, { sx: { pt: 1 }, children: _jsx(TextField, { autoFocus: true, fullWidth: true, size: "small", label: label, value: name, slotProps: { htmlInput: { maxLength: 50, "aria-label": label } }, onChange: (e) => setName(e.target.value), onKeyDown: (e) => { if (e.key === "Enter") {
142
+ e.preventDefault();
143
+ void submit();
144
+ } } }) }) }), _jsxs(DialogActions, { children: [_jsx(Button, { onClick: onClose, disabled: saving, children: messages.cancel }), _jsx(Button, { variant: "contained", onClick: () => void submit(), disabled: saving || !name.trim(), children: messages.confirm })] })] }));
145
+ }
@@ -0,0 +1,35 @@
1
+ import { type ReactNode } from "react";
2
+ import type { UrlSync } from "../../../core/src/index";
3
+ export interface ShareAction {
4
+ key: string;
5
+ label: ReactNode;
6
+ icon?: ReactNode;
7
+ /** 현재 공유 URL 을 받는다 — 알림 공유 API 등 소비 앱 몫 */
8
+ onClick: (shareUrl: string) => void | Promise<void>;
9
+ disabled?: boolean;
10
+ }
11
+ export interface ShareDropdownMessages {
12
+ copied: string;
13
+ copyFailedTitle: string;
14
+ copyFailedHint: string;
15
+ }
16
+ export interface ShareDropdownProps {
17
+ /** `useUrlSync(search)` 결과 — `shareUrl` 과 `copyShareUrl` 만 쓴다 */
18
+ urlSync: Pick<UrlSync, "shareUrl" | "copyShareUrl">;
19
+ /** "알림으로 공유" 같은 앱 액션. URL 복사 아래에 순서대로 놓인다 */
20
+ extraActions?: ShareAction[];
21
+ size?: "small" | "medium" | "large";
22
+ tooltip?: string;
23
+ copyLabel?: ReactNode;
24
+ messages?: Partial<ShareDropdownMessages>;
25
+ /** 복사 성공/실패 뒤 호출 — 호출부가 자체 토스트를 쓰면 `showSnackbar=false` 와 함께 */
26
+ onCopy?: (ok: boolean, shareUrl: string) => void;
27
+ showSnackbar?: boolean;
28
+ /** 아이콘 대신 텍스트 버튼으로 그린다 */
29
+ variant?: "icon" | "button";
30
+ }
31
+ /**
32
+ * 공유 드롭다운 — 기존 ERP `ShareDropdown` 대응.
33
+ * URL 복사는 `copyShareUrl()` 로, 실패하면 URL 을 보여 주는 다이얼로그로 폴백한다.
34
+ */
35
+ export declare function ShareDropdown({ urlSync, extraActions, size, tooltip, copyLabel, messages: givenMessages, onCopy, showSnackbar, variant, }: ShareDropdownProps): import("react").JSX.Element;
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, ListItemIcon, ListItemText, Menu, MenuItem, Snackbar, TextField, Tooltip, Typography, } from "@mui/material";
4
+ import LinkIcon from "@mui/icons-material/Link";
5
+ import ShareIcon from "@mui/icons-material/Share";
6
+ const DEFAULT_MESSAGES = {
7
+ copied: "URL 을 복사했습니다",
8
+ copyFailedTitle: "URL 복사 실패",
9
+ copyFailedHint: "클립보드 권한이 없습니다. 아래 URL 을 직접 복사하세요.",
10
+ };
11
+ /**
12
+ * 공유 드롭다운 — 기존 ERP `ShareDropdown` 대응.
13
+ * URL 복사는 `copyShareUrl()` 로, 실패하면 URL 을 보여 주는 다이얼로그로 폴백한다.
14
+ */
15
+ export function ShareDropdown({ urlSync, extraActions = [], size = "small", tooltip = "공유", copyLabel = "URL 복사", messages: givenMessages, onCopy, showSnackbar = true, variant = "icon", }) {
16
+ const messages = { ...DEFAULT_MESSAGES, ...givenMessages };
17
+ const [anchorEl, setAnchorEl] = useState(null);
18
+ const [toast, setToast] = useState(null);
19
+ const [fallbackUrl, setFallbackUrl] = useState(null);
20
+ const close = () => setAnchorEl(null);
21
+ const copy = async () => {
22
+ close();
23
+ const url = urlSync.shareUrl || (typeof window !== "undefined" ? window.location.href : "");
24
+ const ok = await urlSync.copyShareUrl();
25
+ onCopy?.(ok, url);
26
+ if (ok) {
27
+ if (showSnackbar)
28
+ setToast(messages.copied);
29
+ }
30
+ else {
31
+ setFallbackUrl(url);
32
+ }
33
+ };
34
+ const run = async (action) => {
35
+ close();
36
+ const url = urlSync.shareUrl || (typeof window !== "undefined" ? window.location.href : "");
37
+ await action.onClick(url);
38
+ };
39
+ const trigger = variant === "button" ? (_jsx(Button, { size: size === "large" ? "medium" : size, variant: "outlined", color: "info", startIcon: _jsx(ShareIcon, {}), onClick: (event) => setAnchorEl(event.currentTarget), "aria-label": tooltip, "aria-haspopup": "menu", "aria-expanded": !!anchorEl, children: tooltip })) : (_jsx(IconButton, { size: size, onClick: (event) => setAnchorEl(event.currentTarget), "aria-label": tooltip, "aria-haspopup": "menu", "aria-expanded": !!anchorEl, sx: { border: 1, borderColor: "info.main", color: "info.main" }, children: _jsx(ShareIcon, { fontSize: "small" }) }));
40
+ return (_jsxs(_Fragment, { children: [_jsx(Tooltip, { title: tooltip, children: trigger }), _jsxs(Menu, { anchorEl: anchorEl, open: !!anchorEl, onClose: close, anchorOrigin: { vertical: "bottom", horizontal: "right" }, transformOrigin: { vertical: "top", horizontal: "right" }, children: [_jsxs(MenuItem, { onClick: () => { void copy(); }, children: [_jsx(ListItemIcon, { children: _jsx(LinkIcon, { fontSize: "small" }) }), _jsx(ListItemText, { children: copyLabel })] }), extraActions.map((action) => (_jsxs(MenuItem, { disabled: action.disabled, onClick: () => { void run(action); }, children: [action.icon && _jsx(ListItemIcon, { children: action.icon }), _jsx(ListItemText, { children: action.label })] }, action.key)))] }), _jsx(Snackbar, { open: !!toast, autoHideDuration: 2500, onClose: () => setToast(null), message: toast }), _jsxs(Dialog, { open: fallbackUrl !== null, onClose: () => setFallbackUrl(null), maxWidth: "sm", fullWidth: true, children: [_jsx(DialogTitle, { children: messages.copyFailedTitle }), _jsxs(DialogContent, { children: [_jsx(Typography, { variant: "body2", sx: { mb: 1.5 }, children: messages.copyFailedHint }), _jsx(TextField, { value: fallbackUrl ?? "", fullWidth: true, size: "small", multiline: true, maxRows: 4, slotProps: { input: { readOnly: true }, htmlInput: { "aria-label": "공유 URL" } }, onFocus: (event) => event.target.select() })] }), _jsx(DialogActions, { children: _jsx(Button, { onClick: () => setFallbackUrl(null), children: "\uB2EB\uAE30" }) })] })] }));
41
+ }
@@ -0,0 +1,44 @@
1
+ import type { RowData } from "@tanstack/react-table";
2
+ import { type SearchState, type TableSearch } from "../../../core/src/index";
3
+ export interface SearchStoreSnapshot {
4
+ params: SearchState;
5
+ draft: SearchState;
6
+ isDirty: boolean;
7
+ /** 브리지가 붙어 있는가 — false 면 아직 표 화면이 마운트되지 않았다 */
8
+ bound: boolean;
9
+ }
10
+ /** 브리지가 스토어에 연결하는 search 메서드 — `SearchState` 만 오간다 */
11
+ export interface SearchStoreActions {
12
+ patchParams: (partial: Partial<SearchState>) => void;
13
+ patchDraft: (partial: Partial<SearchState>) => void;
14
+ setParams: (params: SearchState) => void;
15
+ setDraft: (draft: SearchState) => void;
16
+ apply: () => void;
17
+ reset: () => void;
18
+ }
19
+ export interface SearchStore extends SearchStoreActions {
20
+ subscribe: (listener: () => void) => () => void;
21
+ getSnapshot: () => SearchStoreSnapshot;
22
+ /** `getSnapshot` 의 별칭 */
23
+ getState: () => SearchStoreSnapshot;
24
+ /** @internal 브리지 전용 */
25
+ _bind: (actions: SearchStoreActions | null) => void;
26
+ /** @internal 브리지 전용 */
27
+ _publish: (next: Pick<SearchStoreSnapshot, "params" | "draft" | "isDirty">) => void;
28
+ }
29
+ export interface CreateSearchStoreOptions {
30
+ /** 브리지가 붙기 전 조작을 받았을 때 (기본 무시). 경고 로그 등에 쓴다 */
31
+ onUnboundAction?: (action: keyof SearchStoreActions) => void;
32
+ }
33
+ /** 스토어 생성 — 모듈 스코프나 `useMemo` 안에서 만든다 */
34
+ export declare function createSearchStore(options?: CreateSearchStoreOptions): SearchStore;
35
+ /**
36
+ * 표 화면 안에서 `search` 를 스토어에 연결한다.
37
+ * 언마운트하면 연결을 끊는다 — 스냅샷은 마지막 값으로 남아 있고 `bound` 만 false 가 된다.
38
+ */
39
+ export declare function useSearchStoreBridge<T extends RowData>(store: SearchStore, search: TableSearch<T>): void;
40
+ /**
41
+ * 스토어 구독 — `selector` 결과가 `equalityFn`(기본 `Object.is`) 으로 같으면 리렌더하지 않는다.
42
+ * 배열·객체를 새로 만드는 selector 는 `equalityFn` 을 주거나 원시값으로 좁힌다.
43
+ */
44
+ export declare function useSearchStore<S = SearchStoreSnapshot>(store: SearchStore, selector?: (snapshot: SearchStoreSnapshot) => S, equalityFn?: (a: S, b: S) => boolean): S;
@@ -0,0 +1,112 @@
1
+ /**
2
+ * 검색 상태 스토어 — 기존 ERP `createTableFilterStore`(zustand) 의 무의존 대체.
3
+ *
4
+ * 표 화면의 브리지(`useSearchStoreBridge`) 가 `search.params/draft/isDirty` 를 밀어넣고,
5
+ * 표 트리 밖의 컴포넌트가 `useSearchStore(store, selector)` 로 구독한다.
6
+ * 원격 조작(`patchParams` 등) 은 브리지가 연결한 search 메서드로 되돌아간다.
7
+ * 타입은 `SearchState` 만 쓰므로 행 타입 `T` 가 없다 — 스토어를 모듈 스코프에 두어도 된다.
8
+ */
9
+ import { useEffect, useRef, useSyncExternalStore } from "react";
10
+ import { DEFAULT_PAGINATION } from "../../../core/src/index";
11
+ function emptyState() {
12
+ return {
13
+ columnFilters: [],
14
+ columnFilterFns: {},
15
+ globalFilter: "",
16
+ pagination: { ...DEFAULT_PAGINATION },
17
+ sorting: [],
18
+ };
19
+ }
20
+ /** 스토어 생성 — 모듈 스코프나 `useMemo` 안에서 만든다 */
21
+ export function createSearchStore(options = {}) {
22
+ const initial = emptyState();
23
+ let snapshot = { params: initial, draft: initial, isDirty: false, bound: false };
24
+ let actions = null;
25
+ const listeners = new Set();
26
+ const emit = () => { for (const listener of listeners)
27
+ listener(); };
28
+ const call = (name, ...args) => {
29
+ if (!actions) {
30
+ options.onUnboundAction?.(name);
31
+ return;
32
+ }
33
+ actions[name](...args);
34
+ };
35
+ return {
36
+ subscribe: (listener) => {
37
+ listeners.add(listener);
38
+ return () => { listeners.delete(listener); };
39
+ },
40
+ getSnapshot: () => snapshot,
41
+ getState: () => snapshot,
42
+ patchParams: (partial) => call("patchParams", partial),
43
+ patchDraft: (partial) => call("patchDraft", partial),
44
+ setParams: (params) => call("setParams", params),
45
+ setDraft: (draft) => call("setDraft", draft),
46
+ apply: () => call("apply"),
47
+ reset: () => call("reset"),
48
+ _bind: (next) => {
49
+ actions = next;
50
+ const bound = next !== null;
51
+ if (snapshot.bound !== bound) {
52
+ snapshot = { ...snapshot, bound };
53
+ emit();
54
+ }
55
+ },
56
+ _publish: (next) => {
57
+ if (snapshot.params === next.params && snapshot.draft === next.draft && snapshot.isDirty === next.isDirty)
58
+ return;
59
+ snapshot = { ...snapshot, params: next.params, draft: next.draft, isDirty: next.isDirty };
60
+ emit();
61
+ },
62
+ };
63
+ }
64
+ /**
65
+ * 표 화면 안에서 `search` 를 스토어에 연결한다.
66
+ * 언마운트하면 연결을 끊는다 — 스냅샷은 마지막 값으로 남아 있고 `bound` 만 false 가 된다.
67
+ */
68
+ export function useSearchStoreBridge(store, search) {
69
+ const searchRef = useRef(search);
70
+ searchRef.current = search;
71
+ useEffect(() => {
72
+ // ref 를 거쳐 부르므로 search 객체가 매 렌더 바뀌어도 다시 바인딩하지 않는다
73
+ store._bind({
74
+ patchParams: (partial) => searchRef.current.patchParams(partial),
75
+ patchDraft: (partial) => searchRef.current.patchDraft(partial),
76
+ setParams: (params) => searchRef.current.setParams(params),
77
+ setDraft: (draft) => searchRef.current.setDraft(draft),
78
+ apply: () => searchRef.current.apply(),
79
+ reset: () => searchRef.current.reset(),
80
+ });
81
+ return () => store._bind(null);
82
+ }, [store]);
83
+ useEffect(() => {
84
+ store._publish({ params: search.params, draft: search.draft, isDirty: search.isDirty });
85
+ }, [store, search.params, search.draft, search.isDirty]);
86
+ }
87
+ const identity = (snapshot) => snapshot;
88
+ /**
89
+ * 스토어 구독 — `selector` 결과가 `equalityFn`(기본 `Object.is`) 으로 같으면 리렌더하지 않는다.
90
+ * 배열·객체를 새로 만드는 selector 는 `equalityFn` 을 주거나 원시값으로 좁힌다.
91
+ */
92
+ export function useSearchStore(store, selector = (identity), equalityFn = Object.is) {
93
+ const cache = useRef(null);
94
+ const selectorRef = useRef(selector);
95
+ selectorRef.current = selector;
96
+ const equalityRef = useRef(equalityFn);
97
+ equalityRef.current = equalityFn;
98
+ const getSelected = () => {
99
+ const snapshot = store.getSnapshot();
100
+ const previous = cache.current;
101
+ if (previous && previous.snapshot === snapshot)
102
+ return previous.selected;
103
+ const selected = selectorRef.current(snapshot);
104
+ if (previous && equalityRef.current(previous.selected, selected)) {
105
+ cache.current = { snapshot, selected: previous.selected };
106
+ return previous.selected;
107
+ }
108
+ cache.current = { snapshot, selected };
109
+ return selected;
110
+ };
111
+ return useSyncExternalStore(store.subscribe, getSelected, getSelected);
112
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * 검색 상태 요약 — `FilterStatusContent` 가 그리는 표의 **순수 데이터**.
3
+ *
4
+ * 기존 ERP `FilterStatusContent.formatFilterValue` 의 값 표현 규칙을 따른다
5
+ * (빈 값 "(없음)", 날짜는 `YYYY-MM-DD`, 배열은 쉼표, 관계 필터는 컬럼별 값 묶음 또는
6
+ * "N개 그룹"). 값 문구 자체는 core 로 옮겼다 — 컬럼에 필터 kind 가 있으면
7
+ * `kind.operators[op].describe`, 없으면 `describeFilterValue` 의 일반 규칙(값 모양 추정).
8
+ * JSX 가 없으므로 모달 밖(툴팁·로그) 에서도 쓴다.
9
+ */
10
+ import type { RowData } from "@tanstack/react-table";
11
+ import { type AnyFilterKind, type ColumnDescription, type FilterDescribeLabels, type FilterOperator, type SearchState, type TableColumnDefs } from "../../../core/src/index";
12
+ export declare const EMPTY_VALUE_TEXT: string;
13
+ /** 값을 쓰지 않는 연산자의 값 칸 */
14
+ export declare const VALUELESS_TEXT: string;
15
+ export type ConditionStatus =
16
+ /** params 와 draft 가 같다 */
17
+ "applied"
18
+ /** 적용됐지만 draft 에서 값·연산자가 바뀌었다 */
19
+ | "changed"
20
+ /** draft 에만 있다 (아직 적용 안 됨) */
21
+ | "added"
22
+ /** params 에는 있는데 draft 에서 지워졌다 */
23
+ | "removed";
24
+ export interface ConditionSummary {
25
+ id: string;
26
+ header: string;
27
+ /** 적용된 연산자 (draft 에만 있는 항목은 draft 연산자) */
28
+ operator: FilterOperator;
29
+ operatorLabel: string;
30
+ /** 적용된 값의 표시 문자열 */
31
+ value: string;
32
+ status: ConditionStatus;
33
+ /** status 가 applied 가 아닐 때 draft 쪽 연산자·값 */
34
+ draftOperator?: FilterOperator;
35
+ draftOperatorLabel?: string;
36
+ draftValue?: string;
37
+ }
38
+ export interface SortSummary {
39
+ id: string;
40
+ header: string;
41
+ desc: boolean;
42
+ /** "거래처 오름차순" */
43
+ label: string;
44
+ }
45
+ export interface SearchSummary {
46
+ conditions: ConditionSummary[];
47
+ globalFilter: {
48
+ value: string;
49
+ draftValue: string;
50
+ pending: boolean;
51
+ };
52
+ sorting: SortSummary[];
53
+ /** 그룹핑 컬럼 헤더 */
54
+ grouping: string[];
55
+ pagination: {
56
+ pageIndex: number;
57
+ pageSize: number;
58
+ };
59
+ /** 적용 중인(값이 있는) 조건 수 — 전역검색 포함 */
60
+ appliedCount: number;
61
+ /** draft 와 다른 항목 수 (전역검색 포함) */
62
+ pendingCount: number;
63
+ }
64
+ export interface FormatConditionValueOptions {
65
+ operator?: FilterOperator;
66
+ column?: Pick<ColumnDescription, "variant" | "selectOptions" | "relationColumns">;
67
+ /** 컬럼의 필터 kind — 있으면 `operators[op].describe` 가 문구를 정한다 */
68
+ kind?: AnyFilterKind;
69
+ /** 배열 값이 이보다 길면 앞부분만 보이고 "외 N개" (기본 5) */
70
+ maxListItems?: number;
71
+ /** 문구 묶음 (기본 한국어 `DEFAULT_DESCRIBE_LABELS`) */
72
+ labels?: FilterDescribeLabels;
73
+ }
74
+ /**
75
+ * 필터 값 하나를 사람이 읽는 문자열로.
76
+ *
77
+ * kind 가 있으면 kind 의 `describe`(값 없는 연산자·빈 값은 공통 처리), 없으면 값 모양으로
78
+ * 추정하는 core `describeFilterValue` 의 일반 규칙:
79
+ * - 값 없는 연산자(`empty`/`hasChild` …) → `-`
80
+ * - 선택지가 있으면 값 대신 라벨
81
+ * - 두 원소 범위(숫자·날짜) → `a ~ b`, 그 외 배열은 쉼표
82
+ * - 관계(`groups`)/선형(`terms`)/일정(`date`|`start`) 은 요약 문구
83
+ */
84
+ export declare function formatConditionValue(value: unknown, options?: FormatConditionValueOptions): string;
85
+ /** 조건이 실제로 걸려 있는가 — 값이 있거나 값 없는 연산자다 */
86
+ export declare function isActiveCondition(value: unknown, operator?: FilterOperator): boolean;
87
+ /**
88
+ * 적용 중인 조건 수 — 값이 있는 컬럼 필터 + 전역검색.
89
+ * 트리거 버튼 배지와 접힌 패널 요약이 같은 수를 보여야 하므로 한 곳에 둔다.
90
+ */
91
+ export declare function countActiveConditions(state: Pick<SearchState, "columnFilters" | "globalFilter" | "columnFilterFns">, operators?: Readonly<Record<string, FilterOperator>>): number;
92
+ export interface SummarizeSearchOptions<T extends RowData> {
93
+ columns: TableColumnDefs<T>;
94
+ params: SearchState;
95
+ /** 주면 params 와 비교해 미적용 항목을 표시한다 */
96
+ draft?: SearchState;
97
+ /** 컬럼 기본값이 덧대진 연산자 맵 (`search.operators`). 없으면 `params.columnFilterFns` */
98
+ operators?: Readonly<Record<string, FilterOperator>>;
99
+ draftOperators?: Readonly<Record<string, FilterOperator>>;
100
+ /** 값이 비어 있는 항목도 표에 남긴다 (기본 false) */
101
+ includeInactive?: boolean;
102
+ /** 값 문구 묶음 — Provider 라벨을 넘길 때 (기본 한국어) */
103
+ labels?: FilterDescribeLabels;
104
+ }
105
+ /** `search.params`(+`draft`) 를 표 데이터로 요약한다 */
106
+ export declare function summarizeSearch<T extends RowData>(options: SummarizeSearchOptions<T>): SearchSummary;
@@ -0,0 +1,109 @@
1
+ import { DEFAULT_DESCRIBE_LABELS, describeColumns, describeFilter, filterKindMap, isUnsetFilterValue, isValuelessOperator, } from "../../../core/src/index";
2
+ import { operatorLabel } from "../filter/inputs/OperatorMenu";
3
+ export const EMPTY_VALUE_TEXT = DEFAULT_DESCRIBE_LABELS.empty;
4
+ /** 값을 쓰지 않는 연산자의 값 칸 */
5
+ export const VALUELESS_TEXT = DEFAULT_DESCRIBE_LABELS.valueless;
6
+ /**
7
+ * 필터 값 하나를 사람이 읽는 문자열로.
8
+ *
9
+ * kind 가 있으면 kind 의 `describe`(값 없는 연산자·빈 값은 공통 처리), 없으면 값 모양으로
10
+ * 추정하는 core `describeFilterValue` 의 일반 규칙:
11
+ * - 값 없는 연산자(`empty`/`hasChild` …) → `-`
12
+ * - 선택지가 있으면 값 대신 라벨
13
+ * - 두 원소 범위(숫자·날짜) → `a ~ b`, 그 외 배열은 쉼표
14
+ * - 관계(`groups`)/선형(`terms`)/일정(`date`|`start`) 은 요약 문구
15
+ */
16
+ export function formatConditionValue(value, options = {}) {
17
+ const { operator, column, kind, maxListItems = 5, labels } = options;
18
+ return describeFilter(kind, operator, value, {
19
+ operator,
20
+ variant: column?.variant,
21
+ options: column?.selectOptions,
22
+ relationColumns: column?.relationColumns,
23
+ maxListItems,
24
+ labels,
25
+ });
26
+ }
27
+ /** 조건이 실제로 걸려 있는가 — 값이 있거나 값 없는 연산자다 */
28
+ export function isActiveCondition(value, operator) {
29
+ if (operator && isValuelessOperator(operator))
30
+ return true;
31
+ return !isUnsetFilterValue(value);
32
+ }
33
+ /**
34
+ * 적용 중인 조건 수 — 값이 있는 컬럼 필터 + 전역검색.
35
+ * 트리거 버튼 배지와 접힌 패널 요약이 같은 수를 보여야 하므로 한 곳에 둔다.
36
+ */
37
+ export function countActiveConditions(state, operators) {
38
+ const ops = operators ?? state.columnFilterFns;
39
+ const filters = state.columnFilters.filter((f) => f && isActiveCondition(f.value, ops[f.id])).length;
40
+ return filters + (state.globalFilter ? 1 : 0);
41
+ }
42
+ const sameJson = (a, b) => JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
43
+ /** `search.params`(+`draft`) 를 표 데이터로 요약한다 */
44
+ export function summarizeSearch(options) {
45
+ const { columns, params, draft, includeInactive = false, labels } = options;
46
+ const operators = options.operators ?? params.columnFilterFns;
47
+ const draftOperators = options.draftOperators ?? draft?.columnFilterFns ?? operators;
48
+ const described = new Map(describeColumns(columns).map((c) => [c.id, c]));
49
+ const kinds = filterKindMap(columns);
50
+ const headerOf = (id) => described.get(id)?.header ?? id;
51
+ const operatorOf = (id, map) => map[id] ?? described.get(id)?.defaultOperator ?? "fuzzy";
52
+ const appliedById = new Map(params.columnFilters.filter(Boolean).map((f) => [f.id, f]));
53
+ const draftById = new Map((draft?.columnFilters ?? []).filter(Boolean).map((f) => [f.id, f]));
54
+ const ids = Array.from(new Set([...appliedById.keys(), ...draftById.keys()]));
55
+ const conditions = [];
56
+ for (const id of ids) {
57
+ const column = described.get(id);
58
+ const applied = appliedById.get(id);
59
+ const inDraft = draftById.get(id);
60
+ const appliedOperator = operatorOf(id, operators);
61
+ const draftOperator = operatorOf(id, draftOperators);
62
+ const appliedActive = applied ? isActiveCondition(applied.value, appliedOperator) : false;
63
+ const draftActive = draft ? (inDraft ? isActiveCondition(inDraft.value, draftOperator) : false) : appliedActive;
64
+ if (!appliedActive && !draftActive && !includeInactive)
65
+ continue;
66
+ let status = "applied";
67
+ if (draft) {
68
+ if (appliedActive && !draftActive)
69
+ status = "removed";
70
+ else if (!appliedActive && draftActive)
71
+ status = "added";
72
+ else if (!sameJson(applied?.value, inDraft?.value) || appliedOperator !== draftOperator)
73
+ status = "changed";
74
+ }
75
+ const format = (value, operator) => formatConditionValue(value, { operator, column, kind: kinds.get(id), labels });
76
+ const summary = {
77
+ id,
78
+ header: headerOf(id),
79
+ operator: status === "added" ? draftOperator : appliedOperator,
80
+ operatorLabel: operatorLabel(status === "added" ? draftOperator : appliedOperator, labels?.operators),
81
+ value: status === "added" ? EMPTY_VALUE_TEXT : format(applied?.value, appliedOperator),
82
+ status,
83
+ };
84
+ if (status !== "applied") {
85
+ summary.draftOperator = draftOperator;
86
+ summary.draftOperatorLabel = operatorLabel(draftOperator, labels?.operators);
87
+ summary.draftValue = status === "removed" ? EMPTY_VALUE_TEXT : format(inDraft?.value, draftOperator);
88
+ }
89
+ conditions.push(summary);
90
+ }
91
+ const globalValue = params.globalFilter ?? "";
92
+ const globalDraft = draft ? draft.globalFilter ?? "" : globalValue;
93
+ const globalPending = globalDraft !== globalValue;
94
+ const sorting = params.sorting.map((s) => ({
95
+ id: s.id,
96
+ header: headerOf(s.id),
97
+ desc: !!s.desc,
98
+ label: `${headerOf(s.id)} ${s.desc ? "내림차순" : "오름차순"}`,
99
+ }));
100
+ return {
101
+ conditions,
102
+ globalFilter: { value: globalValue, draftValue: globalDraft, pending: globalPending },
103
+ sorting,
104
+ grouping: (params.grouping ?? []).map(headerOf),
105
+ pagination: { pageIndex: params.pagination.pageIndex, pageSize: params.pagination.pageSize },
106
+ appliedCount: countActiveConditions(params, operators),
107
+ pendingCount: conditions.filter((c) => c.status !== "applied").length + (globalPending ? 1 : 0),
108
+ };
109
+ }