@questpie/admin 3.0.3 → 3.0.5

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 (253) hide show
  1. package/README.md +34 -5
  2. package/dist/client/blocks/block-renderer.d.mts +2 -2
  3. package/dist/client/blocks/block-renderer.mjs +4 -1
  4. package/dist/client/builder/types/action-types.d.mts +31 -3
  5. package/dist/client/builder/types/collection-types.d.mts +140 -0
  6. package/dist/client/builder/types/ui-config.d.mts +16 -2
  7. package/dist/client/builder/types/views.d.mts +57 -0
  8. package/dist/client/builder/types/widget-types.d.mts +5 -0
  9. package/dist/client/components/actions/action-button.mjs +137 -199
  10. package/dist/client/components/actions/action-dialog.mjs +198 -156
  11. package/dist/client/components/actions/confirmation-dialog.mjs +2 -2
  12. package/dist/client/components/actions/header-actions.mjs +52 -53
  13. package/dist/client/components/admin-link.d.mts +2 -2
  14. package/dist/client/components/auth/auth-loading.mjs +41 -18
  15. package/dist/client/components/blocks/block-editor-layout.mjs +2 -2
  16. package/dist/client/components/blocks/block-fields-renderer.mjs +64 -28
  17. package/dist/client/components/blocks/block-insert-button.mjs +4 -4
  18. package/dist/client/components/blocks/block-item.mjs +2 -2
  19. package/dist/client/components/blocks/block-library-sidebar.mjs +91 -63
  20. package/dist/client/components/component-renderer.mjs +1 -1
  21. package/dist/client/components/fields/array-field.mjs +14 -14
  22. package/dist/client/components/fields/asset-preview-field.mjs +1 -1
  23. package/dist/client/components/fields/blocks-field/blocks-field.mjs +84 -104
  24. package/dist/client/components/fields/json-field.mjs +2 -2
  25. package/dist/client/components/fields/object-array-field.mjs +22 -22
  26. package/dist/client/components/fields/object-field.mjs +5 -5
  27. package/dist/client/components/fields/relation/displays/cards-display.mjs +16 -9
  28. package/dist/client/components/fields/relation/displays/chips-display.mjs +15 -12
  29. package/dist/client/components/fields/relation/displays/grid-display.mjs +15 -11
  30. package/dist/client/components/fields/relation/displays/list-display.mjs +33 -20
  31. package/dist/client/components/fields/relation/displays/table-display.mjs +62 -93
  32. package/dist/client/components/fields/relation/relation-items-display.mjs +1 -1
  33. package/dist/client/components/fields/relation-picker.mjs +7 -6
  34. package/dist/client/components/fields/relation-select.mjs +71 -47
  35. package/dist/client/components/fields/rich-text-editor/bubble-menu.mjs +392 -82
  36. package/dist/client/components/fields/rich-text-editor/extensions.mjs +54 -23
  37. package/dist/client/components/fields/rich-text-editor/image-popover.mjs +24 -50
  38. package/dist/client/components/fields/rich-text-editor/image-upload.mjs +66 -0
  39. package/dist/client/components/fields/rich-text-editor/index.d.mts +38 -0
  40. package/dist/client/components/fields/rich-text-editor/index.mjs +637 -376
  41. package/dist/client/components/fields/rich-text-editor/link-utils.mjs +26 -0
  42. package/dist/client/components/fields/rich-text-editor/presets.d.mts +10 -0
  43. package/dist/client/components/fields/rich-text-editor/slash-commands.mjs +27 -6
  44. package/dist/client/components/fields/rich-text-editor/toolbar.mjs +464 -346
  45. package/dist/client/components/fields/rich-text-editor/types.d.mts +77 -0
  46. package/dist/client/components/fields/upload-field.mjs +45 -49
  47. package/dist/client/components/filter-builder/columns-tab.mjs +69 -62
  48. package/dist/client/components/filter-builder/filter-builder-sheet.mjs +473 -308
  49. package/dist/client/components/filter-builder/filters-tab.mjs +109 -82
  50. package/dist/client/components/filter-builder/saved-views-tab.mjs +300 -198
  51. package/dist/client/components/history-sidebar.mjs +850 -340
  52. package/dist/client/components/layout/field-layout-renderer.mjs +6 -5
  53. package/dist/client/components/locale-switcher.mjs +8 -8
  54. package/dist/client/components/media/media-grid.mjs +106 -86
  55. package/dist/client/components/media/media-picker-dialog.mjs +242 -230
  56. package/dist/client/components/preview/live-preview-mode.mjs +1 -1
  57. package/dist/client/components/primitives/asset-preview.mjs +37 -22
  58. package/dist/client/components/primitives/date-input.mjs +212 -249
  59. package/dist/client/components/primitives/dropzone.mjs +192 -159
  60. package/dist/client/components/primitives/field-select-control.mjs +93 -0
  61. package/dist/client/components/primitives/select-multi.mjs +406 -365
  62. package/dist/client/components/primitives/select-single.mjs +391 -323
  63. package/dist/client/components/primitives/time-input.mjs +2 -2
  64. package/dist/client/components/sheets/resource-sheet.mjs +2 -0
  65. package/dist/client/components/ui/accordion.mjs +4 -4
  66. package/dist/client/components/ui/alert.mjs +3 -3
  67. package/dist/client/components/ui/badge.mjs +4 -4
  68. package/dist/client/components/ui/button.mjs +47 -37
  69. package/dist/client/components/ui/card.mjs +2 -2
  70. package/dist/client/components/ui/checkbox.mjs +1 -1
  71. package/dist/client/components/ui/command.mjs +5 -5
  72. package/dist/client/components/ui/dialog.mjs +3 -3
  73. package/dist/client/components/ui/drawer.mjs +1 -1
  74. package/dist/client/components/ui/dropdown-menu.mjs +157 -15
  75. package/dist/client/components/ui/empty-state.mjs +88 -59
  76. package/dist/client/components/ui/field.mjs +2 -2
  77. package/dist/client/components/ui/input-group.mjs +3 -3
  78. package/dist/client/components/ui/input.mjs +1 -1
  79. package/dist/client/components/ui/kbd.mjs +1 -1
  80. package/dist/client/components/ui/label.mjs +1 -1
  81. package/dist/client/components/ui/popover.mjs +19 -11
  82. package/dist/client/components/ui/scroll-fade.mjs +170 -0
  83. package/dist/client/components/ui/search-input.mjs +1 -1
  84. package/dist/client/components/ui/select.mjs +129 -27
  85. package/dist/client/components/ui/sheet.mjs +54 -34
  86. package/dist/client/components/ui/sidebar.mjs +15 -14
  87. package/dist/client/components/ui/skeleton.mjs +28 -12
  88. package/dist/client/components/ui/switch.mjs +2 -2
  89. package/dist/client/components/ui/table.mjs +82 -74
  90. package/dist/client/components/ui/tabs.mjs +26 -31
  91. package/dist/client/components/ui/textarea.mjs +1 -1
  92. package/dist/client/components/ui/tooltip.mjs +1 -1
  93. package/dist/client/components/widgets/chart-widget.mjs +154 -100
  94. package/dist/client/components/widgets/progress-widget.mjs +63 -36
  95. package/dist/client/components/widgets/quick-actions-widget.mjs +207 -115
  96. package/dist/client/components/widgets/recent-items-widget.mjs +147 -103
  97. package/dist/client/components/widgets/stats-widget.mjs +91 -72
  98. package/dist/client/components/widgets/table-widget.mjs +161 -247
  99. package/dist/client/components/widgets/timeline-widget.mjs +119 -78
  100. package/dist/client/components/widgets/value-widget.mjs +286 -157
  101. package/dist/client/components/widgets/widget-empty-state.mjs +88 -0
  102. package/dist/client/components/widgets/widget-skeletons.mjs +53 -20
  103. package/dist/client/contexts/focus-context.d.mts +2 -2
  104. package/dist/client/hooks/use-action.mjs +63 -55
  105. package/dist/client/hooks/use-audit-history.mjs +1 -65
  106. package/dist/client/hooks/use-collection-validation.mjs +36 -23
  107. package/dist/client/hooks/use-collection.mjs +96 -1
  108. package/dist/client/hooks/use-saved-views.mjs +70 -49
  109. package/dist/client/hooks/use-server-actions.mjs +70 -46
  110. package/dist/client/hooks/use-server-validation.mjs +156 -41
  111. package/dist/client/hooks/use-server-widget-data.mjs +1 -1
  112. package/dist/client/hooks/use-setup-status.d.mts +3 -3
  113. package/dist/client/hooks/use-setup-status.mjs +2 -2
  114. package/dist/client/hooks/use-transition-stage.mjs +2 -10
  115. package/dist/client/hooks/use-validation-error-map.mjs +31 -13
  116. package/dist/client/hooks/use-view-state.mjs +238 -174
  117. package/dist/client/i18n/date-locale.mjs +33 -0
  118. package/dist/client/i18n/hooks.mjs +17 -1
  119. package/dist/client/lib/utils.mjs +3 -2
  120. package/dist/client/preview/block-scope-context.d.mts +2 -2
  121. package/dist/client/preview/preview-banner.d.mts +2 -2
  122. package/dist/client/preview/preview-banner.mjs +75 -46
  123. package/dist/client/preview/preview-field.d.mts +4 -4
  124. package/dist/client/preview/preview-field.mjs +2 -2
  125. package/dist/client/runtime/provider.mjs +8 -1
  126. package/dist/client/runtime/translations-provider.mjs +1 -1
  127. package/dist/client/scope/picker.d.mts +2 -2
  128. package/dist/client/scope/provider.d.mts +2 -2
  129. package/dist/client/styles/base.css +1022 -0
  130. package/dist/client/styles/index.css +3 -589
  131. package/dist/client/utils/auto-expand-fields.mjs +4 -2
  132. package/dist/client/utils/keyboard-shortcuts.mjs +26 -0
  133. package/dist/client/utils/use-lazy-component.mjs +80 -0
  134. package/dist/client/views/auth/accept-invite-form.d.mts +2 -2
  135. package/dist/client/views/auth/auth-layout.d.mts +17 -10
  136. package/dist/client/views/auth/auth-layout.mjs +291 -80
  137. package/dist/client/views/auth/forgot-password-form.d.mts +2 -2
  138. package/dist/client/views/auth/forgot-password-form.mjs +2 -2
  139. package/dist/client/views/auth/login-form.d.mts +2 -2
  140. package/dist/client/views/auth/login-form.mjs +1 -1
  141. package/dist/client/views/auth/reset-password-form.d.mts +2 -2
  142. package/dist/client/views/auth/reset-password-form.mjs +2 -2
  143. package/dist/client/views/auth/setup-form.d.mts +2 -2
  144. package/dist/client/views/collection/auto-form-fields.mjs +11 -9
  145. package/dist/client/views/collection/bulk-action-toolbar.mjs +173 -138
  146. package/dist/client/views/collection/cells/complex-cells.mjs +22 -22
  147. package/dist/client/views/collection/cells/primitive-cells.mjs +1 -1
  148. package/dist/client/views/collection/cells/relation-cells.mjs +147 -129
  149. package/dist/client/views/collection/cells/shared/asset-thumbnail.mjs +224 -278
  150. package/dist/client/views/collection/cells/shared/relation-chip.mjs +64 -36
  151. package/dist/client/views/collection/cells/upload-cells.mjs +199 -9
  152. package/dist/client/views/collection/columns/build-columns.mjs +29 -9
  153. package/dist/client/views/collection/columns/column-defaults.mjs +2 -2
  154. package/dist/client/views/collection/field-renderer.mjs +50 -89
  155. package/dist/client/views/collection/form-view.mjs +237 -227
  156. package/dist/client/views/collection/table-view.mjs +1167 -234
  157. package/dist/client/views/collection/view-skeletons.mjs +222 -79
  158. package/dist/client/views/common/global-search.mjs +29 -18
  159. package/dist/client/views/dashboard/dashboard-grid.mjs +678 -501
  160. package/dist/client/views/dashboard/dashboard-widget.mjs +6 -3
  161. package/dist/client/views/dashboard/widget-card.mjs +23 -14
  162. package/dist/client/views/globals/global-form-view.mjs +634 -589
  163. package/dist/client/views/layout/admin-layout-provider.mjs +67 -70
  164. package/dist/client/views/layout/admin-layout.d.mts +3 -6
  165. package/dist/client/views/layout/admin-layout.mjs +152 -155
  166. package/dist/client/views/layout/admin-router.mjs +936 -616
  167. package/dist/client/views/layout/admin-sidebar.d.mts +38 -1
  168. package/dist/client/views/layout/admin-sidebar.mjs +762 -592
  169. package/dist/client/views/layout/admin-theme.d.mts +10 -0
  170. package/dist/client/views/layout/admin-theme.mjs +84 -0
  171. package/dist/client/views/layout/admin-view-layout.mjs +161 -0
  172. package/dist/client/views/pages/accept-invite-page.d.mts +2 -2
  173. package/dist/client/views/pages/accept-invite-page.mjs +49 -26
  174. package/dist/client/views/pages/dashboard-page.d.mts +2 -2
  175. package/dist/client/views/pages/forgot-password-page.d.mts +2 -2
  176. package/dist/client/views/pages/forgot-password-page.mjs +2 -19
  177. package/dist/client/views/pages/invite-page.d.mts +2 -2
  178. package/dist/client/views/pages/invite-page.mjs +2 -19
  179. package/dist/client/views/pages/login-page.d.mts +3 -3
  180. package/dist/client/views/pages/login-page.mjs +4 -21
  181. package/dist/client/views/pages/reset-password-page.d.mts +2 -2
  182. package/dist/client/views/pages/reset-password-page.mjs +3 -20
  183. package/dist/client/views/pages/setup-page.d.mts +2 -2
  184. package/dist/client/views/pages/setup-page.mjs +70 -71
  185. package/dist/client.d.mts +6 -2
  186. package/dist/client.mjs +2 -1
  187. package/dist/components/rich-text/rich-text-renderer.d.mts +2 -2
  188. package/dist/index.d.mts +6 -2
  189. package/dist/index.mjs +2 -1
  190. package/dist/server/augmentation/dashboard.d.mts +67 -3
  191. package/dist/server/augmentation/form-layout.d.mts +21 -0
  192. package/dist/server/augmentation/index.d.mts +1 -1
  193. package/dist/server/codegen/admin-client-template.mjs +4 -0
  194. package/dist/server/fields/blocks.d.mts +1 -1
  195. package/dist/server/fields/blocks.mjs +12 -0
  196. package/dist/server/fields/rich-text.d.mts +1 -1
  197. package/dist/server/fields/rich-text.mjs +8 -0
  198. package/dist/server/i18n/index.mjs +29 -7
  199. package/dist/server/i18n/messages/cs.mjs +414 -1
  200. package/dist/server/i18n/messages/de.mjs +412 -1
  201. package/dist/server/i18n/messages/en.mjs +166 -1
  202. package/dist/server/i18n/messages/es.mjs +412 -1
  203. package/dist/server/i18n/messages/fr.mjs +412 -1
  204. package/dist/server/i18n/messages/pl.mjs +416 -1
  205. package/dist/server/i18n/messages/pt.mjs +409 -1
  206. package/dist/server/i18n/messages/sk.mjs +216 -2
  207. package/dist/server/modules/admin/block/introspection.mjs +4 -1
  208. package/dist/server/modules/admin/block/prefetch.mjs +12 -2
  209. package/dist/server/modules/admin/collections/account.d.mts +2 -2
  210. package/dist/server/modules/admin/collections/admin-locks.d.mts +2 -2
  211. package/dist/server/modules/admin/collections/admin-preferences.d.mts +39 -39
  212. package/dist/server/modules/admin/collections/admin-saved-views.d.mts +47 -47
  213. package/dist/server/modules/admin/collections/apikey.d.mts +42 -42
  214. package/dist/server/modules/admin/collections/assets.d.mts +20 -20
  215. package/dist/server/modules/admin/collections/assets.mjs +0 -1
  216. package/dist/server/modules/admin/collections/session.d.mts +42 -42
  217. package/dist/server/modules/admin/collections/user.d.mts +40 -28
  218. package/dist/server/modules/admin/collections/user.mjs +40 -9
  219. package/dist/server/modules/admin/collections/verification.d.mts +36 -36
  220. package/dist/server/modules/admin/dto/admin-config.dto.mjs +2 -0
  221. package/dist/server/modules/admin/factories.mjs +7 -18
  222. package/dist/server/modules/admin/index.d.mts +1 -1
  223. package/dist/server/modules/admin/routes/admin-config.d.mts +2 -2
  224. package/dist/server/modules/admin/routes/admin-config.mjs +34 -16
  225. package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
  226. package/dist/server/modules/admin/routes/execute-action.mjs +67 -28
  227. package/dist/server/modules/admin/routes/i18n-helpers.mjs +34 -0
  228. package/dist/server/modules/admin/routes/locales.d.mts +2 -2
  229. package/dist/server/modules/admin/routes/preview.mjs +25 -17
  230. package/dist/server/modules/admin/routes/reactive.d.mts +9 -9
  231. package/dist/server/modules/admin/routes/route-helpers.mjs +1 -1
  232. package/dist/server/modules/admin/routes/setup.d.mts +10 -10
  233. package/dist/server/modules/admin/routes/setup.mjs +16 -13
  234. package/dist/server/modules/admin/routes/translations.d.mts +4 -4
  235. package/dist/server/modules/admin/routes/translations.mjs +5 -1
  236. package/dist/server/modules/admin-preferences/collections/admin-preferences.mjs +1 -1
  237. package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +2 -2
  238. package/dist/server/modules/audit/.generated/module.d.mts +1 -1
  239. package/dist/server/modules/audit/.generated/module.mjs +1 -1
  240. package/dist/server/modules/audit/collections/audit-log.d.mts +2 -2
  241. package/dist/server/modules/audit/collections/audit-log.mjs +1 -1
  242. package/dist/server/modules/audit/config/app.mjs +99 -42
  243. package/dist/server/modules/audit/jobs/audit-cleanup.mjs +1 -1
  244. package/dist/server/plugin.mjs +4 -2
  245. package/dist/server/proxy-factories.d.mts +4 -3
  246. package/dist/server/proxy-factories.mjs +34 -8
  247. package/dist/shared/types/saved-views.types.d.mts +2 -0
  248. package/package.json +6 -4
  249. package/dist/client/components/fields/rich-text-editor/link-popover.mjs +0 -85
  250. package/dist/client/components/ui/spinner.mjs +0 -52
  251. package/dist/client/components/ui/toolbar.mjs +0 -136
  252. package/dist/client/contexts/breadcrumb-context.mjs +0 -60
  253. package/dist/client/views/layout/admin-topbar.mjs +0 -236
@@ -5,39 +5,53 @@ import { useSafeContentLocales } from "../../runtime/content-locales-provider.mj
5
5
  import { useScopedLocale } from "../../runtime/locale-scope.mjs";
6
6
  import { Button } from "../../components/ui/button.mjs";
7
7
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../components/ui/select.mjs";
8
+ import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from "../../components/ui/sheet.mjs";
9
+ import { sanitizeFilename } from "../../components/fields/field-utils.mjs";
10
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../../components/ui/tooltip.mjs";
8
11
  import { LocaleSwitcher } from "../../components/locale-switcher.mjs";
9
12
  import { Checkbox } from "../../components/ui/checkbox.mjs";
10
13
  import { flattenOptions } from "../../components/primitives/types.mjs";
11
14
  import { createActionRegistryProxy } from "../../builder/types/action-registry.mjs";
15
+ import { ActionButton } from "../../components/actions/action-button.mjs";
12
16
  import { useCollectionFields } from "../../hooks/use-collection-fields.mjs";
13
17
  import { useSuspenseCollectionMeta } from "../../hooks/use-collection-meta.mjs";
14
18
  import { ActionDialog } from "../../components/actions/action-dialog.mjs";
19
+ import { EmptyState } from "../../components/ui/empty-state.mjs";
20
+ import { ScrollFade } from "../../components/ui/scroll-fade.mjs";
15
21
  import { useSidebarSearchParam } from "../../hooks/use-sidebar-search-param.mjs";
16
- import { useCollectionDelete, useCollectionList, useCollectionRestore } from "../../hooks/use-collection.mjs";
22
+ import { useCollectionDelete, useCollectionList, useCollectionRestore, useCollectionUpdateBatch } from "../../hooks/use-collection.mjs";
17
23
  import { useSessionState } from "../../hooks/use-current-user.mjs";
18
24
  import { getLockUser, useLocks } from "../../hooks/use-locks.mjs";
19
25
  import { mergeServerActions, useServerActions } from "../../hooks/use-server-actions.mjs";
26
+ import { AdminViewHeader, AdminViewLayout } from "../layout/admin-view-layout.mjs";
27
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../components/ui/table.mjs";
20
28
  import { TableViewSkeleton } from "./view-skeletons.mjs";
29
+ import { ResourceSheet } from "../../components/sheets/resource-sheet.mjs";
30
+ import { useUploadCollection } from "../../hooks/use-upload-collection.mjs";
31
+ import { useUpload } from "../../hooks/use-upload.mjs";
32
+ import { AssetPreview } from "../../components/primitives/asset-preview.mjs";
33
+ import { Dropzone } from "../../components/primitives/dropzone.mjs";
21
34
  import { useDebouncedValue, useSearch } from "../../hooks/use-search.mjs";
35
+ import { autoExpandFields, hasFieldsToExpand } from "../../utils/auto-expand-fields.mjs";
36
+ import { computeDefaultColumns, getAllAvailableFields } from "./columns/column-defaults.mjs";
37
+ import { buildColumns } from "./columns/build-columns.mjs";
22
38
  import { HeaderActions } from "../../components/actions/header-actions.mjs";
23
39
  import { FilterBuilderSheet } from "../../components/filter-builder/filter-builder-sheet.mjs";
24
- import { EmptyState } from "../../components/ui/empty-state.mjs";
25
40
  import { SearchInput } from "../../components/ui/search-input.mjs";
26
- import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../components/ui/table.mjs";
27
- import { Toolbar, ToolbarSection, ToolbarSeparator } from "../../components/ui/toolbar.mjs";
28
41
  import { useActions } from "../../hooks/use-action.mjs";
29
42
  import { useRealtimeHighlight } from "../../hooks/use-realtime-highlight.mjs";
30
43
  import { useDeleteSavedView, useSaveView, useSavedViews } from "../../hooks/use-saved-views.mjs";
31
44
  import { useViewState } from "../../hooks/use-view-state.mjs";
32
- import { autoExpandFields, hasFieldsToExpand } from "../../utils/auto-expand-fields.mjs";
33
45
  import { BulkActionToolbar } from "./bulk-action-toolbar.mjs";
34
- import { computeDefaultColumns, getAllAvailableFields } from "./columns/column-defaults.mjs";
35
- import { buildColumns } from "./columns/build-columns.mjs";
36
46
  import { c } from "react/compiler-runtime";
37
47
  import { Icon } from "@iconify/react";
38
48
  import * as React from "react";
39
49
  import { Suspense, useMemo, useState } from "react";
40
50
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
51
+ import { DndContext, DragOverlay, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
52
+ import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
53
+ import { CSS } from "@dnd-kit/utilities";
54
+ import { toast } from "sonner";
41
55
  import { flexRender, getCoreRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
42
56
 
43
57
  //#region src/client/views/collection/table-view.tsx
@@ -48,8 +62,198 @@ import { flexRender, getCoreRowModel, getSortedRowModel, useReactTable } from "@
48
62
  * This is the default list view registered in the admin view registry.
49
63
  */
50
64
  const actionRegistry = createActionRegistryProxy();
65
+ const STICKY_TABLE_COLUMN_COUNT = 2;
66
+ const REORDER_DROP_DURATION = 160;
67
+ const REORDER_MOVE_EASING = "cubic-bezier(0.25, 1, 0.5, 1)";
68
+ const REORDER_DROP_ANIMATION = {
69
+ duration: REORDER_DROP_DURATION,
70
+ easing: REORDER_MOVE_EASING
71
+ };
72
+ function UploadCollectionButton(t0) {
73
+ const $ = c(13);
74
+ const { collection, onUploaded } = t0;
75
+ const { t } = useTranslation();
76
+ const [open, setOpen] = React.useState(false);
77
+ let t1;
78
+ let t2;
79
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
80
+ t1 = () => setOpen(true);
81
+ t2 = /* @__PURE__ */ jsx(Icon, {
82
+ icon: "ph:cloud-arrow-up",
83
+ className: "size-3.5"
84
+ });
85
+ $[0] = t1;
86
+ $[1] = t2;
87
+ } else {
88
+ t1 = $[0];
89
+ t2 = $[1];
90
+ }
91
+ let t3;
92
+ if ($[2] !== t) {
93
+ t3 = t("common.upload");
94
+ $[2] = t;
95
+ $[3] = t3;
96
+ } else t3 = $[3];
97
+ let t4;
98
+ if ($[4] !== t3) {
99
+ t4 = /* @__PURE__ */ jsxs(Button, {
100
+ variant: "default",
101
+ size: "sm",
102
+ className: "gap-2",
103
+ onClick: t1,
104
+ children: [t2, t3]
105
+ });
106
+ $[4] = t3;
107
+ $[5] = t4;
108
+ } else t4 = $[5];
109
+ let t5;
110
+ if ($[6] !== collection || $[7] !== onUploaded || $[8] !== open) {
111
+ t5 = /* @__PURE__ */ jsx(UploadCollectionSheet, {
112
+ open,
113
+ onOpenChange: setOpen,
114
+ collection,
115
+ onUploaded
116
+ });
117
+ $[6] = collection;
118
+ $[7] = onUploaded;
119
+ $[8] = open;
120
+ $[9] = t5;
121
+ } else t5 = $[9];
122
+ let t6;
123
+ if ($[10] !== t4 || $[11] !== t5) {
124
+ t6 = /* @__PURE__ */ jsxs(Fragment, { children: [t4, t5] });
125
+ $[10] = t4;
126
+ $[11] = t5;
127
+ $[12] = t6;
128
+ } else t6 = $[12];
129
+ return t6;
130
+ }
131
+ function UploadCollectionSheet({ open, onOpenChange, collection, onUploaded }) {
132
+ const { t } = useTranslation();
133
+ const { uploadMany, isUploading, progress } = useUpload();
134
+ const [uploadedAssets, setUploadedAssets] = React.useState([]);
135
+ const [editAssetId, setEditAssetId] = React.useState(null);
136
+ React.useEffect(() => {
137
+ if (!open) {
138
+ setUploadedAssets([]);
139
+ setEditAssetId(null);
140
+ }
141
+ }, [open]);
142
+ const handleValidationError = React.useCallback((errors) => {
143
+ for (const validationError of errors) toast.error(validationError.message);
144
+ }, []);
145
+ const handleDrop = React.useCallback(async (files) => {
146
+ if (files.length === 0 || isUploading) return;
147
+ const sanitizedFiles = files.map((file) => new File([file], sanitizeFilename(file.name), {
148
+ type: file.type,
149
+ lastModified: file.lastModified
150
+ }));
151
+ try {
152
+ const uploaded = await uploadMany(sanitizedFiles, { to: collection });
153
+ setUploadedAssets((current) => [...uploaded, ...current]);
154
+ toast.success(t("upload.bulkSuccess", { count: uploaded.length }));
155
+ await onUploaded?.();
156
+ } catch (error) {
157
+ toast.error(error instanceof Error ? error.message : t("upload.error"));
158
+ }
159
+ }, [
160
+ collection,
161
+ isUploading,
162
+ onUploaded,
163
+ t,
164
+ uploadMany
165
+ ]);
166
+ return /* @__PURE__ */ jsx(Sheet, {
167
+ open,
168
+ onOpenChange,
169
+ modal: false,
170
+ children: /* @__PURE__ */ jsxs(SheetContent, {
171
+ side: "right",
172
+ showOverlay: false,
173
+ className: "qa-upload-sheet w-full p-0 data-[side=right]:sm:max-w-xl",
174
+ children: [
175
+ /* @__PURE__ */ jsxs(SheetHeader, {
176
+ className: "border-b px-6 py-5",
177
+ children: [/* @__PURE__ */ jsx(SheetTitle, { children: t("upload.bulkTitle") }), /* @__PURE__ */ jsx(SheetDescription, { children: t("upload.bulkDescription") })]
178
+ }),
179
+ /* @__PURE__ */ jsxs("div", {
180
+ className: "flex flex-1 flex-col gap-5 overflow-y-auto px-6 py-5",
181
+ children: [/* @__PURE__ */ jsx(Dropzone, {
182
+ onDrop: handleDrop,
183
+ multiple: true,
184
+ loading: isUploading,
185
+ progress: isUploading ? progress : void 0,
186
+ label: t("upload.dropzone"),
187
+ hint: t("upload.bulkHint"),
188
+ onValidationError: handleValidationError
189
+ }), uploadedAssets.length > 0 && /* @__PURE__ */ jsxs("div", {
190
+ className: "space-y-3",
191
+ children: [/* @__PURE__ */ jsx("p", {
192
+ className: "text-muted-foreground font-chrome chrome-meta text-xs font-medium",
193
+ children: t("upload.uploadedCount", { count: uploadedAssets.length })
194
+ }), /* @__PURE__ */ jsx("div", {
195
+ className: "grid gap-2",
196
+ children: uploadedAssets.map((asset) => /* @__PURE__ */ jsx(AssetPreview, {
197
+ asset,
198
+ variant: "compact",
199
+ onEdit: () => setEditAssetId(asset.id)
200
+ }, asset.id))
201
+ })]
202
+ })]
203
+ }),
204
+ /* @__PURE__ */ jsx(SheetFooter, {
205
+ className: "border-t px-6 py-4",
206
+ children: /* @__PURE__ */ jsx(Button, {
207
+ variant: "outline",
208
+ onClick: () => onOpenChange(false),
209
+ disabled: isUploading,
210
+ children: t("common.close")
211
+ })
212
+ }),
213
+ editAssetId && /* @__PURE__ */ jsx(ResourceSheet, {
214
+ type: "collection",
215
+ collection,
216
+ itemId: editAssetId,
217
+ open: !!editAssetId,
218
+ onOpenChange: (nextOpen) => {
219
+ if (!nextOpen) setEditAssetId(null);
220
+ },
221
+ onSave: () => {
222
+ onUploaded?.();
223
+ }
224
+ })
225
+ ]
226
+ })
227
+ });
228
+ }
229
+ function getColumnSizeStyle(width) {
230
+ return {
231
+ width,
232
+ minWidth: width,
233
+ maxWidth: width
234
+ };
235
+ }
236
+ function getColumnSize(column, fallback = 120) {
237
+ return typeof column?.getSize === "function" ? column.getSize() : fallback;
238
+ }
239
+ function getStickyLeftOffset(columns, index) {
240
+ return columns.slice(0, index).reduce((left, column, columnIndex) => {
241
+ return left + getColumnSize(column, columnIndex === 0 ? 40 : 360);
242
+ }, 0);
243
+ }
244
+ function getActionReferenceType(reference) {
245
+ if (typeof reference === "string") return reference;
246
+ if (typeof reference === "function") {
247
+ const actionType = reference.type;
248
+ if (typeof actionType === "string") return actionType;
249
+ const resolved = reference();
250
+ return typeof resolved === "string" ? resolved : void 0;
251
+ }
252
+ return typeof reference?.type === "string" ? reference.type : void 0;
253
+ }
51
254
  function resolveBuiltinListAction(reference) {
52
- const type = typeof reference === "string" ? reference : typeof reference?.type === "string" ? reference.type : void 0;
255
+ if (typeof reference === "object" && reference !== null && "id" in reference && "handler" in reference) return reference;
256
+ const type = getActionReferenceType(reference);
53
257
  const config = typeof reference === "object" && reference !== null ? reference.config : void 0;
54
258
  if (!type) return null;
55
259
  switch (type) {
@@ -64,29 +268,302 @@ function mapActionReferencesToDefinitions(references) {
64
268
  if (!Array.isArray(references)) return [];
65
269
  return references.map((reference) => resolveBuiltinListAction(reference)).filter((action) => action !== null);
66
270
  }
271
+ function mapListActionsToDefinitions(actions) {
272
+ if (!actions || typeof actions !== "object") return void 0;
273
+ const listActions = actions;
274
+ const header = listActions.header ? {
275
+ primary: mapActionReferencesToDefinitions(listActions.header.primary),
276
+ secondary: mapActionReferencesToDefinitions(listActions.header.secondary)
277
+ } : void 0;
278
+ const row = mapActionReferencesToDefinitions(listActions.row);
279
+ const bulk = mapActionReferencesToDefinitions(listActions.bulk);
280
+ if (!header && row.length === 0 && bulk.length === 0) return void 0;
281
+ return {
282
+ ...header ? { header } : {},
283
+ ...row.length > 0 ? { row } : {},
284
+ ...bulk.length > 0 ? { bulk } : {}
285
+ };
286
+ }
67
287
  function mapListSchemaToConfig(list) {
68
288
  if (!list) return void 0;
69
289
  const config = {};
70
290
  if (list.columns?.length) config.columns = list.columns;
71
291
  if (list.defaultSort) config.defaultSort = list.defaultSort;
292
+ if (list.orderable) config.orderable = list.orderable;
72
293
  if (list.searchable?.length) {
73
294
  config.searchFields = list.searchable;
74
295
  config.searchable = true;
75
296
  }
76
- if (list.actions && typeof list.actions === "object") {
77
- const listActions = list.actions;
78
- const header = listActions.header ? {
79
- primary: mapActionReferencesToDefinitions(listActions.header.primary),
80
- secondary: mapActionReferencesToDefinitions(listActions.header.secondary)
81
- } : void 0;
82
- const bulk = mapActionReferencesToDefinitions(listActions.bulk);
83
- if (header || bulk.length > 0) config.actions = {
84
- ...header ? { header } : {},
85
- ...bulk.length > 0 ? { bulk } : {}
86
- };
87
- }
297
+ if (list.grouping?.fields?.length) config.grouping = list.grouping;
298
+ config.actions = mapListActionsToDefinitions(list.actions);
88
299
  return config;
89
300
  }
301
+ function stringifyGroupValue(value, field, resolveText, noValueLabel = "No value") {
302
+ if (value === null || value === void 0 || value === "") return noValueLabel;
303
+ if (Array.isArray(value)) return value.length > 0 ? value.map((item) => stringifyGroupValue(item, field, resolveText, noValueLabel)).join(", ") : noValueLabel;
304
+ const options = field?.options?.options;
305
+ if (options) {
306
+ const option = flattenOptions(options).find((item) => String(item.value) === String(value));
307
+ if (option) return resolveText?.(option.label, String(option.value)) ?? String(option.label);
308
+ }
309
+ if (typeof value === "object") {
310
+ const record = value;
311
+ const displayValue = record.title ?? record.name ?? record.label ?? record.id;
312
+ return resolveText?.(displayValue, "Object") ?? String(displayValue ?? "Object");
313
+ }
314
+ return String(value);
315
+ }
316
+ function getGroupSortIndex(value, field) {
317
+ const options = field?.options?.options;
318
+ if (!options) return Number.MAX_SAFE_INTEGER;
319
+ const compareValue = Array.isArray(value) ? value[0] : value;
320
+ const index = flattenOptions(options).findIndex((option) => String(option.value) === String(compareValue));
321
+ return index === -1 ? Number.MAX_SAFE_INTEGER : index;
322
+ }
323
+ const ReorderRowContext = React.createContext(null);
324
+ function ReorderHandle() {
325
+ const $ = c(9);
326
+ const sortable = React.useContext(ReorderRowContext);
327
+ const t0 = sortable?.setActivatorNodeRef;
328
+ let t1;
329
+ if ($[0] !== sortable?.attributes) {
330
+ t1 = sortable?.attributes ?? {};
331
+ $[0] = sortable?.attributes;
332
+ $[1] = t1;
333
+ } else t1 = $[1];
334
+ let t2;
335
+ if ($[2] !== sortable?.listeners) {
336
+ t2 = sortable?.listeners ?? {};
337
+ $[2] = sortable?.listeners;
338
+ $[3] = t2;
339
+ } else t2 = $[3];
340
+ let t3;
341
+ if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
342
+ t3 = /* @__PURE__ */ jsx(Icon, {
343
+ icon: "ph:dots-six-vertical",
344
+ className: "size-3.5"
345
+ });
346
+ $[4] = t3;
347
+ } else t3 = $[4];
348
+ let t4;
349
+ if ($[5] !== t0 || $[6] !== t1 || $[7] !== t2) {
350
+ t4 = /* @__PURE__ */ jsx("button", {
351
+ type: "button",
352
+ ref: t0,
353
+ className: "text-muted-foreground/50 hover:text-muted-foreground focus-visible:ring-ring/40 flex h-8 w-full cursor-grab touch-none items-center justify-center rounded-md transition-colors select-none focus-visible:ring-2 focus-visible:outline-none active:cursor-grabbing",
354
+ "aria-label": "Drag to reorder",
355
+ ...t1,
356
+ ...t2,
357
+ children: t3
358
+ });
359
+ $[5] = t0;
360
+ $[6] = t1;
361
+ $[7] = t2;
362
+ $[8] = t4;
363
+ } else t4 = $[8];
364
+ return t4;
365
+ }
366
+ function ReorderDragOverlay(t0) {
367
+ const $ = c(24);
368
+ const { row, columns, rect } = t0;
369
+ if (!row) return null;
370
+ let t1;
371
+ let t2;
372
+ let t3;
373
+ let t4;
374
+ let t5;
375
+ if ($[0] !== columns || $[1] !== rect?.height || $[2] !== rect?.width || $[3] !== row) {
376
+ const cells = row.getVisibleCells?.() ?? [];
377
+ t4 = "bg-background text-foreground ring-border-strong pointer-events-none overflow-hidden rounded-md shadow-xl ring-1";
378
+ const t6$1 = rect?.width;
379
+ const t7$1 = rect?.height;
380
+ if ($[9] !== t6$1 || $[10] !== t7$1) {
381
+ t5 = {
382
+ width: t6$1,
383
+ height: t7$1
384
+ };
385
+ $[9] = t6$1;
386
+ $[10] = t7$1;
387
+ $[11] = t5;
388
+ } else t5 = $[11];
389
+ t1 = "grid h-full items-center";
390
+ let t8;
391
+ if ($[12] !== columns) {
392
+ t8 = columns.map(_temp3).join(" ");
393
+ $[12] = columns;
394
+ $[13] = t8;
395
+ } else t8 = $[13];
396
+ if ($[14] !== t8) {
397
+ t2 = { gridTemplateColumns: t8 };
398
+ $[14] = t8;
399
+ $[15] = t2;
400
+ } else t2 = $[15];
401
+ t3 = cells.map(_temp4);
402
+ $[0] = columns;
403
+ $[1] = rect?.height;
404
+ $[2] = rect?.width;
405
+ $[3] = row;
406
+ $[4] = t1;
407
+ $[5] = t2;
408
+ $[6] = t3;
409
+ $[7] = t4;
410
+ $[8] = t5;
411
+ } else {
412
+ t1 = $[4];
413
+ t2 = $[5];
414
+ t3 = $[6];
415
+ t4 = $[7];
416
+ t5 = $[8];
417
+ }
418
+ let t6;
419
+ if ($[16] !== t1 || $[17] !== t2 || $[18] !== t3) {
420
+ t6 = /* @__PURE__ */ jsx("div", {
421
+ className: t1,
422
+ style: t2,
423
+ children: t3
424
+ });
425
+ $[16] = t1;
426
+ $[17] = t2;
427
+ $[18] = t3;
428
+ $[19] = t6;
429
+ } else t6 = $[19];
430
+ let t7;
431
+ if ($[20] !== t4 || $[21] !== t5 || $[22] !== t6) {
432
+ t7 = /* @__PURE__ */ jsx("div", {
433
+ className: t4,
434
+ style: t5,
435
+ children: t6
436
+ });
437
+ $[20] = t4;
438
+ $[21] = t5;
439
+ $[22] = t6;
440
+ $[23] = t7;
441
+ } else t7 = $[23];
442
+ return t7;
443
+ }
444
+ function _temp4(cell, index) {
445
+ return /* @__PURE__ */ jsx("div", {
446
+ className: cn("min-w-0 truncate px-3 py-1.5 text-sm whitespace-nowrap tabular-nums", index === 0 && "px-1.5 text-center"),
447
+ children: index === 0 ? /* @__PURE__ */ jsx(Icon, {
448
+ icon: "ph:dots-six-vertical",
449
+ className: "text-muted-foreground mx-auto size-3.5"
450
+ }) : flexRender(cell.column.columnDef.cell, cell.getContext())
451
+ }, cell.id);
452
+ }
453
+ function _temp3(column) {
454
+ return `${getColumnSize(column, 120)}px`;
455
+ }
456
+ function SortableTableRow(t0) {
457
+ const $ = c(33);
458
+ let children;
459
+ let className;
460
+ let id;
461
+ let props;
462
+ if ($[0] !== t0) {
463
+ ({id, className, children, ...props} = t0);
464
+ $[0] = t0;
465
+ $[1] = children;
466
+ $[2] = className;
467
+ $[3] = id;
468
+ $[4] = props;
469
+ } else {
470
+ children = $[1];
471
+ className = $[2];
472
+ id = $[3];
473
+ props = $[4];
474
+ }
475
+ let t1;
476
+ if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
477
+ t1 = {
478
+ duration: REORDER_DROP_DURATION,
479
+ easing: REORDER_MOVE_EASING
480
+ };
481
+ $[5] = t1;
482
+ } else t1 = $[5];
483
+ let t2;
484
+ if ($[6] !== id) {
485
+ t2 = {
486
+ id,
487
+ transition: t1
488
+ };
489
+ $[6] = id;
490
+ $[7] = t2;
491
+ } else t2 = $[7];
492
+ const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable(t2);
493
+ const t3 = isDragging && "bg-muted/[0.18] opacity-35";
494
+ let t4;
495
+ if ($[8] !== className || $[9] !== t3) {
496
+ t4 = cn("select-none", t3, className);
497
+ $[8] = className;
498
+ $[9] = t3;
499
+ $[10] = t4;
500
+ } else t4 = $[10];
501
+ let t5;
502
+ if ($[11] !== isDragging || $[12] !== transform) {
503
+ t5 = isDragging ? void 0 : CSS.Transform.toString(transform);
504
+ $[11] = isDragging;
505
+ $[12] = transform;
506
+ $[13] = t5;
507
+ } else t5 = $[13];
508
+ const t6 = isDragging ? void 0 : transition;
509
+ let t7;
510
+ if ($[14] !== props.style) {
511
+ t7 = props.style ?? {};
512
+ $[14] = props.style;
513
+ $[15] = t7;
514
+ } else t7 = $[15];
515
+ let t8;
516
+ if ($[16] !== t5 || $[17] !== t6 || $[18] !== t7) {
517
+ t8 = {
518
+ transform: t5,
519
+ transition: t6,
520
+ ...t7
521
+ };
522
+ $[16] = t5;
523
+ $[17] = t6;
524
+ $[18] = t7;
525
+ $[19] = t8;
526
+ } else t8 = $[19];
527
+ let t9;
528
+ if ($[20] !== attributes || $[21] !== listeners || $[22] !== setActivatorNodeRef) {
529
+ t9 = {
530
+ attributes,
531
+ listeners,
532
+ setActivatorNodeRef
533
+ };
534
+ $[20] = attributes;
535
+ $[21] = listeners;
536
+ $[22] = setActivatorNodeRef;
537
+ $[23] = t9;
538
+ } else t9 = $[23];
539
+ let t10;
540
+ if ($[24] !== children || $[25] !== t9) {
541
+ t10 = /* @__PURE__ */ jsx(ReorderRowContext.Provider, {
542
+ value: t9,
543
+ children
544
+ });
545
+ $[24] = children;
546
+ $[25] = t9;
547
+ $[26] = t10;
548
+ } else t10 = $[26];
549
+ let t11;
550
+ if ($[27] !== props || $[28] !== setNodeRef || $[29] !== t10 || $[30] !== t4 || $[31] !== t8) {
551
+ t11 = /* @__PURE__ */ jsx(TableRow, {
552
+ ref: setNodeRef,
553
+ className: t4,
554
+ style: t8,
555
+ ...props,
556
+ children: t10
557
+ });
558
+ $[27] = props;
559
+ $[28] = setNodeRef;
560
+ $[29] = t10;
561
+ $[30] = t4;
562
+ $[31] = t8;
563
+ $[32] = t11;
564
+ } else t11 = $[32];
565
+ return t11;
566
+ }
90
567
  /**
91
568
  * TableView - Default table-based list view for collections
92
569
  *
@@ -139,10 +616,12 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
139
616
  "use no memo";
140
617
  const globalRealtimeConfig = useAdminStore(selectRealtime);
141
618
  const { fields: resolvedFields, schema } = useCollectionFields(collection, { fallbackFields: config?.fields });
619
+ const { collections: uploadCollections } = useUploadCollection();
142
620
  const schemaListConfig = mapListSchemaToConfig(schema?.admin?.list);
143
621
  const resolvedListConfig = viewConfig ?? (config?.list)?.["~config"] ?? config?.list ?? schemaListConfig;
144
622
  const resolvedRealtime = realtime ?? resolvedListConfig?.realtime ?? globalRealtimeConfig.enabled;
145
- const resolvedActionsConfig = actionsConfig ?? resolvedListConfig?.actions;
623
+ const rawActionsConfig = actionsConfig ?? resolvedListConfig?.actions;
624
+ const resolvedActionsConfig = React.useMemo(() => mapListActionsToDefinitions(rawActionsConfig), [rawActionsConfig]);
146
625
  const { serverActions } = useServerActions({ collection });
147
626
  const mergedActionsConfig = React.useMemo(() => mergeServerActions(resolvedActionsConfig ?? {}, serverActions), [resolvedActionsConfig, serverActions]);
148
627
  const { user } = useSessionState();
@@ -155,6 +634,7 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
155
634
  collection,
156
635
  actionsConfig: mergedActionsConfig
157
636
  });
637
+ const canUploadToCollection = uploadCollections.includes(collection) && schema?.access?.operations?.create?.allowed === true;
158
638
  const columns = useMemo(() => buildColumns({
159
639
  config: {
160
640
  fields: resolvedFields,
@@ -179,6 +659,32 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
179
659
  ]);
180
660
  const [isSheetOpen, setIsSheetOpen] = useSidebarSearchParam("view-options", { legacyKey: "viewOptions" });
181
661
  const [searchTerm, setSearchTerm] = useState("");
662
+ const [isSearchPanelOpen, setIsSearchPanelOpen] = useState(false);
663
+ const [isReorderMode, setIsReorderMode] = useState(false);
664
+ const [activeReorderId, setActiveReorderId] = useState(null);
665
+ const [activeReorderRect, setActiveReorderRect] = useState(null);
666
+ const [optimisticOrderIds, setOptimisticOrderIds] = useState(null);
667
+ const reorderStartOrderIdsRef = React.useRef(null);
668
+ const reorderOverlayCleanupRef = React.useRef(null);
669
+ const clearReorderOverlay = React.useCallback((delay = 0) => {
670
+ if (reorderOverlayCleanupRef.current !== null) {
671
+ window.clearTimeout(reorderOverlayCleanupRef.current);
672
+ reorderOverlayCleanupRef.current = null;
673
+ }
674
+ const clear = () => {
675
+ setActiveReorderId(null);
676
+ setActiveReorderRect(null);
677
+ reorderOverlayCleanupRef.current = null;
678
+ };
679
+ if (delay > 0) {
680
+ reorderOverlayCleanupRef.current = window.setTimeout(clear, delay);
681
+ return;
682
+ }
683
+ clear();
684
+ }, []);
685
+ React.useEffect(() => () => {
686
+ if (reorderOverlayCleanupRef.current !== null) window.clearTimeout(reorderOverlayCleanupRef.current);
687
+ }, []);
182
688
  const defaultColumns = useMemo(() => computeDefaultColumns(resolvedFields, {
183
689
  meta: collectionMeta,
184
690
  configuredColumns: resolvedListConfig?.columns
@@ -187,11 +693,41 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
187
693
  resolvedListConfig?.columns,
188
694
  collectionMeta
189
695
  ]);
190
- const viewState = useViewState(defaultColumns, { realtime: resolvedRealtime }, collection, user?.id);
696
+ const groupingConfig = resolvedListConfig?.grouping;
697
+ const defaultGroupBy = groupingConfig?.defaultField ?? null;
698
+ const orderableConfig = resolvedListConfig?.orderable;
699
+ const isOrderableEnabled = !!orderableConfig;
700
+ const orderField = "order";
701
+ const orderDirection = typeof orderableConfig === "object" ? orderableConfig.direction ?? "asc" : "asc";
702
+ const orderStep = typeof orderableConfig === "object" ? orderableConfig.step ?? 10 : 10;
703
+ const viewState = useViewState(defaultColumns, {
704
+ realtime: resolvedRealtime,
705
+ groupBy: defaultGroupBy
706
+ }, collection, user?.id);
191
707
  const effectiveRealtime = viewState.config.realtime ?? resolvedRealtime;
708
+ const isKnownSortField = React.useCallback((field) => !!field && (field === "_title" || !!resolvedFields?.[field]), [resolvedFields]);
709
+ const hasOrderField = isKnownSortField(orderField);
710
+ const canUseOrderableSort = isOrderableEnabled && hasOrderField;
711
+ const effectiveSort = useMemo(() => {
712
+ if (isKnownSortField(viewState.config.sortConfig?.field)) return viewState.config.sortConfig;
713
+ if (isKnownSortField(resolvedListConfig?.defaultSort?.field)) return resolvedListConfig.defaultSort;
714
+ if (canUseOrderableSort) return {
715
+ field: orderField,
716
+ direction: orderDirection
717
+ };
718
+ return null;
719
+ }, [
720
+ viewState.config.sortConfig,
721
+ resolvedListConfig?.defaultSort,
722
+ canUseOrderableSort,
723
+ orderField,
724
+ orderDirection,
725
+ isKnownSortField
726
+ ]);
192
727
  const queryOptions = useMemo(() => {
193
728
  const options = {};
194
729
  if (collectionMeta?.softDelete) options.includeDeleted = !!viewState.config.includeDeleted;
730
+ if (viewState.config.groupBy) options.groupBy = { field: viewState.config.groupBy };
195
731
  if (hasFieldsToExpand(expandedFields)) options.with = expandedFields;
196
732
  if (viewState.config.filters.length > 0) {
197
733
  const whereConditions = { ...options.where };
@@ -248,12 +784,12 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
248
784
  }
249
785
  };
250
786
  for (const filter of viewState.config.filters) {
251
- const { field, operator: operator_0, value } = filter;
252
- if (!field || field === "_title") continue;
253
- const fieldDef_0 = resolvedFields?.[field];
787
+ const { field: field_0, operator: operator_0, value } = filter;
788
+ if (!field_0 || field_0 === "_title") continue;
789
+ const fieldDef_0 = resolvedFields?.[field_0];
254
790
  const fieldType_0 = fieldDef_0?.name ?? "text";
255
791
  const fieldOptions_1 = fieldDef_0?.["~options"] ?? {};
256
- const relationName = fieldType_0 === "relation" ? fieldOptions_1.relationName ?? field : void 0;
792
+ const relationName = fieldType_0 === "relation" ? fieldOptions_1.relationName ?? field_0 : void 0;
257
793
  const hasRelation = relationName && (relationNames.length === 0 || relationNames.includes(relationName));
258
794
  const isRelationField = fieldType_0 === "relation" && !!hasRelation;
259
795
  if (operator_0 !== "is_empty" && operator_0 !== "is_not_empty" && isEmptyValue(value)) continue;
@@ -265,55 +801,56 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
265
801
  }
266
802
  switch (operator_0) {
267
803
  case "equals":
268
- whereConditions[field] = normalizedValue;
804
+ whereConditions[field_0] = normalizedValue;
269
805
  break;
270
806
  case "not_equals":
271
- whereConditions[field] = { ne: normalizedValue };
807
+ whereConditions[field_0] = { ne: normalizedValue };
272
808
  break;
273
809
  case "contains":
274
- whereConditions[field] = { contains: normalizedValue };
810
+ whereConditions[field_0] = { contains: normalizedValue };
275
811
  break;
276
812
  case "not_contains":
277
- whereConditions[field] = { notIlike: `%${normalizedValue}%` };
813
+ whereConditions[field_0] = { notIlike: `%${normalizedValue}%` };
278
814
  break;
279
815
  case "starts_with":
280
- whereConditions[field] = { startsWith: normalizedValue };
816
+ whereConditions[field_0] = { startsWith: normalizedValue };
281
817
  break;
282
818
  case "ends_with":
283
- whereConditions[field] = { endsWith: normalizedValue };
819
+ whereConditions[field_0] = { endsWith: normalizedValue };
284
820
  break;
285
821
  case "greater_than":
286
- whereConditions[field] = { gt: normalizedValue };
822
+ whereConditions[field_0] = { gt: normalizedValue };
287
823
  break;
288
824
  case "less_than":
289
- whereConditions[field] = { lt: normalizedValue };
825
+ whereConditions[field_0] = { lt: normalizedValue };
290
826
  break;
291
827
  case "greater_than_or_equal":
292
- whereConditions[field] = { gte: normalizedValue };
828
+ whereConditions[field_0] = { gte: normalizedValue };
293
829
  break;
294
830
  case "less_than_or_equal":
295
- whereConditions[field] = { lte: normalizedValue };
831
+ whereConditions[field_0] = { lte: normalizedValue };
296
832
  break;
297
833
  case "in":
298
- whereConditions[field] = { in: Array.isArray(normalizedValue) ? normalizedValue : [normalizedValue] };
834
+ whereConditions[field_0] = { in: Array.isArray(normalizedValue) ? normalizedValue : [normalizedValue] };
299
835
  break;
300
836
  case "not_in":
301
- whereConditions[field] = { notIn: Array.isArray(normalizedValue) ? normalizedValue : [normalizedValue] };
837
+ whereConditions[field_0] = { notIn: Array.isArray(normalizedValue) ? normalizedValue : [normalizedValue] };
302
838
  break;
303
839
  case "is_empty":
304
- whereConditions[field] = { isNull: true };
840
+ whereConditions[field_0] = { isNull: true };
305
841
  break;
306
842
  case "is_not_empty":
307
- whereConditions[field] = { isNotNull: true };
843
+ whereConditions[field_0] = { isNotNull: true };
308
844
  break;
309
845
  }
310
846
  }
311
847
  options.where = whereConditions;
312
848
  }
313
- if (viewState.config.sortConfig) {
314
- const { field: field_0, direction } = viewState.config.sortConfig;
315
- options.orderBy = { [field_0]: direction };
316
- }
849
+ const groupBy = viewState.config.groupBy;
850
+ const sortConfig = effectiveSort;
851
+ if (groupBy && sortConfig?.field && sortConfig.field !== groupBy) options.orderBy = [{ [groupBy]: "asc" }, { [sortConfig.field]: sortConfig.direction }];
852
+ else if (groupBy) options.orderBy = { [groupBy]: sortConfig?.direction ?? "asc" };
853
+ else if (sortConfig) options.orderBy = { [sortConfig.field]: sortConfig.direction };
317
854
  const pageSize = viewState.config.pagination?.pageSize ?? 25;
318
855
  const page = viewState.config.pagination?.page ?? 1;
319
856
  options.limit = pageSize;
@@ -323,7 +860,8 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
323
860
  expandedFields,
324
861
  viewState.config.filters,
325
862
  viewState.config.includeDeleted,
326
- viewState.config.sortConfig,
863
+ viewState.config.groupBy,
864
+ effectiveSort,
327
865
  viewState.config.pagination?.page,
328
866
  viewState.config.pagination?.pageSize,
329
867
  resolvedFields,
@@ -341,18 +879,34 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
341
879
  const { data: listData, isLoading: listLoading, error: listError } = useCollectionList(collection, queryOptions, { enabled: !isSearching }, { realtime: effectiveRealtime });
342
880
  const isLoading = isSearching ? searchLoading : listLoading;
343
881
  const isSearchActive = isSearching && searchFetching;
344
- const { data: savedViewsData, isLoading: savedViewsLoading } = useSavedViews(collection);
345
- const saveViewMutation = useSaveView(collection);
346
- const deleteViewMutation = useDeleteSavedView(collection);
882
+ const { data: savedViewsData, isLoading: savedViewsLoading } = useSavedViews(collection, user?.id);
883
+ const saveViewMutation = useSaveView(collection, user?.id);
884
+ const deleteViewMutation = useDeleteSavedView(collection, user?.id);
347
885
  const deleteMutation = useCollectionDelete(collection);
348
886
  const restoreMutation = useCollectionRestore(collection);
887
+ const updateBatchMutation = useCollectionUpdateBatch(collection);
349
888
  const availableFields = useMemo(() => {
350
889
  return getAllAvailableFields(resolvedFields, { meta: collectionMeta });
351
890
  }, [resolvedFields, collectionMeta]);
891
+ const groupableFields = useMemo(() => {
892
+ const groupableNames = groupingConfig?.fields ?? [];
893
+ if (groupableNames.length === 0) return [];
894
+ const groupableSet = new Set(groupableNames);
895
+ return availableFields.filter((field_1) => groupableSet.has(field_1.name));
896
+ }, [availableFields, groupingConfig?.fields]);
352
897
  const visibleColumnDefs = useMemo(() => {
353
898
  const selectCol = {
354
899
  id: "_select",
355
900
  header: ({ table: t_0 }) => {
901
+ if (isReorderMode) return /* @__PURE__ */ jsx("div", {
902
+ className: "text-muted-foreground/60 flex h-8 items-center justify-center",
903
+ title: "Order",
904
+ "aria-label": "Order",
905
+ children: /* @__PURE__ */ jsx(Icon, {
906
+ icon: "ph:dots-six-vertical",
907
+ className: "size-3.5"
908
+ })
909
+ });
356
910
  const isAllSelected = t_0.getIsAllPageRowsSelected();
357
911
  const isSomeSelected = t_0.getIsSomePageRowsSelected();
358
912
  return /* @__PURE__ */ jsx("div", {
@@ -368,6 +922,7 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
368
922
  });
369
923
  },
370
924
  cell: ({ row }) => {
925
+ if (isReorderMode) return /* @__PURE__ */ jsx(ReorderHandle, {});
371
926
  return /* @__PURE__ */ jsx("div", {
372
927
  role: "presentation",
373
928
  onClick: (e_1) => e_1.stopPropagation(),
@@ -400,15 +955,46 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
400
955
  const col = columnMap.get(colName);
401
956
  if (col) orderedColumns.push(col);
402
957
  }
958
+ if (actions.row.length > 0) orderedColumns.push({
959
+ id: "_actions",
960
+ header: () => /* @__PURE__ */ jsx("span", {
961
+ className: "sr-only",
962
+ children: t("common.actions")
963
+ }),
964
+ cell: ({ row: row_0 }) => /* @__PURE__ */ jsx("div", {
965
+ role: "presentation",
966
+ className: "flex justify-end gap-1",
967
+ onClick: (event) => event.stopPropagation(),
968
+ onKeyDown: (event_0) => event_0.stopPropagation(),
969
+ children: actions.row.map((action) => /* @__PURE__ */ jsx(ActionButton, {
970
+ action,
971
+ collection,
972
+ item: row_0.original,
973
+ helpers: actionHelpers,
974
+ size: "icon-sm",
975
+ iconOnly: true,
976
+ onOpenDialog: (dialogAction_0) => openDialog(dialogAction_0, row_0.original)
977
+ }, action.id))
978
+ }),
979
+ size: 72,
980
+ enableSorting: false,
981
+ enableHiding: false
982
+ });
403
983
  return orderedColumns;
404
984
  }, [
405
985
  columns,
406
986
  viewState.config.visibleColumns,
407
987
  defaultColumns,
408
- collectionMeta
988
+ collectionMeta,
989
+ isReorderMode,
990
+ actions.row,
991
+ collection,
992
+ actionHelpers,
993
+ openDialog,
994
+ t
409
995
  ]);
410
996
  const [sorting, setSorting] = React.useState(() => {
411
- const sortSource = viewState.config.sortConfig ?? resolvedListConfig?.defaultSort;
997
+ const sortSource = effectiveSort;
412
998
  if (sortSource?.field) return [{
413
999
  id: sortSource.field,
414
1000
  desc: sortSource.direction === "desc"
@@ -439,7 +1025,78 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
439
1025
  resource: collection,
440
1026
  realtime: effectiveRealtime
441
1027
  });
442
- const filteredItems = items;
1028
+ React.useEffect(() => {
1029
+ if (!isReorderMode) {
1030
+ setOptimisticOrderIds(null);
1031
+ return;
1032
+ }
1033
+ setOptimisticOrderIds((current) => {
1034
+ const itemIds = items.map((item_0) => String(item_0.id));
1035
+ if (!current) return itemIds;
1036
+ const knownIds = new Set(itemIds);
1037
+ const next = current.filter((id) => knownIds.has(id));
1038
+ for (const id_0 of itemIds) if (!next.includes(id_0)) next.push(id_0);
1039
+ return next;
1040
+ });
1041
+ }, [isReorderMode, items]);
1042
+ const filteredItems = useMemo(() => {
1043
+ if (!isReorderMode || !optimisticOrderIds) return items;
1044
+ const itemsById = new Map(items.map((item_1) => [String(item_1.id), item_1]));
1045
+ const seen = /* @__PURE__ */ new Set();
1046
+ const ordered = optimisticOrderIds.map((id_1) => {
1047
+ const item_2 = itemsById.get(id_1);
1048
+ if (item_2) seen.add(id_1);
1049
+ return item_2;
1050
+ }).filter(Boolean);
1051
+ for (const item_3 of items) {
1052
+ const id_2 = String(item_3.id);
1053
+ if (!seen.has(id_2)) ordered.push(item_3);
1054
+ }
1055
+ return ordered;
1056
+ }, [
1057
+ isReorderMode,
1058
+ items,
1059
+ optimisticOrderIds
1060
+ ]);
1061
+ const hasActiveFilters = viewState.config.filters.length > 0;
1062
+ const isOrderSortActive = canUseOrderableSort && effectiveSort?.field === orderField && (effectiveSort.direction ?? "asc") === orderDirection;
1063
+ const hasMultiplePages = !isSearching && (listData?.totalPages ?? 1) > 1;
1064
+ const reorderHardBlocker = !isOrderableEnabled ? t("collection.reorderEnableOrderable") : !hasOrderField ? t("collection.reorderAddOrderField") : isSearching ? t("collection.reorderClearSearch") : viewState.config.groupBy ? t("collection.reorderRemoveGrouping") : hasActiveFilters ? t("collection.reorderClearFilters") : hasMultiplePages ? t("collection.reorderShowOnePage") : null;
1065
+ const reorderTooltip = reorderHardBlocker ?? (isOrderSortActive ? isReorderMode ? t("collection.reorderExitMode") : t("collection.reorderItems") : t("collection.reorderSwitchSort", { field: orderField }));
1066
+ const reorderAriaLabel = reorderHardBlocker ? t("collection.reorderUnavailable", { reason: reorderHardBlocker }) : isReorderMode ? t("collection.reorderExitMode") : t("collection.reorderEnterMode");
1067
+ const canReorder = isOrderableEnabled && !reorderHardBlocker;
1068
+ const handleReorderToggle = React.useCallback(() => {
1069
+ if (!canReorder) return;
1070
+ if (!isOrderSortActive) {
1071
+ const nextSort = {
1072
+ field: orderField,
1073
+ direction: orderDirection
1074
+ };
1075
+ setSorting([{
1076
+ id: nextSort.field,
1077
+ desc: nextSort.direction === "desc"
1078
+ }]);
1079
+ viewState.setSort(nextSort);
1080
+ setIsReorderMode(true);
1081
+ return;
1082
+ }
1083
+ setIsReorderMode((active) => !active);
1084
+ }, [
1085
+ canReorder,
1086
+ isOrderSortActive,
1087
+ orderDirection,
1088
+ viewState
1089
+ ]);
1090
+ const hasViewOptionsState = hasActiveFilters || !!viewState.config.groupBy || viewState.config.visibleColumns.length !== defaultColumns.length || !!viewState.config.includeDeleted;
1091
+ const clearFilters = () => {
1092
+ viewState.setConfig({
1093
+ ...viewState.config,
1094
+ filters: []
1095
+ });
1096
+ };
1097
+ React.useEffect(() => {
1098
+ if (isReorderMode && !canReorder) setIsReorderMode(false);
1099
+ }, [isReorderMode, canReorder]);
443
1100
  const table = useReactTable({
444
1101
  data: filteredItems,
445
1102
  columns: visibleColumnDefs,
@@ -448,12 +1105,154 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
448
1105
  onSortingChange: handleSortingChange,
449
1106
  enableRowSelection: true,
450
1107
  onRowSelectionChange: setRowSelection,
451
- getRowId: (row_0) => row_0.id,
1108
+ getRowId: (row_1) => row_1.id,
452
1109
  state: {
453
1110
  sorting,
454
1111
  rowSelection
455
1112
  }
456
1113
  });
1114
+ const tableRows = table.getRowModel().rows;
1115
+ const visibleLeafColumns = table.getVisibleLeafColumns();
1116
+ const selectColumnWidth = getColumnSize(visibleLeafColumns[0], 40);
1117
+ const titleColumnWidth = getColumnSize(visibleLeafColumns[1], 360);
1118
+ const sortableRowIds = useMemo(() => tableRows.map((row_2) => String(row_2.id)), [tableRows]);
1119
+ const activeReorderRow = useMemo(() => activeReorderId ? tableRows.find((row_3) => String(row_3.id) === activeReorderId) : null, [activeReorderId, tableRows]);
1120
+ const reorderSensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 4 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
1121
+ const handleReorderDragStart = React.useCallback((event_1) => {
1122
+ const initialRect = event_1.active.rect.current.initial;
1123
+ clearReorderOverlay();
1124
+ reorderStartOrderIdsRef.current = sortableRowIds;
1125
+ setActiveReorderId(String(event_1.active.id));
1126
+ setActiveReorderRect(initialRect ? {
1127
+ width: initialRect.width,
1128
+ height: initialRect.height
1129
+ } : null);
1130
+ setOptimisticOrderIds((current_0) => current_0 ?? sortableRowIds);
1131
+ }, [clearReorderOverlay, sortableRowIds]);
1132
+ const handleReorderDragCancel = React.useCallback(() => {
1133
+ setOptimisticOrderIds(reorderStartOrderIdsRef.current);
1134
+ clearReorderOverlay();
1135
+ reorderStartOrderIdsRef.current = null;
1136
+ }, [clearReorderOverlay]);
1137
+ const handleReorderDragEnd = React.useCallback(async (event_2) => {
1138
+ if (updateBatchMutation.isPending) {
1139
+ clearReorderOverlay();
1140
+ return;
1141
+ }
1142
+ const { active: active_0, over } = event_2;
1143
+ const previousOrderIds = reorderStartOrderIdsRef.current ?? sortableRowIds;
1144
+ reorderStartOrderIdsRef.current = null;
1145
+ if (!over) {
1146
+ setOptimisticOrderIds(previousOrderIds);
1147
+ clearReorderOverlay();
1148
+ return;
1149
+ }
1150
+ let nextOrderIds = previousOrderIds;
1151
+ if (active_0.id !== over.id) {
1152
+ const oldIndex = previousOrderIds.indexOf(String(active_0.id));
1153
+ const newIndex = previousOrderIds.indexOf(String(over.id));
1154
+ if (oldIndex === -1 || newIndex === -1 || oldIndex === newIndex) {
1155
+ clearReorderOverlay(REORDER_DROP_DURATION);
1156
+ return;
1157
+ }
1158
+ nextOrderIds = arrayMove(previousOrderIds, oldIndex, newIndex);
1159
+ }
1160
+ if (nextOrderIds.join("\0") === previousOrderIds.join("\0")) {
1161
+ clearReorderOverlay(REORDER_DROP_DURATION);
1162
+ return;
1163
+ }
1164
+ setOptimisticOrderIds(nextOrderIds);
1165
+ clearReorderOverlay(REORDER_DROP_DURATION);
1166
+ const rowsById = new Map(tableRows.map((row_4) => [String(row_4.id), row_4]));
1167
+ const reorderedRows = nextOrderIds.map((id_3) => rowsById.get(id_3)).filter((row_5) => !!row_5);
1168
+ try {
1169
+ await updateBatchMutation.mutateAsync({ updates: reorderedRows.map((row_6, index) => ({
1170
+ id: String(row_6.id),
1171
+ data: { [orderField]: (index + 1) * orderStep }
1172
+ })) });
1173
+ actionHelpers.toast.success(t("collection.orderSaved"));
1174
+ } catch (error) {
1175
+ clearReorderOverlay();
1176
+ setOptimisticOrderIds(previousOrderIds);
1177
+ actionHelpers.toast.error(error instanceof Error ? error.message : t("collection.orderSaveFailed"));
1178
+ }
1179
+ }, [
1180
+ sortableRowIds,
1181
+ tableRows,
1182
+ updateBatchMutation,
1183
+ orderField,
1184
+ orderStep,
1185
+ t,
1186
+ actionHelpers.toast,
1187
+ clearReorderOverlay
1188
+ ]);
1189
+ const groupedRowModel = useMemo(() => {
1190
+ const rows = tableRows;
1191
+ const groupBy_0 = viewState.config.groupBy;
1192
+ if (!groupBy_0) return rows.map((row_7) => ({
1193
+ type: "row",
1194
+ row: row_7
1195
+ }));
1196
+ const groupField = groupableFields.find((field_2) => field_2.name === groupBy_0);
1197
+ const collapsedGroups = new Set(viewState.config.collapsedGroups ?? []);
1198
+ const serverGroups = !isSearching ? listData?.groups : void 0;
1199
+ if (serverGroups?.length) {
1200
+ const rowsById_0 = new Map(rows.map((row_8) => [row_8.id, row_8]));
1201
+ return serverGroups.flatMap((group) => {
1202
+ const label = stringifyGroupValue(group.value, groupField, resolveText, t("common.noValue"));
1203
+ const groupKey = `${groupBy_0}:${label}`;
1204
+ const collapsed = collapsedGroups.has(groupKey);
1205
+ const groupRows = (group.docs ?? []).map((doc) => rowsById_0.get(String(doc.id))).filter(Boolean);
1206
+ return [{
1207
+ type: "group",
1208
+ key: groupKey,
1209
+ label,
1210
+ count: group.count,
1211
+ collapsed
1212
+ }, ...collapsed ? [] : groupRows.map((row_9) => ({
1213
+ type: "row",
1214
+ row: row_9
1215
+ }))];
1216
+ });
1217
+ }
1218
+ const groups = /* @__PURE__ */ new Map();
1219
+ for (const row_10 of rows) {
1220
+ const valueLabel = stringifyGroupValue(row_10.original?.[groupBy_0], groupField, resolveText, t("common.noValue"));
1221
+ const groupKey_0 = `${groupBy_0}:${valueLabel}`;
1222
+ const group_0 = groups.get(groupKey_0);
1223
+ if (group_0) {
1224
+ group_0.rows.push(row_10);
1225
+ continue;
1226
+ }
1227
+ groups.set(groupKey_0, {
1228
+ label: valueLabel,
1229
+ rows: [row_10],
1230
+ sortIndex: getGroupSortIndex(row_10.original?.[groupBy_0], groupField)
1231
+ });
1232
+ }
1233
+ return Array.from(groups.entries()).sort(([, a], [, b]) => a.sortIndex - b.sortIndex).flatMap(([key_0, group_1]) => {
1234
+ const collapsed_0 = collapsedGroups.has(key_0);
1235
+ return [{
1236
+ type: "group",
1237
+ key: key_0,
1238
+ label: group_1.label,
1239
+ count: group_1.rows.length,
1240
+ collapsed: collapsed_0
1241
+ }, ...collapsed_0 ? [] : group_1.rows.map((row_11) => ({
1242
+ type: "row",
1243
+ row: row_11
1244
+ }))];
1245
+ });
1246
+ }, [
1247
+ tableRows,
1248
+ viewState.config.groupBy,
1249
+ viewState.config.collapsedGroups,
1250
+ groupableFields,
1251
+ isSearching,
1252
+ listData?.groups,
1253
+ resolveText,
1254
+ t
1255
+ ]);
457
1256
  const handleSaveView = (name, config_0) => {
458
1257
  saveViewMutation.mutate({
459
1258
  name,
@@ -463,11 +1262,11 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
463
1262
  const handleDeleteView = (viewId) => {
464
1263
  deleteViewMutation.mutate(viewId);
465
1264
  };
466
- const handleRowClick = (item_0) => {
467
- navigate(`${basePath}/collections/${collection}/${item_0.id}`);
1265
+ const handleRowClick = (item_4) => {
1266
+ navigate(`${basePath}/collections/${collection}/${item_4.id}`);
468
1267
  };
469
1268
  const handleBulkDelete = React.useCallback(async (ids_0) => {
470
- const results = await Promise.allSettled(ids_0.map((id) => deleteMutation.mutateAsync({ id })));
1269
+ const results = await Promise.allSettled(ids_0.map((id_4) => deleteMutation.mutateAsync({ id: id_4 })));
471
1270
  const successCount = results.filter((r) => r.status === "fulfilled").length;
472
1271
  const failCount = results.filter((r_0) => r_0.status === "rejected").length;
473
1272
  if (failCount === 0) actionHelpers.toast.success(t("collection.bulkDeleteSuccess", { count: successCount }));
@@ -482,7 +1281,7 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
482
1281
  t
483
1282
  ]);
484
1283
  const handleBulkRestore = React.useCallback(async (ids_1) => {
485
- const results_0 = await Promise.allSettled(ids_1.map((id_0) => restoreMutation.mutateAsync({ id: id_0 })));
1284
+ const results_0 = await Promise.allSettled(ids_1.map((id_5) => restoreMutation.mutateAsync({ id: id_5 })));
486
1285
  const successCount_0 = results_0.filter((r_1) => r_1.status === "fulfilled").length;
487
1286
  const failCount_0 = results_0.filter((r_2) => r_2.status === "rejected").length;
488
1287
  if (failCount_0 === 0) actionHelpers.toast.success(t("collection.bulkRestoreSuccess", { count: successCount_0 }));
@@ -496,100 +1295,157 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
496
1295
  actionHelpers,
497
1296
  t
498
1297
  ]);
499
- if (listError && !isSearching) return /* @__PURE__ */ jsx("div", {
500
- className: "container",
501
- children: /* @__PURE__ */ jsxs("div", {
502
- className: "text-muted-foreground flex h-64 flex-col items-center justify-center gap-3",
503
- children: [
504
- /* @__PURE__ */ jsx(Icon, {
505
- icon: "ph:warning-circle",
506
- className: "text-destructive size-8"
507
- }),
508
- /* @__PURE__ */ jsx("p", {
509
- className: "text-sm",
510
- children: listError instanceof Error ? listError.message : t("errors.failedToLoad")
511
- }),
512
- /* @__PURE__ */ jsx(Button, {
1298
+ if (listError && !isSearching) {
1299
+ const errorMessage = listError instanceof Error ? listError.message : void 0;
1300
+ return /* @__PURE__ */ jsx("div", {
1301
+ className: "container",
1302
+ children: /* @__PURE__ */ jsx(EmptyState, {
1303
+ variant: "error",
1304
+ iconName: "ph:warning-circle",
1305
+ title: t("error.failedToLoad"),
1306
+ description: errorMessage,
1307
+ height: "h-64",
1308
+ action: /* @__PURE__ */ jsxs(Button, {
513
1309
  variant: "outline",
514
1310
  size: "sm",
1311
+ className: "gap-2",
515
1312
  onClick: () => window.location.reload(),
516
- children: t("common.retry")
1313
+ children: [/* @__PURE__ */ jsx(Icon, {
1314
+ icon: "ph:arrow-clockwise",
1315
+ className: "size-3.5"
1316
+ }), t("common.retry")]
517
1317
  })
518
- ]
519
- })
520
- });
521
- if (isLoading) return /* @__PURE__ */ jsx("div", {
522
- className: "container",
523
- "aria-busy": "true",
524
- children: /* @__PURE__ */ jsxs("div", {
525
- className: "text-muted-foreground flex h-64 items-center justify-center",
526
- role: "status",
527
- children: [/* @__PURE__ */ jsx(Icon, {
528
- icon: "ph:spinner-gap",
529
- className: "size-6 animate-spin",
530
- "aria-hidden": "true"
531
- }), /* @__PURE__ */ jsx("span", {
532
- className: "sr-only",
533
- children: "Loading collection data..."
534
- })]
535
- })
536
- });
537
- return /* @__PURE__ */ jsx("div", {
538
- className: "qa-table-view min-w-0",
1318
+ })
1319
+ });
1320
+ }
1321
+ if (isLoading) return /* @__PURE__ */ jsx(TableViewSkeleton, {});
1322
+ const emptyStateTitle = isSearching || hasActiveFilters ? t("collectionSearch.noResults") : t("table.noItemsInCollection");
1323
+ const emptyStateDescription = isSearching ? t("collectionSearch.noResultsDescription") : hasActiveFilters ? t("viewOptions.noResultsDescription") : t("table.emptyDescription");
1324
+ const emptyStateAction = isSearching || hasActiveFilters ? /* @__PURE__ */ jsxs(Fragment, { children: [isSearching && /* @__PURE__ */ jsxs(Button, {
1325
+ variant: "outline",
1326
+ size: "sm",
1327
+ className: "gap-2",
1328
+ onClick: () => setSearchTerm(""),
1329
+ children: [/* @__PURE__ */ jsx(Icon, {
1330
+ icon: "ph:x",
1331
+ className: "size-3.5"
1332
+ }), t("common.clear")]
1333
+ }), hasActiveFilters && /* @__PURE__ */ jsxs(Button, {
1334
+ variant: "outline",
1335
+ size: "sm",
1336
+ className: "gap-2",
1337
+ onClick: clearFilters,
1338
+ children: [/* @__PURE__ */ jsx(Icon, {
1339
+ icon: "ph:funnel-x",
1340
+ className: "size-3.5"
1341
+ }), t("viewOptions.clearFilters")]
1342
+ })] }) : void 0;
1343
+ return /* @__PURE__ */ jsx(AdminViewLayout, {
1344
+ header: /* @__PURE__ */ jsx(AdminViewHeader, {
1345
+ title: resolveText(config?.label ?? schema?.admin?.config?.label, collection),
1346
+ titleAccessory: localeOptions.length > 0 ? /* @__PURE__ */ jsx(LocaleSwitcher, {
1347
+ locales: localeOptions,
1348
+ value: contentLocale,
1349
+ onChange: setContentLocale
1350
+ }) : void 0,
1351
+ description: resolveText(config?.description ?? schema?.admin?.config?.description),
1352
+ actions: /* @__PURE__ */ jsxs(Fragment, { children: [
1353
+ isOrderableEnabled && /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, { render: /* @__PURE__ */ jsx(Button, {
1354
+ variant: "outline",
1355
+ size: "icon-sm",
1356
+ className: cn("relative aria-disabled:opacity-50", isReorderMode && "border-foreground bg-foreground text-background hover:bg-foreground/90 hover:text-background"),
1357
+ onClick: handleReorderToggle,
1358
+ "aria-label": reorderAriaLabel,
1359
+ "aria-disabled": !canReorder || void 0,
1360
+ "aria-pressed": isReorderMode,
1361
+ children: /* @__PURE__ */ jsx(Icon, { icon: "ph:arrows-down-up" })
1362
+ }) }), /* @__PURE__ */ jsx(TooltipContent, {
1363
+ side: "bottom",
1364
+ align: "end",
1365
+ children: reorderTooltip
1366
+ })] }),
1367
+ showSearch && /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, { render: /* @__PURE__ */ jsxs(Button, {
1368
+ variant: "outline",
1369
+ size: "icon-sm",
1370
+ className: "relative",
1371
+ onClick: () => setIsSearchPanelOpen((open) => !open),
1372
+ "aria-label": t("common.search"),
1373
+ children: [/* @__PURE__ */ jsx(Icon, { icon: "ph:magnifying-glass" }), searchTerm && /* @__PURE__ */ jsx("span", { className: "bg-foreground absolute top-1 right-1 size-1.5 rounded-full" })]
1374
+ }) }), /* @__PURE__ */ jsx(TooltipContent, {
1375
+ side: "bottom",
1376
+ align: "end",
1377
+ children: t("common.search")
1378
+ })] }),
1379
+ showFilters && /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, { render: /* @__PURE__ */ jsxs(Button, {
1380
+ variant: "outline",
1381
+ size: "icon-sm",
1382
+ className: "relative",
1383
+ onClick: () => setIsSheetOpen(true),
1384
+ "aria-label": t("viewOptions.title"),
1385
+ children: [/* @__PURE__ */ jsx(Icon, { icon: "ph:sliders-horizontal" }), hasViewOptionsState && /* @__PURE__ */ jsx("span", { className: "bg-foreground absolute top-1 right-1 size-1.5 rounded-full" })]
1386
+ }) }), /* @__PURE__ */ jsx(TooltipContent, {
1387
+ side: "bottom",
1388
+ align: "end",
1389
+ children: t("viewOptions.title")
1390
+ })] }),
1391
+ canUploadToCollection && /* @__PURE__ */ jsx(UploadCollectionButton, {
1392
+ collection,
1393
+ onUploaded: () => actionHelpers.invalidateCollection(collection)
1394
+ }),
1395
+ headerActions,
1396
+ ((actions.header.primary?.length ?? 0) > 0 || (actions.header.secondary?.length ?? 0) > 0) && /* @__PURE__ */ jsx(HeaderActions, {
1397
+ actions: actions.header,
1398
+ collection,
1399
+ helpers: actionHelpers,
1400
+ onOpenDialog: (action_0) => openDialog(action_0)
1401
+ })
1402
+ ] })
1403
+ }),
1404
+ contentClassName: "overflow-y-auto pb-3",
539
1405
  children: /* @__PURE__ */ jsxs("div", {
540
- className: "qa-table-view__inner min-w-0 space-y-4",
1406
+ className: "qa-table-view min-w-0 space-y-4",
541
1407
  children: [
542
- /* @__PURE__ */ jsxs("div", {
543
- className: "qa-table-view__header flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between",
1408
+ showToolbar && showSearch && (isSearchPanelOpen || searchTerm) && /* @__PURE__ */ jsx("div", {
1409
+ className: "max-w-xl",
1410
+ children: /* @__PURE__ */ jsx(SearchInput, {
1411
+ value: searchTerm,
1412
+ onChange: (e_3) => setSearchTerm(e_3.target.value),
1413
+ onClear: () => setSearchTerm(""),
1414
+ placeholder: t("common.search"),
1415
+ containerClassName: "h-10"
1416
+ })
1417
+ }),
1418
+ isReorderMode && canUseOrderableSort && /* @__PURE__ */ jsxs("div", {
1419
+ className: "border-border/70 bg-muted/30 text-muted-foreground flex min-h-10 items-center justify-between gap-3 border-y px-3 py-2 font-mono text-xs",
544
1420
  children: [/* @__PURE__ */ jsxs("div", {
545
- className: "min-w-0 flex-1",
546
- children: [/* @__PURE__ */ jsxs("div", {
547
- className: "flex items-center gap-3",
548
- children: [/* @__PURE__ */ jsx("h1", {
549
- className: "qa-table-view__title truncate text-2xl font-extrabold tracking-tight md:text-3xl",
550
- children: resolveText(config?.label ?? schema?.admin?.config?.label, collection)
551
- }), localeOptions.length > 0 && /* @__PURE__ */ jsx(LocaleSwitcher, {
552
- locales: localeOptions,
553
- value: contentLocale,
554
- onChange: setContentLocale
555
- })]
556
- }), config?.description ?? schema?.admin?.config?.description ? /* @__PURE__ */ jsx("p", {
557
- className: "qa-table-view__description text-muted-foreground mt-1 line-clamp-2 text-sm",
558
- children: resolveText(config?.description ?? schema?.admin?.config?.description)
559
- }) : null]
560
- }), /* @__PURE__ */ jsxs("div", {
561
- className: "flex shrink-0 items-center gap-2",
562
- children: [headerActions, ((actions.header.primary?.length ?? 0) > 0 || (actions.header.secondary?.length ?? 0) > 0) && /* @__PURE__ */ jsx(HeaderActions, {
563
- actions: actions.header,
564
- collection,
565
- helpers: actionHelpers,
566
- onOpenDialog: (action) => openDialog(action)
567
- })]
1421
+ className: "flex min-w-0 items-center gap-2",
1422
+ children: [
1423
+ /* @__PURE__ */ jsx("span", {
1424
+ className: "bg-foreground text-background inline-flex size-5 items-center justify-center rounded-full",
1425
+ children: /* @__PURE__ */ jsx(Icon, {
1426
+ icon: "ph:arrows-down-up",
1427
+ className: "size-3"
1428
+ })
1429
+ }),
1430
+ /* @__PURE__ */ jsx("span", {
1431
+ className: "text-foreground font-medium",
1432
+ children: t("collection.reorderMode")
1433
+ }),
1434
+ /* @__PURE__ */ jsx("span", {
1435
+ className: "hidden sm:inline",
1436
+ children: t("collection.sortedByField", {
1437
+ field: orderField,
1438
+ direction: orderDirection
1439
+ })
1440
+ })
1441
+ ]
1442
+ }), /* @__PURE__ */ jsx(Button, {
1443
+ variant: "ghost",
1444
+ size: "xs",
1445
+ onClick: () => setIsReorderMode(false),
1446
+ children: t("common.done")
568
1447
  })]
569
1448
  }),
570
- showToolbar && /* @__PURE__ */ jsx("div", {
571
- className: "space-y-2",
572
- children: /* @__PURE__ */ jsxs(Toolbar, { children: [showSearch && /* @__PURE__ */ jsx(ToolbarSection, {
573
- className: "flex-1",
574
- children: /* @__PURE__ */ jsx(SearchInput, {
575
- value: searchTerm,
576
- onChange: (e_3) => setSearchTerm(e_3.target.value),
577
- onClear: () => setSearchTerm(""),
578
- placeholder: t("common.search"),
579
- containerClassName: "border-none bg-transparent dark:bg-transparent"
580
- })
581
- }), showFilters && /* @__PURE__ */ jsxs(Fragment, { children: [showSearch && /* @__PURE__ */ jsx(ToolbarSeparator, {}), /* @__PURE__ */ jsx(ToolbarSection, { children: /* @__PURE__ */ jsxs(Button, {
582
- variant: "outline",
583
- size: "sm",
584
- onClick: () => setIsSheetOpen(true),
585
- className: "gap-2",
586
- children: [/* @__PURE__ */ jsx(Icon, {
587
- icon: "ph:sliders-horizontal",
588
- width: 16,
589
- height: 16
590
- }), t("viewOptions.title")]
591
- }) })] })] })
592
- }),
593
1449
  /* @__PURE__ */ jsx(BulkActionToolbar, {
594
1450
  table,
595
1451
  actions: actions.bulk,
@@ -597,104 +1453,178 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
597
1453
  helpers: actionHelpers,
598
1454
  totalCount: isSearching ? searchData?.total : listData?.totalDocs,
599
1455
  pageCount: filteredItems.length,
600
- onOpenDialog: (action_0, items_0) => openDialog(action_0, items_0),
1456
+ onOpenDialog: (action_1, items_0) => openDialog(action_1, items_0),
601
1457
  onBulkDelete: handleBulkDelete,
602
1458
  onBulkRestore: handleBulkRestore,
603
1459
  filterCount: viewState.config.filters.length,
604
1460
  onOpenFilters: () => setIsSheetOpen(true),
605
- onClearFilters: () => viewState.setConfig({
606
- ...viewState.config,
607
- filters: []
608
- })
1461
+ onClearFilters: clearFilters
609
1462
  }),
610
1463
  /* @__PURE__ */ jsxs("div", {
611
- className: "qa-table-view__table-wrapper bg-card border-border min-w-0 overflow-x-auto border",
612
- children: [/* @__PURE__ */ jsxs(Table, {
613
- "aria-label": resolveText(config?.label ?? schema?.admin?.config?.label, collection),
614
- children: [/* @__PURE__ */ jsx(TableHeader, { children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx(TableRow, {
615
- className: "hover:bg-transparent",
616
- children: headerGroup.headers.map((header, headerIndex) => {
617
- const stickyLeft = headerIndex === 0 ? 0 : headerIndex === 1 ? 36 : void 0;
618
- const showStickyBorder = headerIndex === 1;
619
- const isCheckboxCol = headerIndex === 0;
620
- const sortDirection = header.column.getIsSorted();
621
- const ariaSort = header.column.getCanSort() ? sortDirection === "asc" ? "ascending" : sortDirection === "desc" ? "descending" : "none" : void 0;
622
- return /* @__PURE__ */ jsx(TableHead, {
623
- stickyLeft,
624
- showStickyBorder,
625
- className: isCheckboxCol ? "w-9 min-w-9 px-1.5" : void 0,
626
- "aria-sort": ariaSort,
627
- children: header.isPlaceholder ? null : /* @__PURE__ */ jsxs("button", {
628
- type: "button",
629
- className: header.column.getCanSort() ? "hover:text-foreground flex cursor-pointer items-center gap-2 transition-colors select-none" : "",
630
- onClick: header.column.getToggleSortingHandler(),
631
- "aria-label": header.column.getCanSort() ? `Sort by ${typeof header.column.columnDef.header === "string" ? header.column.columnDef.header : header.column.id}` : void 0,
632
- children: [flexRender(header.column.columnDef.header, header.getContext()), header.column.getIsSorted() && /* @__PURE__ */ jsx("span", {
633
- "aria-hidden": "true",
634
- children: header.column.getIsSorted() === "asc" ? "↑" : "↓"
635
- })]
636
- })
637
- }, header.id);
638
- })
639
- }, headerGroup.id)) }), /* @__PURE__ */ jsx(TableBody, { children: table.getRowModel().rows.map((row_1) => {
640
- const isRowDeleted = !!row_1.original?.deletedAt;
641
- return /* @__PURE__ */ jsx(TableRow, {
642
- "data-state": row_1.getIsSelected() && "selected",
643
- className: cn("group", isHighlighted(row_1.id) && "animate-realtime-pulse", isRowDeleted && "opacity-50"),
644
- children: row_1.getVisibleCells().map((cell, cellIndex) => {
645
- return /* @__PURE__ */ jsx(TableCell, {
646
- stickyLeft: cellIndex === 0 ? 0 : cellIndex === 1 ? 36 : void 0,
647
- showStickyBorder: cellIndex === 1,
648
- className: cellIndex === 0 ? "w-9 min-w-9 px-1.5" : void 0,
649
- children: cellIndex === 1 ? /* @__PURE__ */ jsxs("div", {
650
- className: "flex items-center gap-2",
651
- children: [
652
- /* @__PURE__ */ jsx("button", {
1464
+ className: "qa-table-view__table-wrapper min-w-0",
1465
+ children: [/* @__PURE__ */ jsx(ScrollFade, {
1466
+ leftInset: selectColumnWidth + titleColumnWidth,
1467
+ children: /* @__PURE__ */ jsxs(DndContext, {
1468
+ sensors: reorderSensors,
1469
+ collisionDetection: closestCenter,
1470
+ onDragStart: handleReorderDragStart,
1471
+ onDragCancel: handleReorderDragCancel,
1472
+ onDragEnd: handleReorderDragEnd,
1473
+ children: [/* @__PURE__ */ jsxs(Table, {
1474
+ className: "table-fixed",
1475
+ style: { width: table.getTotalSize() },
1476
+ "aria-label": resolveText(config?.label ?? schema?.admin?.config?.label, collection),
1477
+ children: [
1478
+ /* @__PURE__ */ jsx("colgroup", { children: visibleLeafColumns.map((column) => /* @__PURE__ */ jsx("col", { style: { width: column.getSize() } }, column.id)) }),
1479
+ /* @__PURE__ */ jsx(TableHeader, { children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx(TableRow, {
1480
+ className: "hover:bg-transparent",
1481
+ children: headerGroup.headers.map((header, headerIndex) => {
1482
+ const isCheckboxCol = headerIndex === 0;
1483
+ const columnWidth = getColumnSize(header.column, isCheckboxCol ? 40 : 120);
1484
+ const stickyLeft = headerIndex < STICKY_TABLE_COLUMN_COUNT ? getStickyLeftOffset(visibleLeafColumns, headerIndex) : void 0;
1485
+ const sortDirection = header.column.getIsSorted();
1486
+ const ariaSort = header.column.getCanSort() ? sortDirection === "asc" ? "ascending" : sortDirection === "desc" ? "descending" : "none" : void 0;
1487
+ return /* @__PURE__ */ jsx(TableHead, {
1488
+ stickyLeft,
1489
+ showStickyBorder: headerIndex === STICKY_TABLE_COLUMN_COUNT - 1,
1490
+ className: isCheckboxCol ? "w-9 min-w-9 px-1.5" : void 0,
1491
+ style: getColumnSizeStyle(columnWidth),
1492
+ "aria-sort": ariaSort,
1493
+ children: header.isPlaceholder ? null : /* @__PURE__ */ jsxs("button", {
653
1494
  type: "button",
654
- onClick: () => handleRowClick(row_1.original),
655
- className: "decoration-muted-foreground/50 hover:decoration-foreground cursor-pointer text-left underline underline-offset-2 transition-colors",
656
- children: flexRender(cell.column.columnDef.cell, cell.getContext())
657
- }),
658
- isRowDeleted && /* @__PURE__ */ jsxs("span", {
659
- className: "text-destructive bg-destructive/10 inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-xs",
660
- children: [/* @__PURE__ */ jsx(Icon, {
661
- icon: "ph:trash",
662
- className: "size-3"
663
- }), t("common.deleted")]
664
- }),
665
- isDocLocked(row_1.id) && (() => {
666
- const lock = getLock(row_1.id);
667
- const user_0 = lock ? getLockUser(lock) : null;
668
- return /* @__PURE__ */ jsxs("span", {
669
- className: "text-muted-foreground bg-muted inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-xs",
670
- title: user_0?.name ?? user_0?.email ?? "Someone is editing",
671
- children: [user_0?.image ? /* @__PURE__ */ jsx("img", {
672
- src: user_0.image,
673
- alt: "",
674
- className: "size-4 rounded-full"
675
- }) : /* @__PURE__ */ jsx(Icon, {
676
- icon: "ph:pencil-simple",
677
- className: "size-3"
678
- }), /* @__PURE__ */ jsx("span", {
679
- className: "max-w-20 truncate",
680
- children: user_0?.name?.split(" ")[0] ?? t("table.editing")
681
- })]
682
- });
683
- })()
684
- ]
685
- }) : flexRender(cell.column.columnDef.cell, cell.getContext())
686
- }, cell.id);
1495
+ className: header.column.getCanSort() ? "hover:text-foreground focus-visible:ring-ring/40 -mx-1.5 flex min-h-7 cursor-pointer items-center gap-2 rounded-md px-1.5 transition-colors select-none focus-visible:ring-2 focus-visible:outline-none" : "",
1496
+ onClick: header.column.getToggleSortingHandler(),
1497
+ "aria-label": header.column.getCanSort() ? `Sort by ${typeof header.column.columnDef.header === "string" ? header.column.columnDef.header : header.column.id}` : void 0,
1498
+ children: [flexRender(header.column.columnDef.header, header.getContext()), header.column.getIsSorted() && /* @__PURE__ */ jsx("span", {
1499
+ "aria-hidden": "true",
1500
+ children: header.column.getIsSorted() === "asc" ? "↑" : "↓"
1501
+ })]
1502
+ })
1503
+ }, header.id);
1504
+ })
1505
+ }, headerGroup.id)) }),
1506
+ /* @__PURE__ */ jsx(SortableContext, {
1507
+ items: sortableRowIds,
1508
+ strategy: verticalListSortingStrategy,
1509
+ children: /* @__PURE__ */ jsx(TableBody, { children: groupedRowModel.map((entry) => {
1510
+ if (entry.type === "group") return /* @__PURE__ */ jsxs(TableRow, {
1511
+ className: "hover:bg-transparent",
1512
+ children: [
1513
+ /* @__PURE__ */ jsx(TableCell, {
1514
+ stickyLeft: 0,
1515
+ className: "w-9 min-w-9 border-b-0 px-1.5 group-hover/row:bg-transparent",
1516
+ style: getColumnSizeStyle(selectColumnWidth)
1517
+ }),
1518
+ /* @__PURE__ */ jsx(TableCell, {
1519
+ stickyLeft: selectColumnWidth,
1520
+ showStickyBorder: true,
1521
+ className: "bg-background top-8 z-20 border-b-0 group-hover/row:bg-transparent",
1522
+ style: getColumnSizeStyle(titleColumnWidth),
1523
+ children: /* @__PURE__ */ jsxs("button", {
1524
+ type: "button",
1525
+ "aria-expanded": !entry.collapsed,
1526
+ className: "text-muted-foreground hover:text-foreground focus-visible:ring-ring/40 -ml-1 inline-flex min-h-8 items-center gap-2 rounded-md px-1 font-mono text-[11px] font-semibold tracking-[0.12em] uppercase transition-colors focus-visible:ring-2 focus-visible:outline-none",
1527
+ onClick: () => viewState.toggleCollapsedGroup(entry.key),
1528
+ children: [
1529
+ /* @__PURE__ */ jsx(Icon, {
1530
+ icon: entry.collapsed ? "ph:caret-right" : "ph:caret-down",
1531
+ className: "size-3.5 shrink-0"
1532
+ }),
1533
+ /* @__PURE__ */ jsx("span", { children: entry.label }),
1534
+ groupingConfig?.showCounts !== false && /* @__PURE__ */ jsx("span", {
1535
+ className: "bg-muted text-muted-foreground inline-flex h-5 min-w-5 items-center justify-center rounded-full px-1.5 text-[10px] tracking-normal tabular-nums",
1536
+ children: entry.count
1537
+ })
1538
+ ]
1539
+ })
1540
+ }),
1541
+ visibleLeafColumns.length > STICKY_TABLE_COLUMN_COUNT && /* @__PURE__ */ jsx(TableCell, {
1542
+ colSpan: visibleLeafColumns.length - STICKY_TABLE_COLUMN_COUNT,
1543
+ className: "border-b-0"
1544
+ })
1545
+ ]
1546
+ }, entry.key);
1547
+ const row_12 = entry.row;
1548
+ const isRowDeleted = !!row_12.original?.deletedAt;
1549
+ return /* @__PURE__ */ jsx(isReorderMode ? SortableTableRow : TableRow, {
1550
+ id: String(row_12.id),
1551
+ "data-state": row_12.getIsSelected() && "selected",
1552
+ className: cn("group", isReorderMode && "bg-muted/[0.18]", isHighlighted(row_12.id) && "animate-realtime-pulse", isRowDeleted && "opacity-50"),
1553
+ children: row_12.getVisibleCells().map((cell, cellIndex) => {
1554
+ const isCheckboxCol_0 = cellIndex === 0;
1555
+ const columnWidth_0 = getColumnSize(cell.column, isCheckboxCol_0 ? 40 : 120);
1556
+ const stickyLeft_0 = cellIndex < STICKY_TABLE_COLUMN_COUNT ? getStickyLeftOffset(visibleLeafColumns, cellIndex) : void 0;
1557
+ const isTitleCol = cellIndex === 1;
1558
+ return /* @__PURE__ */ jsx(TableCell, {
1559
+ stickyLeft: stickyLeft_0,
1560
+ showStickyBorder: cellIndex === STICKY_TABLE_COLUMN_COUNT - 1,
1561
+ className: isCheckboxCol_0 ? "w-9 min-w-9 px-1.5" : void 0,
1562
+ style: getColumnSizeStyle(columnWidth_0),
1563
+ children: isTitleCol ? /* @__PURE__ */ jsxs("div", {
1564
+ className: "flex min-w-0 items-center gap-2",
1565
+ children: [
1566
+ /* @__PURE__ */ jsx("button", {
1567
+ type: "button",
1568
+ onClick: () => handleRowClick(row_12.original),
1569
+ disabled: isReorderMode,
1570
+ className: cn("decoration-muted-foreground/50 hover:decoration-foreground max-w-full min-w-0 text-left underline underline-offset-2 transition-colors disabled:cursor-default disabled:no-underline", !isReorderMode && "cursor-pointer"),
1571
+ children: flexRender(cell.column.columnDef.cell, cell.getContext())
1572
+ }),
1573
+ isRowDeleted && /* @__PURE__ */ jsxs("span", {
1574
+ className: "text-destructive bg-destructive/10 inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-xs",
1575
+ children: [/* @__PURE__ */ jsx(Icon, {
1576
+ icon: "ph:trash",
1577
+ className: "size-3"
1578
+ }), t("common.deleted")]
1579
+ }),
1580
+ isDocLocked(row_12.id) && (() => {
1581
+ const lock = getLock(row_12.id);
1582
+ const user_0 = lock ? getLockUser(lock) : null;
1583
+ return /* @__PURE__ */ jsxs("span", {
1584
+ className: "text-muted-foreground bg-muted inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-xs",
1585
+ title: user_0?.name ?? user_0?.email ?? "Someone is editing",
1586
+ children: [user_0?.image ? /* @__PURE__ */ jsx("img", {
1587
+ src: user_0.image,
1588
+ alt: "",
1589
+ className: "image-outline size-4 rounded-full"
1590
+ }) : /* @__PURE__ */ jsx(Icon, {
1591
+ icon: "ph:pencil-simple",
1592
+ className: "size-3"
1593
+ }), /* @__PURE__ */ jsx("span", {
1594
+ className: "max-w-20 truncate",
1595
+ children: user_0?.name?.split(" ")[0] ?? t("table.editing")
1596
+ })]
1597
+ });
1598
+ })()
1599
+ ]
1600
+ }) : flexRender(cell.column.columnDef.cell, cell.getContext())
1601
+ }, cell.id);
1602
+ })
1603
+ }, row_12.id);
1604
+ }) })
1605
+ })
1606
+ ]
1607
+ }), /* @__PURE__ */ jsx(DragOverlay, {
1608
+ adjustScale: false,
1609
+ dropAnimation: REORDER_DROP_ANIMATION,
1610
+ children: /* @__PURE__ */ jsx(ReorderDragOverlay, {
1611
+ row: activeReorderRow,
1612
+ columns: visibleLeafColumns,
1613
+ rect: activeReorderRect
687
1614
  })
688
- }, row_1.id);
689
- }) })]
1615
+ })]
1616
+ })
690
1617
  }), !table.getRowModel().rows.length && (emptyState || /* @__PURE__ */ jsx(EmptyState, {
691
- title: "NO_RESULTS",
692
- description: isSearching ? t("collectionSearch.noResults") : t("table.noItemsInCollection"),
1618
+ variant: isSearching || hasActiveFilters ? "search" : "empty",
1619
+ iconName: isSearching ? "ph:magnifying-glass" : hasActiveFilters ? "ph:funnel-x" : "ph:tray",
1620
+ title: emptyStateTitle,
1621
+ description: emptyStateDescription,
1622
+ action: emptyStateAction,
693
1623
  height: "h-48"
694
1624
  }))]
695
1625
  }),
696
1626
  !isSearching && /* @__PURE__ */ jsxs("div", {
697
- className: "qa-table-view__pagination flex items-center justify-between gap-4 py-2",
1627
+ className: "qa-table-view__pagination flex items-center justify-between gap-4 py-2 tabular-nums",
698
1628
  role: "navigation",
699
1629
  "aria-label": t("table.pagination"),
700
1630
  children: [/* @__PURE__ */ jsxs("div", {
@@ -704,13 +1634,14 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
704
1634
  children: [/* @__PURE__ */ jsxs("span", { children: [
705
1635
  filteredItems.length > 0 ? `${((viewState.config.pagination?.page ?? 1) - 1) * (viewState.config.pagination?.pageSize ?? 25) + 1}-${Math.min(((viewState.config.pagination?.page ?? 1) - 1) * (viewState.config.pagination?.pageSize ?? 25) + (viewState.config.pagination?.pageSize ?? 25), listData?.totalDocs ?? filteredItems.length)}` : "0",
706
1636
  " ",
707
- "of ",
1637
+ t("table.of"),
1638
+ " ",
708
1639
  listData?.totalDocs ?? 0
709
1640
  ] }), /* @__PURE__ */ jsxs("div", {
710
1641
  className: "flex items-center gap-2",
711
1642
  children: [/* @__PURE__ */ jsx("span", {
712
1643
  className: "text-muted-foreground",
713
- children: "Show"
1644
+ children: t("table.show")
714
1645
  }), /* @__PURE__ */ jsxs(Select, {
715
1646
  value: String(viewState.config.pagination?.pageSize ?? 25),
716
1647
  onValueChange: (value_0) => viewState.setPageSize(Number(value_0)),
@@ -740,7 +1671,7 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
740
1671
  className: "size-8 p-0",
741
1672
  disabled: (viewState.config.pagination?.page ?? 1) <= 1,
742
1673
  onClick: () => viewState.setPage((viewState.config.pagination?.page ?? 1) - 1),
743
- "aria-label": "Previous page",
1674
+ "aria-label": t("table.previousPage"),
744
1675
  children: /* @__PURE__ */ jsx(Icon, {
745
1676
  icon: "ph:caret-left",
746
1677
  className: "size-4"
@@ -757,9 +1688,9 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
757
1688
  return /* @__PURE__ */ jsx(Button, {
758
1689
  variant: currentPage === pageNum ? "secondary" : "ghost",
759
1690
  size: "sm",
760
- className: "size-8 min-w-[32px] p-0",
1691
+ className: "size-8 min-w-[32px] p-0 tabular-nums",
761
1692
  onClick: () => viewState.setPage(pageNum),
762
- "aria-label": `Page ${pageNum}`,
1693
+ "aria-label": t("table.page", { page: pageNum }),
763
1694
  "aria-current": currentPage === pageNum ? "page" : void 0,
764
1695
  children: pageNum
765
1696
  }, pageNum);
@@ -770,7 +1701,7 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
770
1701
  className: "size-8 p-0",
771
1702
  disabled: (viewState.config.pagination?.page ?? 1) >= (listData?.totalPages ?? 1),
772
1703
  onClick: () => viewState.setPage((viewState.config.pagination?.page ?? 1) + 1),
773
- "aria-label": "Next page",
1704
+ "aria-label": t("table.nextPage"),
774
1705
  children: /* @__PURE__ */ jsx(Icon, {
775
1706
  icon: "ph:caret-right",
776
1707
  className: "size-4"
@@ -780,7 +1711,7 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
780
1711
  })]
781
1712
  }),
782
1713
  isSearching && /* @__PURE__ */ jsxs("div", {
783
- className: "text-muted-foreground flex items-center gap-2 py-2 text-sm",
1714
+ className: "text-muted-foreground flex items-center gap-2 py-2 text-sm tabular-nums",
784
1715
  "aria-live": "polite",
785
1716
  "aria-atomic": "true",
786
1717
  children: [
@@ -809,6 +1740,8 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
809
1740
  onConfigChange: viewState.setConfig,
810
1741
  isOpen: isSheetOpen,
811
1742
  onOpenChange: setIsSheetOpen,
1743
+ groupableFields,
1744
+ defaultGroupBy,
812
1745
  savedViews: savedViewsData?.docs ?? [],
813
1746
  savedViewsLoading,
814
1747
  onSaveView: handleSaveView,
@@ -817,7 +1750,7 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
817
1750
  }),
818
1751
  dialogAction && /* @__PURE__ */ jsx(ActionDialog, {
819
1752
  open: !!dialogAction,
820
- onOpenChange: (open) => !open && closeDialog(),
1753
+ onOpenChange: (open_0) => !open_0 && closeDialog(),
821
1754
  action: dialogAction,
822
1755
  collection,
823
1756
  item: dialogItem,