@questpie/admin 3.0.2 → 3.0.4

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 (249) 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-fields-renderer.mjs +64 -28
  16. package/dist/client/components/blocks/block-insert-button.mjs +4 -4
  17. package/dist/client/components/blocks/block-item.mjs +2 -2
  18. package/dist/client/components/blocks/block-library-sidebar.mjs +2 -2
  19. package/dist/client/components/component-renderer.mjs +1 -1
  20. package/dist/client/components/fields/array-field.mjs +14 -14
  21. package/dist/client/components/fields/asset-preview-field.mjs +1 -1
  22. package/dist/client/components/fields/blocks-field/blocks-field.mjs +84 -104
  23. package/dist/client/components/fields/json-field.mjs +2 -2
  24. package/dist/client/components/fields/object-array-field.mjs +22 -22
  25. package/dist/client/components/fields/object-field.mjs +5 -5
  26. package/dist/client/components/fields/relation/displays/cards-display.mjs +16 -9
  27. package/dist/client/components/fields/relation/displays/chips-display.mjs +15 -12
  28. package/dist/client/components/fields/relation/displays/grid-display.mjs +15 -11
  29. package/dist/client/components/fields/relation/displays/list-display.mjs +33 -20
  30. package/dist/client/components/fields/relation/displays/table-display.mjs +62 -93
  31. package/dist/client/components/fields/relation/relation-items-display.mjs +1 -1
  32. package/dist/client/components/fields/relation-picker.mjs +7 -6
  33. package/dist/client/components/fields/relation-select.mjs +71 -47
  34. package/dist/client/components/fields/rich-text-editor/bubble-menu.mjs +392 -82
  35. package/dist/client/components/fields/rich-text-editor/extensions.mjs +54 -23
  36. package/dist/client/components/fields/rich-text-editor/image-popover.mjs +24 -50
  37. package/dist/client/components/fields/rich-text-editor/image-upload.mjs +66 -0
  38. package/dist/client/components/fields/rich-text-editor/index.d.mts +38 -0
  39. package/dist/client/components/fields/rich-text-editor/index.mjs +637 -376
  40. package/dist/client/components/fields/rich-text-editor/link-utils.mjs +26 -0
  41. package/dist/client/components/fields/rich-text-editor/presets.d.mts +10 -0
  42. package/dist/client/components/fields/rich-text-editor/slash-commands.mjs +27 -6
  43. package/dist/client/components/fields/rich-text-editor/toolbar.mjs +464 -346
  44. package/dist/client/components/fields/rich-text-editor/types.d.mts +77 -0
  45. package/dist/client/components/fields/upload-field.mjs +45 -49
  46. package/dist/client/components/filter-builder/columns-tab.mjs +69 -62
  47. package/dist/client/components/filter-builder/filter-builder-sheet.mjs +473 -308
  48. package/dist/client/components/filter-builder/filters-tab.mjs +109 -82
  49. package/dist/client/components/filter-builder/saved-views-tab.mjs +300 -198
  50. package/dist/client/components/history-sidebar.mjs +850 -340
  51. package/dist/client/components/layout/field-layout-renderer.mjs +6 -5
  52. package/dist/client/components/locale-switcher.mjs +8 -8
  53. package/dist/client/components/media/media-grid.mjs +12 -9
  54. package/dist/client/components/media/media-picker-dialog.mjs +242 -230
  55. package/dist/client/components/preview/live-preview-mode.mjs +1 -1
  56. package/dist/client/components/primitives/asset-preview.mjs +37 -22
  57. package/dist/client/components/primitives/date-input.mjs +212 -249
  58. package/dist/client/components/primitives/dropzone.mjs +192 -159
  59. package/dist/client/components/primitives/field-select-control.mjs +93 -0
  60. package/dist/client/components/primitives/select-multi.mjs +251 -230
  61. package/dist/client/components/primitives/select-single.mjs +345 -290
  62. package/dist/client/components/primitives/time-input.mjs +2 -2
  63. package/dist/client/components/sheets/resource-sheet.mjs +2 -0
  64. package/dist/client/components/ui/accordion.mjs +4 -4
  65. package/dist/client/components/ui/alert.mjs +3 -3
  66. package/dist/client/components/ui/badge.mjs +4 -4
  67. package/dist/client/components/ui/button.mjs +47 -37
  68. package/dist/client/components/ui/card.mjs +2 -2
  69. package/dist/client/components/ui/checkbox.mjs +1 -1
  70. package/dist/client/components/ui/command.mjs +5 -5
  71. package/dist/client/components/ui/dialog.mjs +3 -3
  72. package/dist/client/components/ui/drawer.mjs +1 -1
  73. package/dist/client/components/ui/dropdown-menu.mjs +157 -15
  74. package/dist/client/components/ui/empty-state.mjs +88 -59
  75. package/dist/client/components/ui/field.mjs +2 -2
  76. package/dist/client/components/ui/input-group.mjs +3 -3
  77. package/dist/client/components/ui/input.mjs +1 -1
  78. package/dist/client/components/ui/kbd.mjs +1 -1
  79. package/dist/client/components/ui/label.mjs +1 -1
  80. package/dist/client/components/ui/popover.mjs +19 -11
  81. package/dist/client/components/ui/scroll-fade.mjs +170 -0
  82. package/dist/client/components/ui/search-input.mjs +1 -1
  83. package/dist/client/components/ui/select.mjs +129 -27
  84. package/dist/client/components/ui/sheet.mjs +54 -34
  85. package/dist/client/components/ui/sidebar.mjs +15 -14
  86. package/dist/client/components/ui/skeleton.mjs +28 -12
  87. package/dist/client/components/ui/switch.mjs +2 -2
  88. package/dist/client/components/ui/table.mjs +82 -74
  89. package/dist/client/components/ui/tabs.mjs +26 -31
  90. package/dist/client/components/ui/textarea.mjs +1 -1
  91. package/dist/client/components/ui/tooltip.mjs +1 -1
  92. package/dist/client/components/widgets/chart-widget.mjs +134 -96
  93. package/dist/client/components/widgets/progress-widget.mjs +59 -34
  94. package/dist/client/components/widgets/quick-actions-widget.mjs +184 -113
  95. package/dist/client/components/widgets/recent-items-widget.mjs +144 -102
  96. package/dist/client/components/widgets/stats-widget.mjs +91 -72
  97. package/dist/client/components/widgets/table-widget.mjs +159 -246
  98. package/dist/client/components/widgets/timeline-widget.mjs +66 -43
  99. package/dist/client/components/widgets/value-widget.mjs +261 -152
  100. package/dist/client/components/widgets/widget-empty-state.mjs +88 -0
  101. package/dist/client/components/widgets/widget-skeletons.mjs +53 -20
  102. package/dist/client/contexts/focus-context.d.mts +2 -2
  103. package/dist/client/hooks/use-action.mjs +63 -55
  104. package/dist/client/hooks/use-audit-history.mjs +1 -65
  105. package/dist/client/hooks/use-collection-validation.mjs +36 -23
  106. package/dist/client/hooks/use-collection.mjs +96 -1
  107. package/dist/client/hooks/use-saved-views.mjs +70 -49
  108. package/dist/client/hooks/use-server-actions.mjs +59 -40
  109. package/dist/client/hooks/use-server-validation.mjs +156 -41
  110. package/dist/client/hooks/use-server-widget-data.mjs +1 -1
  111. package/dist/client/hooks/use-setup-status.d.mts +3 -3
  112. package/dist/client/hooks/use-setup-status.mjs +2 -2
  113. package/dist/client/hooks/use-transition-stage.mjs +2 -10
  114. package/dist/client/hooks/use-validation-error-map.mjs +31 -13
  115. package/dist/client/hooks/use-view-state.mjs +238 -174
  116. package/dist/client/i18n/date-locale.mjs +33 -0
  117. package/dist/client/i18n/hooks.mjs +17 -1
  118. package/dist/client/lib/utils.mjs +3 -2
  119. package/dist/client/preview/block-scope-context.d.mts +2 -2
  120. package/dist/client/preview/preview-banner.d.mts +2 -2
  121. package/dist/client/preview/preview-field.d.mts +4 -4
  122. package/dist/client/preview/preview-field.mjs +2 -2
  123. package/dist/client/runtime/provider.mjs +8 -1
  124. package/dist/client/runtime/translations-provider.mjs +1 -1
  125. package/dist/client/scope/picker.d.mts +2 -2
  126. package/dist/client/scope/provider.d.mts +2 -2
  127. package/dist/client/styles/base.css +1022 -0
  128. package/dist/client/styles/index.css +3 -589
  129. package/dist/client/utils/auto-expand-fields.mjs +4 -2
  130. package/dist/client/utils/keyboard-shortcuts.mjs +26 -0
  131. package/dist/client/utils/use-lazy-component.mjs +80 -0
  132. package/dist/client/views/auth/auth-layout.d.mts +18 -11
  133. package/dist/client/views/auth/auth-layout.mjs +291 -80
  134. package/dist/client/views/auth/forgot-password-form.d.mts +2 -2
  135. package/dist/client/views/auth/forgot-password-form.mjs +2 -2
  136. package/dist/client/views/auth/login-form.d.mts +2 -2
  137. package/dist/client/views/auth/login-form.mjs +1 -1
  138. package/dist/client/views/auth/reset-password-form.d.mts +2 -2
  139. package/dist/client/views/auth/reset-password-form.mjs +2 -2
  140. package/dist/client/views/auth/setup-form.d.mts +2 -2
  141. package/dist/client/views/collection/auto-form-fields.mjs +11 -9
  142. package/dist/client/views/collection/bulk-action-toolbar.mjs +173 -138
  143. package/dist/client/views/collection/cells/complex-cells.mjs +22 -22
  144. package/dist/client/views/collection/cells/primitive-cells.mjs +1 -1
  145. package/dist/client/views/collection/cells/relation-cells.mjs +147 -129
  146. package/dist/client/views/collection/cells/shared/asset-thumbnail.mjs +224 -278
  147. package/dist/client/views/collection/cells/shared/relation-chip.mjs +64 -36
  148. package/dist/client/views/collection/cells/upload-cells.mjs +199 -9
  149. package/dist/client/views/collection/columns/build-columns.mjs +29 -9
  150. package/dist/client/views/collection/columns/column-defaults.mjs +2 -2
  151. package/dist/client/views/collection/field-renderer.mjs +50 -89
  152. package/dist/client/views/collection/form-view.mjs +237 -227
  153. package/dist/client/views/collection/table-view.mjs +1162 -229
  154. package/dist/client/views/collection/view-skeletons.mjs +222 -79
  155. package/dist/client/views/common/global-search.mjs +29 -18
  156. package/dist/client/views/dashboard/dashboard-grid.mjs +678 -501
  157. package/dist/client/views/dashboard/dashboard-widget.mjs +6 -3
  158. package/dist/client/views/dashboard/widget-card.mjs +23 -14
  159. package/dist/client/views/globals/global-form-view.mjs +634 -589
  160. package/dist/client/views/layout/admin-layout-provider.mjs +67 -70
  161. package/dist/client/views/layout/admin-layout.d.mts +3 -6
  162. package/dist/client/views/layout/admin-layout.mjs +149 -172
  163. package/dist/client/views/layout/admin-router.mjs +747 -544
  164. package/dist/client/views/layout/admin-sidebar.d.mts +38 -1
  165. package/dist/client/views/layout/admin-sidebar.mjs +751 -591
  166. package/dist/client/views/layout/admin-theme.d.mts +10 -0
  167. package/dist/client/views/layout/admin-theme.mjs +84 -0
  168. package/dist/client/views/layout/admin-view-layout.mjs +161 -0
  169. package/dist/client/views/pages/accept-invite-page.d.mts +2 -2
  170. package/dist/client/views/pages/accept-invite-page.mjs +49 -26
  171. package/dist/client/views/pages/dashboard-page.d.mts +2 -2
  172. package/dist/client/views/pages/forgot-password-page.d.mts +2 -2
  173. package/dist/client/views/pages/forgot-password-page.mjs +2 -19
  174. package/dist/client/views/pages/invite-page.d.mts +2 -2
  175. package/dist/client/views/pages/invite-page.mjs +2 -19
  176. package/dist/client/views/pages/login-page.d.mts +3 -3
  177. package/dist/client/views/pages/login-page.mjs +4 -21
  178. package/dist/client/views/pages/reset-password-page.d.mts +2 -2
  179. package/dist/client/views/pages/reset-password-page.mjs +3 -20
  180. package/dist/client/views/pages/setup-page.d.mts +2 -2
  181. package/dist/client/views/pages/setup-page.mjs +3 -20
  182. package/dist/client.d.mts +6 -2
  183. package/dist/client.mjs +2 -1
  184. package/dist/components/rich-text/rich-text-renderer.d.mts +2 -2
  185. package/dist/index.d.mts +6 -2
  186. package/dist/index.mjs +2 -1
  187. package/dist/server/augmentation/dashboard.d.mts +67 -3
  188. package/dist/server/augmentation/form-layout.d.mts +21 -0
  189. package/dist/server/augmentation/index.d.mts +1 -1
  190. package/dist/server/codegen/admin-client-template.mjs +4 -0
  191. package/dist/server/fields/blocks.d.mts +1 -1
  192. package/dist/server/fields/blocks.mjs +12 -0
  193. package/dist/server/fields/rich-text.d.mts +1 -1
  194. package/dist/server/fields/rich-text.mjs +8 -0
  195. package/dist/server/i18n/index.mjs +17 -1
  196. package/dist/server/i18n/messages/cs.mjs +23 -0
  197. package/dist/server/i18n/messages/de.mjs +23 -0
  198. package/dist/server/i18n/messages/en.mjs +64 -1
  199. package/dist/server/i18n/messages/es.mjs +23 -0
  200. package/dist/server/i18n/messages/fr.mjs +23 -0
  201. package/dist/server/i18n/messages/pl.mjs +23 -0
  202. package/dist/server/i18n/messages/pt.mjs +23 -0
  203. package/dist/server/i18n/messages/sk.mjs +83 -1
  204. package/dist/server/modules/admin/block/introspection.mjs +4 -1
  205. package/dist/server/modules/admin/block/prefetch.mjs +12 -2
  206. package/dist/server/modules/admin/collections/account.d.mts +50 -50
  207. package/dist/server/modules/admin/collections/admin-locks.d.mts +54 -54
  208. package/dist/server/modules/admin/collections/admin-preferences.d.mts +39 -39
  209. package/dist/server/modules/admin/collections/admin-saved-views.d.mts +47 -47
  210. package/dist/server/modules/admin/collections/apikey.d.mts +64 -64
  211. package/dist/server/modules/admin/collections/assets.d.mts +20 -20
  212. package/dist/server/modules/admin/collections/assets.mjs +0 -1
  213. package/dist/server/modules/admin/collections/session.d.mts +42 -42
  214. package/dist/server/modules/admin/collections/user.d.mts +40 -28
  215. package/dist/server/modules/admin/collections/user.mjs +40 -9
  216. package/dist/server/modules/admin/collections/verification.d.mts +36 -36
  217. package/dist/server/modules/admin/dto/admin-config.dto.mjs +2 -0
  218. package/dist/server/modules/admin/factories.mjs +7 -18
  219. package/dist/server/modules/admin/index.d.mts +1 -1
  220. package/dist/server/modules/admin/routes/admin-config.d.mts +2 -2
  221. package/dist/server/modules/admin/routes/admin-config.mjs +34 -16
  222. package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
  223. package/dist/server/modules/admin/routes/execute-action.mjs +33 -0
  224. package/dist/server/modules/admin/routes/locales.d.mts +2 -2
  225. package/dist/server/modules/admin/routes/preview.d.mts +11 -11
  226. package/dist/server/modules/admin/routes/reactive.d.mts +9 -9
  227. package/dist/server/modules/admin/routes/setup.d.mts +10 -10
  228. package/dist/server/modules/admin/routes/setup.mjs +7 -7
  229. package/dist/server/modules/admin/routes/translations.d.mts +4 -4
  230. package/dist/server/modules/admin/routes/translations.mjs +5 -1
  231. package/dist/server/modules/admin/routes/widget-data.d.mts +5 -5
  232. package/dist/server/modules/admin-preferences/collections/admin-preferences.mjs +1 -1
  233. package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +25 -25
  234. package/dist/server/modules/audit/.generated/module.d.mts +7 -7
  235. package/dist/server/modules/audit/.generated/module.mjs +1 -1
  236. package/dist/server/modules/audit/collections/audit-log.d.mts +39 -39
  237. package/dist/server/modules/audit/collections/audit-log.mjs +1 -1
  238. package/dist/server/modules/audit/config/app.mjs +99 -42
  239. package/dist/server/modules/audit/jobs/audit-cleanup.mjs +1 -1
  240. package/dist/server/plugin.mjs +4 -2
  241. package/dist/server/proxy-factories.d.mts +4 -3
  242. package/dist/server/proxy-factories.mjs +34 -8
  243. package/dist/shared/types/saved-views.types.d.mts +2 -0
  244. package/package.json +6 -4
  245. package/dist/client/components/fields/rich-text-editor/link-popover.mjs +0 -85
  246. package/dist/client/components/ui/spinner.mjs +0 -52
  247. package/dist/client/components/ui/toolbar.mjs +0 -136
  248. package/dist/client/contexts/breadcrumb-context.mjs +0 -60
  249. 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) {
302
+ if (value === null || value === void 0 || value === "") return "No value";
303
+ if (Array.isArray(value)) return value.length > 0 ? value.map((item) => stringifyGroupValue(item, field, resolveText)).join(", ") : "No value";
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 ? "Enable orderable before reordering" : !hasOrderField ? "Add a numeric order field before reordering" : isSearching ? "Clear search to reorder" : viewState.config.groupBy ? "Remove grouping to reorder" : hasActiveFilters ? "Clear filters to reorder" : hasMultiplePages ? "Show one page of items to reorder" : null;
1065
+ const reorderTooltip = reorderHardBlocker ?? (isOrderSortActive ? isReorderMode ? "Exit reorder mode" : "Reorder items" : `Switch to ${orderField} sort and reorder`);
1066
+ const reorderAriaLabel = reorderHardBlocker ? `Reorder unavailable: ${reorderHardBlocker}` : isReorderMode ? "Exit reorder mode" : "Enter reorder mode";
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,152 @@ 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("Order saved");
1174
+ } catch (error) {
1175
+ clearReorderOverlay();
1176
+ setOptimisticOrderIds(previousOrderIds);
1177
+ actionHelpers.toast.error(error instanceof Error ? error.message : "Could not save order");
1178
+ }
1179
+ }, [
1180
+ sortableRowIds,
1181
+ tableRows,
1182
+ updateBatchMutation,
1183
+ orderField,
1184
+ orderStep,
1185
+ actionHelpers.toast,
1186
+ clearReorderOverlay
1187
+ ]);
1188
+ const groupedRowModel = useMemo(() => {
1189
+ const rows = tableRows;
1190
+ const groupBy_0 = viewState.config.groupBy;
1191
+ if (!groupBy_0) return rows.map((row_7) => ({
1192
+ type: "row",
1193
+ row: row_7
1194
+ }));
1195
+ const groupField = groupableFields.find((field_2) => field_2.name === groupBy_0);
1196
+ const collapsedGroups = new Set(viewState.config.collapsedGroups ?? []);
1197
+ const serverGroups = !isSearching ? listData?.groups : void 0;
1198
+ if (serverGroups?.length) {
1199
+ const rowsById_0 = new Map(rows.map((row_8) => [row_8.id, row_8]));
1200
+ return serverGroups.flatMap((group) => {
1201
+ const label = stringifyGroupValue(group.value, groupField, resolveText);
1202
+ const groupKey = `${groupBy_0}:${label}`;
1203
+ const collapsed = collapsedGroups.has(groupKey);
1204
+ const groupRows = (group.docs ?? []).map((doc) => rowsById_0.get(String(doc.id))).filter(Boolean);
1205
+ return [{
1206
+ type: "group",
1207
+ key: groupKey,
1208
+ label,
1209
+ count: group.count,
1210
+ collapsed
1211
+ }, ...collapsed ? [] : groupRows.map((row_9) => ({
1212
+ type: "row",
1213
+ row: row_9
1214
+ }))];
1215
+ });
1216
+ }
1217
+ const groups = /* @__PURE__ */ new Map();
1218
+ for (const row_10 of rows) {
1219
+ const valueLabel = stringifyGroupValue(row_10.original?.[groupBy_0], groupField, resolveText);
1220
+ const groupKey_0 = `${groupBy_0}:${valueLabel}`;
1221
+ const group_0 = groups.get(groupKey_0);
1222
+ if (group_0) {
1223
+ group_0.rows.push(row_10);
1224
+ continue;
1225
+ }
1226
+ groups.set(groupKey_0, {
1227
+ label: valueLabel,
1228
+ rows: [row_10],
1229
+ sortIndex: getGroupSortIndex(row_10.original?.[groupBy_0], groupField)
1230
+ });
1231
+ }
1232
+ return Array.from(groups.entries()).sort(([, a], [, b]) => a.sortIndex - b.sortIndex).flatMap(([key_0, group_1]) => {
1233
+ const collapsed_0 = collapsedGroups.has(key_0);
1234
+ return [{
1235
+ type: "group",
1236
+ key: key_0,
1237
+ label: group_1.label,
1238
+ count: group_1.rows.length,
1239
+ collapsed: collapsed_0
1240
+ }, ...collapsed_0 ? [] : group_1.rows.map((row_11) => ({
1241
+ type: "row",
1242
+ row: row_11
1243
+ }))];
1244
+ });
1245
+ }, [
1246
+ tableRows,
1247
+ viewState.config.groupBy,
1248
+ viewState.config.collapsedGroups,
1249
+ groupableFields,
1250
+ isSearching,
1251
+ listData?.groups,
1252
+ resolveText
1253
+ ]);
457
1254
  const handleSaveView = (name, config_0) => {
458
1255
  saveViewMutation.mutate({
459
1256
  name,
@@ -463,11 +1260,11 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
463
1260
  const handleDeleteView = (viewId) => {
464
1261
  deleteViewMutation.mutate(viewId);
465
1262
  };
466
- const handleRowClick = (item_0) => {
467
- navigate(`${basePath}/collections/${collection}/${item_0.id}`);
1263
+ const handleRowClick = (item_4) => {
1264
+ navigate(`${basePath}/collections/${collection}/${item_4.id}`);
468
1265
  };
469
1266
  const handleBulkDelete = React.useCallback(async (ids_0) => {
470
- const results = await Promise.allSettled(ids_0.map((id) => deleteMutation.mutateAsync({ id })));
1267
+ const results = await Promise.allSettled(ids_0.map((id_4) => deleteMutation.mutateAsync({ id: id_4 })));
471
1268
  const successCount = results.filter((r) => r.status === "fulfilled").length;
472
1269
  const failCount = results.filter((r_0) => r_0.status === "rejected").length;
473
1270
  if (failCount === 0) actionHelpers.toast.success(t("collection.bulkDeleteSuccess", { count: successCount }));
@@ -482,7 +1279,7 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
482
1279
  t
483
1280
  ]);
484
1281
  const handleBulkRestore = React.useCallback(async (ids_1) => {
485
- const results_0 = await Promise.allSettled(ids_1.map((id_0) => restoreMutation.mutateAsync({ id: id_0 })));
1282
+ const results_0 = await Promise.allSettled(ids_1.map((id_5) => restoreMutation.mutateAsync({ id: id_5 })));
486
1283
  const successCount_0 = results_0.filter((r_1) => r_1.status === "fulfilled").length;
487
1284
  const failCount_0 = results_0.filter((r_2) => r_2.status === "rejected").length;
488
1285
  if (failCount_0 === 0) actionHelpers.toast.success(t("collection.bulkRestoreSuccess", { count: successCount_0 }));
@@ -496,100 +1293,160 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
496
1293
  actionHelpers,
497
1294
  t
498
1295
  ]);
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, {
1296
+ if (listError && !isSearching) {
1297
+ const errorMessage = listError instanceof Error ? listError.message : void 0;
1298
+ return /* @__PURE__ */ jsx("div", {
1299
+ className: "container",
1300
+ children: /* @__PURE__ */ jsx(EmptyState, {
1301
+ variant: "error",
1302
+ iconName: "ph:warning-circle",
1303
+ title: t("error.failedToLoad"),
1304
+ description: errorMessage,
1305
+ height: "h-64",
1306
+ action: /* @__PURE__ */ jsxs(Button, {
513
1307
  variant: "outline",
514
1308
  size: "sm",
1309
+ className: "gap-2",
515
1310
  onClick: () => window.location.reload(),
516
- children: t("common.retry")
1311
+ children: [/* @__PURE__ */ jsx(Icon, {
1312
+ icon: "ph:arrow-clockwise",
1313
+ className: "size-3.5"
1314
+ }), t("common.retry")]
517
1315
  })
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",
1316
+ })
1317
+ });
1318
+ }
1319
+ if (isLoading) return /* @__PURE__ */ jsx(TableViewSkeleton, {});
1320
+ const emptyStateTitle = isSearching || hasActiveFilters ? t("collectionSearch.noResults") : t("table.noItemsInCollection");
1321
+ const emptyStateDescription = isSearching ? t("collectionSearch.noResultsDescription") : hasActiveFilters ? t("viewOptions.noResultsDescription") : t("table.emptyDescription");
1322
+ const emptyStateAction = isSearching || hasActiveFilters ? /* @__PURE__ */ jsxs(Fragment, { children: [isSearching && /* @__PURE__ */ jsxs(Button, {
1323
+ variant: "outline",
1324
+ size: "sm",
1325
+ className: "gap-2",
1326
+ onClick: () => setSearchTerm(""),
1327
+ children: [/* @__PURE__ */ jsx(Icon, {
1328
+ icon: "ph:x",
1329
+ className: "size-3.5"
1330
+ }), t("common.clear")]
1331
+ }), hasActiveFilters && /* @__PURE__ */ jsxs(Button, {
1332
+ variant: "outline",
1333
+ size: "sm",
1334
+ className: "gap-2",
1335
+ onClick: clearFilters,
1336
+ children: [/* @__PURE__ */ jsx(Icon, {
1337
+ icon: "ph:funnel-x",
1338
+ className: "size-3.5"
1339
+ }), t("viewOptions.clearFilters")]
1340
+ })] }) : void 0;
1341
+ return /* @__PURE__ */ jsx(AdminViewLayout, {
1342
+ header: /* @__PURE__ */ jsx(AdminViewHeader, {
1343
+ title: resolveText(config?.label ?? schema?.admin?.config?.label, collection),
1344
+ titleAccessory: localeOptions.length > 0 ? /* @__PURE__ */ jsx(LocaleSwitcher, {
1345
+ locales: localeOptions,
1346
+ value: contentLocale,
1347
+ onChange: setContentLocale
1348
+ }) : void 0,
1349
+ description: resolveText(config?.description ?? schema?.admin?.config?.description),
1350
+ actions: /* @__PURE__ */ jsxs(Fragment, { children: [
1351
+ isOrderableEnabled && /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, { render: /* @__PURE__ */ jsx(Button, {
1352
+ variant: "outline",
1353
+ size: "icon-sm",
1354
+ className: cn("relative aria-disabled:opacity-50", isReorderMode && "border-foreground bg-foreground text-background hover:bg-foreground/90 hover:text-background"),
1355
+ onClick: handleReorderToggle,
1356
+ "aria-label": reorderAriaLabel,
1357
+ "aria-disabled": !canReorder || void 0,
1358
+ "aria-pressed": isReorderMode,
1359
+ children: /* @__PURE__ */ jsx(Icon, { icon: "ph:arrows-down-up" })
1360
+ }) }), /* @__PURE__ */ jsx(TooltipContent, {
1361
+ side: "bottom",
1362
+ align: "end",
1363
+ children: reorderTooltip
1364
+ })] }),
1365
+ showSearch && /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, { render: /* @__PURE__ */ jsxs(Button, {
1366
+ variant: "outline",
1367
+ size: "icon-sm",
1368
+ className: "relative",
1369
+ onClick: () => setIsSearchPanelOpen((open) => !open),
1370
+ "aria-label": t("common.search"),
1371
+ 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" })]
1372
+ }) }), /* @__PURE__ */ jsx(TooltipContent, {
1373
+ side: "bottom",
1374
+ align: "end",
1375
+ children: t("common.search")
1376
+ })] }),
1377
+ showFilters && /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, { render: /* @__PURE__ */ jsxs(Button, {
1378
+ variant: "outline",
1379
+ size: "icon-sm",
1380
+ className: "relative",
1381
+ onClick: () => setIsSheetOpen(true),
1382
+ "aria-label": t("viewOptions.title"),
1383
+ 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" })]
1384
+ }) }), /* @__PURE__ */ jsx(TooltipContent, {
1385
+ side: "bottom",
1386
+ align: "end",
1387
+ children: t("viewOptions.title")
1388
+ })] }),
1389
+ canUploadToCollection && /* @__PURE__ */ jsx(UploadCollectionButton, {
1390
+ collection,
1391
+ onUploaded: () => actionHelpers.invalidateCollection(collection)
1392
+ }),
1393
+ headerActions,
1394
+ ((actions.header.primary?.length ?? 0) > 0 || (actions.header.secondary?.length ?? 0) > 0) && /* @__PURE__ */ jsx(HeaderActions, {
1395
+ actions: actions.header,
1396
+ collection,
1397
+ helpers: actionHelpers,
1398
+ onOpenDialog: (action_0) => openDialog(action_0)
1399
+ })
1400
+ ] })
1401
+ }),
1402
+ contentClassName: "overflow-y-auto pb-3",
539
1403
  children: /* @__PURE__ */ jsxs("div", {
540
- className: "qa-table-view__inner min-w-0 space-y-4",
1404
+ className: "qa-table-view min-w-0 space-y-4",
541
1405
  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",
1406
+ showToolbar && showSearch && (isSearchPanelOpen || searchTerm) && /* @__PURE__ */ jsx("div", {
1407
+ className: "max-w-xl",
1408
+ children: /* @__PURE__ */ jsx(SearchInput, {
1409
+ value: searchTerm,
1410
+ onChange: (e_3) => setSearchTerm(e_3.target.value),
1411
+ onClear: () => setSearchTerm(""),
1412
+ placeholder: t("common.search"),
1413
+ containerClassName: "h-10"
1414
+ })
1415
+ }),
1416
+ isReorderMode && canUseOrderableSort && /* @__PURE__ */ jsxs("div", {
1417
+ 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
1418
  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
- })]
1419
+ className: "flex min-w-0 items-center gap-2",
1420
+ children: [
1421
+ /* @__PURE__ */ jsx("span", {
1422
+ className: "bg-foreground text-background inline-flex size-5 items-center justify-center rounded-full",
1423
+ children: /* @__PURE__ */ jsx(Icon, {
1424
+ icon: "ph:arrows-down-up",
1425
+ className: "size-3"
1426
+ })
1427
+ }),
1428
+ /* @__PURE__ */ jsx("span", {
1429
+ className: "text-foreground font-medium",
1430
+ children: "Reorder mode"
1431
+ }),
1432
+ /* @__PURE__ */ jsxs("span", {
1433
+ className: "hidden sm:inline",
1434
+ children: [
1435
+ "Sorted by ",
1436
+ orderField,
1437
+ " ",
1438
+ orderDirection,
1439
+ "."
1440
+ ]
1441
+ })
1442
+ ]
1443
+ }), /* @__PURE__ */ jsx(Button, {
1444
+ variant: "ghost",
1445
+ size: "xs",
1446
+ onClick: () => setIsReorderMode(false),
1447
+ children: "Done"
568
1448
  })]
569
1449
  }),
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
1450
  /* @__PURE__ */ jsx(BulkActionToolbar, {
594
1451
  table,
595
1452
  actions: actions.bulk,
@@ -597,104 +1454,178 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
597
1454
  helpers: actionHelpers,
598
1455
  totalCount: isSearching ? searchData?.total : listData?.totalDocs,
599
1456
  pageCount: filteredItems.length,
600
- onOpenDialog: (action_0, items_0) => openDialog(action_0, items_0),
1457
+ onOpenDialog: (action_1, items_0) => openDialog(action_1, items_0),
601
1458
  onBulkDelete: handleBulkDelete,
602
1459
  onBulkRestore: handleBulkRestore,
603
1460
  filterCount: viewState.config.filters.length,
604
1461
  onOpenFilters: () => setIsSheetOpen(true),
605
- onClearFilters: () => viewState.setConfig({
606
- ...viewState.config,
607
- filters: []
608
- })
1462
+ onClearFilters: clearFilters
609
1463
  }),
610
1464
  /* @__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", {
1465
+ className: "qa-table-view__table-wrapper min-w-0",
1466
+ children: [/* @__PURE__ */ jsx(ScrollFade, {
1467
+ leftInset: selectColumnWidth + titleColumnWidth,
1468
+ children: /* @__PURE__ */ jsxs(DndContext, {
1469
+ sensors: reorderSensors,
1470
+ collisionDetection: closestCenter,
1471
+ onDragStart: handleReorderDragStart,
1472
+ onDragCancel: handleReorderDragCancel,
1473
+ onDragEnd: handleReorderDragEnd,
1474
+ children: [/* @__PURE__ */ jsxs(Table, {
1475
+ className: "table-fixed",
1476
+ style: { width: table.getTotalSize() },
1477
+ "aria-label": resolveText(config?.label ?? schema?.admin?.config?.label, collection),
1478
+ children: [
1479
+ /* @__PURE__ */ jsx("colgroup", { children: visibleLeafColumns.map((column) => /* @__PURE__ */ jsx("col", { style: { width: column.getSize() } }, column.id)) }),
1480
+ /* @__PURE__ */ jsx(TableHeader, { children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx(TableRow, {
1481
+ className: "hover:bg-transparent",
1482
+ children: headerGroup.headers.map((header, headerIndex) => {
1483
+ const isCheckboxCol = headerIndex === 0;
1484
+ const columnWidth = getColumnSize(header.column, isCheckboxCol ? 40 : 120);
1485
+ const stickyLeft = headerIndex < STICKY_TABLE_COLUMN_COUNT ? getStickyLeftOffset(visibleLeafColumns, headerIndex) : void 0;
1486
+ const sortDirection = header.column.getIsSorted();
1487
+ const ariaSort = header.column.getCanSort() ? sortDirection === "asc" ? "ascending" : sortDirection === "desc" ? "descending" : "none" : void 0;
1488
+ return /* @__PURE__ */ jsx(TableHead, {
1489
+ stickyLeft,
1490
+ showStickyBorder: headerIndex === STICKY_TABLE_COLUMN_COUNT - 1,
1491
+ className: isCheckboxCol ? "w-9 min-w-9 px-1.5" : void 0,
1492
+ style: getColumnSizeStyle(columnWidth),
1493
+ "aria-sort": ariaSort,
1494
+ children: header.isPlaceholder ? null : /* @__PURE__ */ jsxs("button", {
653
1495
  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);
1496
+ 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" : "",
1497
+ onClick: header.column.getToggleSortingHandler(),
1498
+ "aria-label": header.column.getCanSort() ? `Sort by ${typeof header.column.columnDef.header === "string" ? header.column.columnDef.header : header.column.id}` : void 0,
1499
+ children: [flexRender(header.column.columnDef.header, header.getContext()), header.column.getIsSorted() && /* @__PURE__ */ jsx("span", {
1500
+ "aria-hidden": "true",
1501
+ children: header.column.getIsSorted() === "asc" ? "↑" : "↓"
1502
+ })]
1503
+ })
1504
+ }, header.id);
1505
+ })
1506
+ }, headerGroup.id)) }),
1507
+ /* @__PURE__ */ jsx(SortableContext, {
1508
+ items: sortableRowIds,
1509
+ strategy: verticalListSortingStrategy,
1510
+ children: /* @__PURE__ */ jsx(TableBody, { children: groupedRowModel.map((entry) => {
1511
+ if (entry.type === "group") return /* @__PURE__ */ jsxs(TableRow, {
1512
+ className: "hover:bg-transparent",
1513
+ children: [
1514
+ /* @__PURE__ */ jsx(TableCell, {
1515
+ stickyLeft: 0,
1516
+ className: "w-9 min-w-9 border-b-0 px-1.5 group-hover/row:bg-transparent",
1517
+ style: getColumnSizeStyle(selectColumnWidth)
1518
+ }),
1519
+ /* @__PURE__ */ jsx(TableCell, {
1520
+ stickyLeft: selectColumnWidth,
1521
+ showStickyBorder: true,
1522
+ className: "bg-background top-8 z-20 border-b-0 group-hover/row:bg-transparent",
1523
+ style: getColumnSizeStyle(titleColumnWidth),
1524
+ children: /* @__PURE__ */ jsxs("button", {
1525
+ type: "button",
1526
+ "aria-expanded": !entry.collapsed,
1527
+ 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",
1528
+ onClick: () => viewState.toggleCollapsedGroup(entry.key),
1529
+ children: [
1530
+ /* @__PURE__ */ jsx(Icon, {
1531
+ icon: entry.collapsed ? "ph:caret-right" : "ph:caret-down",
1532
+ className: "size-3.5 shrink-0"
1533
+ }),
1534
+ /* @__PURE__ */ jsx("span", { children: entry.label }),
1535
+ groupingConfig?.showCounts !== false && /* @__PURE__ */ jsx("span", {
1536
+ 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",
1537
+ children: entry.count
1538
+ })
1539
+ ]
1540
+ })
1541
+ }),
1542
+ visibleLeafColumns.length > STICKY_TABLE_COLUMN_COUNT && /* @__PURE__ */ jsx(TableCell, {
1543
+ colSpan: visibleLeafColumns.length - STICKY_TABLE_COLUMN_COUNT,
1544
+ className: "border-b-0"
1545
+ })
1546
+ ]
1547
+ }, entry.key);
1548
+ const row_12 = entry.row;
1549
+ const isRowDeleted = !!row_12.original?.deletedAt;
1550
+ return /* @__PURE__ */ jsx(isReorderMode ? SortableTableRow : TableRow, {
1551
+ id: String(row_12.id),
1552
+ "data-state": row_12.getIsSelected() && "selected",
1553
+ className: cn("group", isReorderMode && "bg-muted/[0.18]", isHighlighted(row_12.id) && "animate-realtime-pulse", isRowDeleted && "opacity-50"),
1554
+ children: row_12.getVisibleCells().map((cell, cellIndex) => {
1555
+ const isCheckboxCol_0 = cellIndex === 0;
1556
+ const columnWidth_0 = getColumnSize(cell.column, isCheckboxCol_0 ? 40 : 120);
1557
+ const stickyLeft_0 = cellIndex < STICKY_TABLE_COLUMN_COUNT ? getStickyLeftOffset(visibleLeafColumns, cellIndex) : void 0;
1558
+ const isTitleCol = cellIndex === 1;
1559
+ return /* @__PURE__ */ jsx(TableCell, {
1560
+ stickyLeft: stickyLeft_0,
1561
+ showStickyBorder: cellIndex === STICKY_TABLE_COLUMN_COUNT - 1,
1562
+ className: isCheckboxCol_0 ? "w-9 min-w-9 px-1.5" : void 0,
1563
+ style: getColumnSizeStyle(columnWidth_0),
1564
+ children: isTitleCol ? /* @__PURE__ */ jsxs("div", {
1565
+ className: "flex min-w-0 items-center gap-2",
1566
+ children: [
1567
+ /* @__PURE__ */ jsx("button", {
1568
+ type: "button",
1569
+ onClick: () => handleRowClick(row_12.original),
1570
+ disabled: isReorderMode,
1571
+ 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"),
1572
+ children: flexRender(cell.column.columnDef.cell, cell.getContext())
1573
+ }),
1574
+ isRowDeleted && /* @__PURE__ */ jsxs("span", {
1575
+ className: "text-destructive bg-destructive/10 inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-xs",
1576
+ children: [/* @__PURE__ */ jsx(Icon, {
1577
+ icon: "ph:trash",
1578
+ className: "size-3"
1579
+ }), t("common.deleted")]
1580
+ }),
1581
+ isDocLocked(row_12.id) && (() => {
1582
+ const lock = getLock(row_12.id);
1583
+ const user_0 = lock ? getLockUser(lock) : null;
1584
+ return /* @__PURE__ */ jsxs("span", {
1585
+ className: "text-muted-foreground bg-muted inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-xs",
1586
+ title: user_0?.name ?? user_0?.email ?? "Someone is editing",
1587
+ children: [user_0?.image ? /* @__PURE__ */ jsx("img", {
1588
+ src: user_0.image,
1589
+ alt: "",
1590
+ className: "image-outline size-4 rounded-full"
1591
+ }) : /* @__PURE__ */ jsx(Icon, {
1592
+ icon: "ph:pencil-simple",
1593
+ className: "size-3"
1594
+ }), /* @__PURE__ */ jsx("span", {
1595
+ className: "max-w-20 truncate",
1596
+ children: user_0?.name?.split(" ")[0] ?? t("table.editing")
1597
+ })]
1598
+ });
1599
+ })()
1600
+ ]
1601
+ }) : flexRender(cell.column.columnDef.cell, cell.getContext())
1602
+ }, cell.id);
1603
+ })
1604
+ }, row_12.id);
1605
+ }) })
1606
+ })
1607
+ ]
1608
+ }), /* @__PURE__ */ jsx(DragOverlay, {
1609
+ adjustScale: false,
1610
+ dropAnimation: REORDER_DROP_ANIMATION,
1611
+ children: /* @__PURE__ */ jsx(ReorderDragOverlay, {
1612
+ row: activeReorderRow,
1613
+ columns: visibleLeafColumns,
1614
+ rect: activeReorderRect
687
1615
  })
688
- }, row_1.id);
689
- }) })]
1616
+ })]
1617
+ })
690
1618
  }), !table.getRowModel().rows.length && (emptyState || /* @__PURE__ */ jsx(EmptyState, {
691
- title: "NO_RESULTS",
692
- description: isSearching ? t("collectionSearch.noResults") : t("table.noItemsInCollection"),
1619
+ variant: isSearching || hasActiveFilters ? "search" : "empty",
1620
+ iconName: isSearching ? "ph:magnifying-glass" : hasActiveFilters ? "ph:funnel-x" : "ph:tray",
1621
+ title: emptyStateTitle,
1622
+ description: emptyStateDescription,
1623
+ action: emptyStateAction,
693
1624
  height: "h-48"
694
1625
  }))]
695
1626
  }),
696
1627
  !isSearching && /* @__PURE__ */ jsxs("div", {
697
- className: "qa-table-view__pagination flex items-center justify-between gap-4 py-2",
1628
+ className: "qa-table-view__pagination flex items-center justify-between gap-4 py-2 tabular-nums",
698
1629
  role: "navigation",
699
1630
  "aria-label": t("table.pagination"),
700
1631
  children: [/* @__PURE__ */ jsxs("div", {
@@ -757,7 +1688,7 @@ 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
1693
  "aria-label": `Page ${pageNum}`,
763
1694
  "aria-current": currentPage === pageNum ? "page" : void 0,
@@ -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,