@questpie/admin 3.2.7 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +4 -6
  2. package/dist/client/blocks/block-renderer.d.mts +2 -2
  3. package/dist/client/builder/admin-types.d.mts +3 -3
  4. package/dist/client/builder/types/action-types.d.mts +1 -1
  5. package/dist/client/builder/types/collection-types.d.mts +59 -2
  6. package/dist/client/components/blocks/block-editor-provider.mjs +13 -0
  7. package/dist/client/components/fields/array-field.mjs +105 -122
  8. package/dist/client/components/fields/asset-preview-field.mjs +1 -1
  9. package/dist/client/components/fields/blocks-field/blocks-field.mjs +1 -1
  10. package/dist/client/components/fields/boolean-field.mjs +1 -1
  11. package/dist/client/components/fields/date-field.mjs +1 -1
  12. package/dist/client/components/fields/datetime-field.mjs +1 -1
  13. package/dist/client/components/fields/email-field.mjs +1 -1
  14. package/dist/client/components/fields/field-wrapper.mjs +44 -15
  15. package/dist/client/components/fields/number-field.mjs +1 -1
  16. package/dist/client/components/fields/object-array-field.mjs +179 -149
  17. package/dist/client/components/fields/object-field.mjs +96 -87
  18. package/dist/client/components/fields/relation-picker.mjs +1 -1
  19. package/dist/client/components/fields/relation-select.mjs +1 -1
  20. package/dist/client/components/fields/rich-text-editor/index.mjs +1 -1
  21. package/dist/client/components/fields/select-field.mjs +1 -1
  22. package/dist/client/components/fields/text-field.mjs +1 -1
  23. package/dist/client/components/fields/textarea-field.mjs +1 -1
  24. package/dist/client/components/fields/time-field.mjs +1 -1
  25. package/dist/client/components/fields/upload-field.mjs +1 -1
  26. package/dist/client/components/history-sidebar.mjs +10 -4
  27. package/dist/client/components/structured-diff.mjs +367 -0
  28. package/dist/client/components/ui/sidebar.mjs +1 -1
  29. package/dist/client/hooks/use-field-options.mjs +34 -15
  30. package/dist/client/hooks/use-transition-stage.mjs +2 -2
  31. package/dist/client/modules/admin.d.mts +3 -0
  32. package/dist/client/modules/admin.mjs +3 -0
  33. package/dist/client/preview/block-scope-context.d.mts +2 -2
  34. package/dist/client/preview/preview-banner.d.mts +2 -2
  35. package/dist/client/preview/preview-field.d.mts +4 -4
  36. package/dist/client/utils/auto-expand-fields.mjs +1 -1
  37. package/dist/client/views/collection/auto-form-fields.mjs +23 -19
  38. package/dist/client/views/collection/cells/complex-cells.mjs +1 -1
  39. package/dist/client/views/collection/columns/build-columns.mjs +1 -1
  40. package/dist/client/views/collection/columns/column-defaults.mjs +17 -4
  41. package/dist/client/views/collection/field-renderer.mjs +19 -7
  42. package/dist/client/views/collection/form-view.mjs +10 -6
  43. package/dist/client/views/collection/list-view.mjs +830 -0
  44. package/dist/client/views/collection/outline.mjs +363 -0
  45. package/dist/client/views/collection/table-view.mjs +25 -16
  46. package/dist/client/views/globals/global-form-view.mjs +47 -27
  47. package/dist/client/views/layout/admin-layout.d.mts +15 -1
  48. package/dist/client/views/layout/admin-layout.mjs +95 -31
  49. package/dist/client/views/layout/admin-sidebar.mjs +2 -2
  50. package/dist/client.d.mts +6 -6
  51. package/dist/client.mjs +1 -1
  52. package/dist/components/rich-text/rich-text-renderer.d.mts +2 -2
  53. package/dist/factories.d.mts +19 -0
  54. package/dist/factories.mjs +11 -0
  55. package/dist/fields.d.mts +4 -0
  56. package/dist/fields.mjs +5 -0
  57. package/dist/index.d.mts +6 -6
  58. package/dist/index.mjs +1 -1
  59. package/dist/modules/admin.d.mts +10 -0
  60. package/dist/modules/admin.mjs +9 -0
  61. package/dist/modules/audit.d.mts +5 -0
  62. package/dist/modules/audit.mjs +5 -0
  63. package/dist/server/augmentation/form-layout.d.mts +57 -2
  64. package/dist/server/augmentation/index.d.mts +3 -1
  65. package/dist/server/augmentation/shell.d.mts +48 -0
  66. package/dist/server/augmentation.d.mts +2 -1
  67. package/dist/server/codegen/admin-client-template.mjs +11 -4
  68. package/dist/server/fields/blocks.d.mts +9 -2
  69. package/dist/server/fields/blocks.mjs +1 -1
  70. package/dist/server/fields/index.d.mts +2 -2
  71. package/dist/server/fields/index.mjs +2 -2
  72. package/dist/server/fields/rich-text.d.mts +9 -2
  73. package/dist/server/fields/rich-text.mjs +1 -1
  74. package/dist/server/i18n/messages/cs.mjs +8 -0
  75. package/dist/server/i18n/messages/de.mjs +8 -0
  76. package/dist/server/i18n/messages/en.mjs +8 -0
  77. package/dist/server/i18n/messages/es.mjs +8 -0
  78. package/dist/server/i18n/messages/fr.mjs +8 -0
  79. package/dist/server/i18n/messages/pl.mjs +8 -0
  80. package/dist/server/i18n/messages/pt.mjs +8 -0
  81. package/dist/server/i18n/messages/sk.mjs +8 -0
  82. package/dist/server/modules/admin/.generated/module.d.mts +24 -19
  83. package/dist/server/modules/admin/.generated/module.mjs +5 -1
  84. package/dist/server/modules/admin/.generated/registries.d.mts +6 -4
  85. package/dist/server/modules/admin/client/.generated/module.d.mts +70 -70
  86. package/dist/server/modules/admin/client/.generated/module.mjs +3 -1
  87. package/dist/server/modules/admin/client/views/collection-form.d.mts +6 -0
  88. package/dist/server/modules/admin/client/views/collection-table.d.mts +6 -0
  89. package/dist/server/modules/admin/client/views/global-form.d.mts +6 -0
  90. package/dist/server/modules/admin/client/views/list-view.d.mts +6 -0
  91. package/dist/server/modules/admin/client/views/list-view.mjs +10 -0
  92. package/dist/server/modules/admin/collections/account.d.mts +50 -50
  93. package/dist/server/modules/admin/collections/admin-locks.d.mts +54 -54
  94. package/dist/server/modules/admin/collections/admin-preferences.d.mts +39 -39
  95. package/dist/server/modules/admin/collections/admin-saved-views.d.mts +47 -47
  96. package/dist/server/modules/admin/collections/apikey.d.mts +39 -39
  97. package/dist/server/modules/admin/collections/assets.d.mts +39 -39
  98. package/dist/server/modules/admin/collections/session.d.mts +42 -42
  99. package/dist/server/modules/admin/collections/user.d.mts +63 -63
  100. package/dist/server/modules/admin/collections/verification.d.mts +36 -36
  101. package/dist/server/modules/admin/dto/admin-config.dto.mjs +17 -0
  102. package/dist/server/modules/admin/index.d.mts +30 -31
  103. package/dist/server/modules/admin/routes/admin-config.d.mts +2 -17
  104. package/dist/server/modules/admin/routes/admin-config.mjs +21 -5
  105. package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
  106. package/dist/server/modules/admin/routes/execute-action.mjs +18 -12
  107. package/dist/server/modules/admin/routes/i18n-helpers.d.mts +4 -0
  108. package/dist/server/modules/admin/routes/locales.d.mts +2 -2
  109. package/dist/server/modules/admin/routes/preview.d.mts +24 -19
  110. package/dist/server/modules/admin/routes/preview.mjs +83 -62
  111. package/dist/server/modules/admin/routes/reactive.d.mts +9 -9
  112. package/dist/server/modules/admin/routes/route-helpers.mjs +36 -1
  113. package/dist/server/modules/admin/routes/setup.d.mts +7 -14
  114. package/dist/server/modules/admin/routes/setup.mjs +16 -3
  115. package/dist/server/modules/admin/routes/translations.d.mts +4 -4
  116. package/dist/server/modules/admin/routes/widget-data.d.mts +5 -5
  117. package/dist/server/modules/admin/views/list-view.d.mts +8 -0
  118. package/dist/server/modules/admin/views/list-view.mjs +7 -0
  119. package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +41 -41
  120. package/dist/server/modules/audit/.generated/module.d.mts +6 -6
  121. package/dist/server/modules/audit/collections/audit-log.d.mts +87 -80
  122. package/dist/server/modules/audit/collections/audit-log.mjs +7 -2
  123. package/dist/server/modules/audit/config/localize-title.mjs +67 -0
  124. package/dist/server/modules/audit/index.d.mts +3 -2
  125. package/dist/server/modules/audit/jobs/audit-cleanup.d.mts +2 -2
  126. package/dist/server/modules/audit/log-audit-entry.d.mts +85 -0
  127. package/dist/server/modules/audit/log-audit-entry.mjs +125 -0
  128. package/dist/server/plugin.d.mts +1 -1
  129. package/dist/server/plugin.mjs +31 -31
  130. package/dist/server.d.mts +6 -4
  131. package/dist/server.mjs +9 -8
  132. package/dist/shared/preview-utils.d.mts +4 -4
  133. package/dist/shared/preview-utils.mjs +5 -7
  134. package/package.json +13 -3
  135. package/dist/client/hooks/use-audit-history.mjs +0 -38
@@ -0,0 +1,830 @@
1
+ "use client";
2
+
3
+ import { useResolveText, useTranslation } from "../../i18n/hooks.mjs";
4
+ import { cn } from "../../lib/utils.mjs";
5
+ import { selectRealtime, useAdminStore } from "../../runtime/provider.mjs";
6
+ import { useSafeContentLocales } from "../../runtime/content-locales-provider.mjs";
7
+ import { useScopedLocale } from "../../runtime/locale-scope.mjs";
8
+ import { flattenOptions } from "../../components/primitives/types.mjs";
9
+ import { Button } from "../../components/ui/button.mjs";
10
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../components/ui/select.mjs";
11
+ import { LocaleSwitcher } from "../../components/locale-switcher.mjs";
12
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../../components/ui/tooltip.mjs";
13
+ import { Checkbox } from "../../components/ui/checkbox.mjs";
14
+ import { ActionButton } from "../../components/actions/action-button.mjs";
15
+ import { useCollectionFields } from "../../hooks/use-collection-fields.mjs";
16
+ import { useSuspenseCollectionMeta } from "../../hooks/use-collection-meta.mjs";
17
+ import { ActionDialog } from "../../components/actions/action-dialog.mjs";
18
+ import { EmptyState } from "../../components/ui/empty-state.mjs";
19
+ import { useSidebarSearchParam } from "../../hooks/use-sidebar-search-param.mjs";
20
+ import { useQuestpieQueryOptions } from "../../hooks/use-questpie-query-options.mjs";
21
+ import { useCollectionDelete, useCollectionList, useCollectionRestore } from "../../hooks/use-collection.mjs";
22
+ import { useSessionState } from "../../hooks/use-current-user.mjs";
23
+ import { getLockUser, useLocks } from "../../hooks/use-locks.mjs";
24
+ import { mergeServerActions, useServerActions } from "../../hooks/use-server-actions.mjs";
25
+ import { AdminViewHeader, AdminViewLayout } from "../layout/admin-view-layout.mjs";
26
+ import { TableViewSkeleton } from "./view-skeletons.mjs";
27
+ import { useUploadCollection } from "../../hooks/use-upload-collection.mjs";
28
+ import { useDebouncedValue, useSearch } from "../../hooks/use-search.mjs";
29
+ import { autoExpandFields, hasFieldsToExpand } from "../../utils/auto-expand-fields.mjs";
30
+ import { computeDefaultColumns, getAllAvailableFields } from "./columns/column-defaults.mjs";
31
+ import { buildColumns } from "./columns/build-columns.mjs";
32
+ import { HeaderActions } from "../../components/actions/header-actions.mjs";
33
+ import { FilterBuilderSheet } from "../../components/filter-builder/filter-builder-sheet.mjs";
34
+ import { SearchInput } from "../../components/ui/search-input.mjs";
35
+ import { useActions } from "../../hooks/use-action.mjs";
36
+ import { useRealtimeHighlight } from "../../hooks/use-realtime-highlight.mjs";
37
+ import { useDeleteSavedView, useSaveView, useSavedViews } from "../../hooks/use-saved-views.mjs";
38
+ import { useViewState } from "../../hooks/use-view-state.mjs";
39
+ import { BulkActionToolbar } from "./bulk-action-toolbar.mjs";
40
+ import { buildOutlineRows } from "./outline.mjs";
41
+ import { UploadCollectionButton, mapListSchemaToConfig, stringifyGroupValue } from "./table-view.mjs";
42
+ import { Icon } from "@iconify/react";
43
+ import * as React from "react";
44
+ import { useQueries } from "@tanstack/react-query";
45
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
46
+ import { flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
47
+
48
+ //#region src/client/views/collection/list-view.tsx
49
+ function normalizeFieldList(value) {
50
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
51
+ }
52
+ function getColumnKey(column) {
53
+ return column.accessorKey ?? column.id;
54
+ }
55
+ function getValueAtPath(source, path) {
56
+ if (!path) return source;
57
+ let current = source;
58
+ for (const part of path.split(".").filter(Boolean)) {
59
+ if (!current || typeof current !== "object") return void 0;
60
+ current = current[part];
61
+ }
62
+ return current;
63
+ }
64
+ function extractRelationNamesFromOutline(outline) {
65
+ const names = /* @__PURE__ */ new Set();
66
+ for (const level of outline?.levels ?? []) if (level.kind === "relation-field") names.add(level.relation);
67
+ return [...names];
68
+ }
69
+ function extractEdgeLevels(outline) {
70
+ return (outline?.levels ?? []).filter((level) => level.kind === "edge");
71
+ }
72
+ function buildFilterWhere({ filters, resolvedFields, relationNames }) {
73
+ if (filters.length === 0) return void 0;
74
+ const whereConditions = {};
75
+ const isEmptyValue = (val) => {
76
+ if (val === void 0 || val === null) return true;
77
+ if (typeof val === "string") return val.trim().length === 0;
78
+ if (Array.isArray(val)) return val.length === 0;
79
+ return false;
80
+ };
81
+ const normalizeSelectValue = (val, fieldOptions) => {
82
+ const optionsList = fieldOptions?.options;
83
+ if (!optionsList) return val;
84
+ const map = new Map(flattenOptions(optionsList).map((opt) => [String(opt.value), opt.value]));
85
+ const mapValue = (item) => map.get(String(item)) ?? item;
86
+ if (Array.isArray(val)) return val.map(mapValue);
87
+ if (val === void 0 || val === null) return val;
88
+ return mapValue(val);
89
+ };
90
+ const coerceValue = (val, fieldDef) => {
91
+ if (!fieldDef) return val;
92
+ const fieldType = fieldDef?.name ?? "text";
93
+ const fieldOptions = fieldDef?.["~options"] ?? {};
94
+ if (fieldType === "number" && typeof val === "string") {
95
+ const parsed = Number(val);
96
+ return Number.isNaN(parsed) ? val : parsed;
97
+ }
98
+ if ((fieldType === "checkbox" || fieldType === "switch") && typeof val === "string") {
99
+ if (val === "true") return true;
100
+ if (val === "false") return false;
101
+ }
102
+ if (fieldType === "select") return normalizeSelectValue(val, fieldOptions);
103
+ return val;
104
+ };
105
+ const toArray = (val) => {
106
+ if (Array.isArray(val)) return val;
107
+ if (val === void 0 || val === null || val === "") return [];
108
+ return [val];
109
+ };
110
+ const buildRelationCondition = (operator, val, relationType) => {
111
+ const isMultiple = relationType === "multiple";
112
+ const ids = toArray(val);
113
+ switch (operator) {
114
+ case "equals": return isMultiple ? { some: { id: val } } : { is: { id: val } };
115
+ case "not_equals": return isMultiple ? { none: { id: val } } : { isNot: { id: val } };
116
+ case "in": return isMultiple ? { some: { id: { in: ids } } } : { is: { id: { in: ids } } };
117
+ case "not_in": return isMultiple ? { none: { id: { in: ids } } } : { isNot: { id: { in: ids } } };
118
+ case "some": return { some: { id: { in: ids } } };
119
+ case "every": return { every: { id: { in: ids } } };
120
+ case "none": return { none: { id: { in: ids } } };
121
+ case "is_empty": return isMultiple ? { none: {} } : { isNot: {} };
122
+ case "is_not_empty": return isMultiple ? { some: {} } : { is: {} };
123
+ default: return;
124
+ }
125
+ };
126
+ for (const filter of filters) {
127
+ const { field, operator, value } = filter;
128
+ if (!field || field === "_title") continue;
129
+ const fieldDef = resolvedFields?.[field];
130
+ const fieldType = fieldDef?.name ?? "text";
131
+ const fieldOptions = fieldDef?.["~options"] ?? {};
132
+ const relationName = fieldType === "relation" ? fieldOptions.relationName ?? field : void 0;
133
+ const hasRelation = relationName && (relationNames.length === 0 || relationNames.includes(relationName));
134
+ const isRelationField = fieldType === "relation" && !!hasRelation;
135
+ if (operator !== "is_empty" && operator !== "is_not_empty" && isEmptyValue(value)) continue;
136
+ const normalizedValue = coerceValue(value, fieldDef);
137
+ if (isRelationField && relationName) {
138
+ const condition = buildRelationCondition(operator, normalizedValue, fieldOptions.type === "multiple" ? "multiple" : "single");
139
+ if (condition) whereConditions[relationName] = condition;
140
+ continue;
141
+ }
142
+ switch (operator) {
143
+ case "equals":
144
+ whereConditions[field] = normalizedValue;
145
+ break;
146
+ case "not_equals":
147
+ whereConditions[field] = { ne: normalizedValue };
148
+ break;
149
+ case "contains":
150
+ whereConditions[field] = { contains: normalizedValue };
151
+ break;
152
+ case "not_contains":
153
+ whereConditions[field] = { notIlike: `%${normalizedValue}%` };
154
+ break;
155
+ case "starts_with":
156
+ whereConditions[field] = { startsWith: normalizedValue };
157
+ break;
158
+ case "ends_with":
159
+ whereConditions[field] = { endsWith: normalizedValue };
160
+ break;
161
+ case "greater_than":
162
+ whereConditions[field] = { gt: normalizedValue };
163
+ break;
164
+ case "less_than":
165
+ whereConditions[field] = { lt: normalizedValue };
166
+ break;
167
+ case "greater_than_or_equal":
168
+ whereConditions[field] = { gte: normalizedValue };
169
+ break;
170
+ case "less_than_or_equal":
171
+ whereConditions[field] = { lte: normalizedValue };
172
+ break;
173
+ case "in":
174
+ whereConditions[field] = { in: Array.isArray(normalizedValue) ? normalizedValue : [normalizedValue] };
175
+ break;
176
+ case "not_in":
177
+ whereConditions[field] = { notIn: Array.isArray(normalizedValue) ? normalizedValue : [normalizedValue] };
178
+ break;
179
+ case "is_empty":
180
+ whereConditions[field] = { isNull: true };
181
+ break;
182
+ case "is_not_empty":
183
+ whereConditions[field] = { isNotNull: true };
184
+ break;
185
+ }
186
+ }
187
+ return Object.keys(whereConditions).length ? whereConditions : void 0;
188
+ }
189
+ function SimpleValue({ value }) {
190
+ if (value === null || value === void 0 || value === "") return /* @__PURE__ */ jsx("span", {
191
+ className: "text-muted-foreground",
192
+ children: "-"
193
+ });
194
+ if (Array.isArray(value)) return /* @__PURE__ */ jsx(Fragment, { children: value.map((item) => stringifySimpleValue(item)).join(", ") });
195
+ return /* @__PURE__ */ jsx(Fragment, { children: stringifySimpleValue(value) });
196
+ }
197
+ function stringifySimpleValue(value) {
198
+ if (value === null || value === void 0 || value === "") return "-";
199
+ if (typeof value === "object") {
200
+ const record = value;
201
+ return String(record.title ?? record.name ?? record.label ?? record.id ?? "-");
202
+ }
203
+ return String(value);
204
+ }
205
+ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/admin", showSearch = true, showFilters = true, showToolbar = true, realtime, headerActions, actionsConfig }) {
206
+ "use no memo";
207
+ const globalRealtimeConfig = useAdminStore(selectRealtime);
208
+ const { fields: resolvedFields, schema } = useCollectionFields(collection, { fallbackFields: config?.fields });
209
+ const { collections: uploadCollections } = useUploadCollection();
210
+ const schemaListConfig = mapListSchemaToConfig(schema?.admin?.list);
211
+ const resolvedListConfig = viewConfig ?? (config?.list)?.["~config"] ?? config?.list ?? schemaListConfig;
212
+ const resolvedRealtime = realtime ?? resolvedListConfig?.realtime ?? globalRealtimeConfig.enabled;
213
+ const { user } = useSessionState();
214
+ const { data: collectionMeta } = useSuspenseCollectionMeta(collection);
215
+ const { t, locale: uiLocale } = useTranslation();
216
+ const resolveText = useResolveText();
217
+ const { locale: contentLocale, setLocale: setContentLocale } = useScopedLocale();
218
+ const localeOptions = useSafeContentLocales()?.locales ?? [];
219
+ const { queryOpts, locale } = useQuestpieQueryOptions();
220
+ const rawActionsConfig = actionsConfig ?? resolvedListConfig?.actions;
221
+ const { serverActions } = useServerActions({ collection });
222
+ const { helpers: actionHelpers, actions, dialogAction, dialogItem, openDialog, closeDialog } = useActions({
223
+ collection,
224
+ actionsConfig: React.useMemo(() => mergeServerActions(rawActionsConfig ?? {}, serverActions), [rawActionsConfig, serverActions])
225
+ });
226
+ const canUploadToCollection = uploadCollections.includes(collection) && schema?.access?.operations?.create?.allowed === true;
227
+ const columns = React.useMemo(() => buildColumns({
228
+ config: {
229
+ fields: resolvedFields,
230
+ list: resolvedListConfig
231
+ },
232
+ fallbackColumns: ["id"],
233
+ buildAllColumns: true,
234
+ meta: collectionMeta
235
+ }), [
236
+ resolvedFields,
237
+ resolvedListConfig,
238
+ collectionMeta
239
+ ]);
240
+ const columnsByKey = React.useMemo(() => {
241
+ const map = /* @__PURE__ */ new Map();
242
+ for (const column of columns) {
243
+ const key = getColumnKey(column);
244
+ if (key) map.set(key, column);
245
+ }
246
+ return map;
247
+ }, [columns]);
248
+ const [isSheetOpen, setIsSheetOpen] = useSidebarSearchParam("view-options", { legacyKey: "viewOptions" });
249
+ const [searchTerm, setSearchTerm] = React.useState("");
250
+ const [isSearchPanelOpen, setIsSearchPanelOpen] = React.useState(false);
251
+ const [collapsedOutlineKeys, setCollapsedOutlineKeys] = React.useState(() => /* @__PURE__ */ new Set());
252
+ const [rowSelection, setRowSelection] = React.useState({});
253
+ const defaultColumns = React.useMemo(() => computeDefaultColumns(resolvedFields, {
254
+ meta: collectionMeta,
255
+ configuredColumns: resolvedListConfig?.columns
256
+ }), [
257
+ resolvedFields,
258
+ resolvedListConfig?.columns,
259
+ collectionMeta
260
+ ]);
261
+ const groupingConfig = resolvedListConfig?.grouping;
262
+ const defaultGroupBy = groupingConfig?.defaultField ?? null;
263
+ const viewState = useViewState(defaultColumns, {
264
+ realtime: resolvedRealtime,
265
+ groupBy: defaultGroupBy
266
+ }, collection, user?.id);
267
+ const effectiveRealtime = viewState.config.realtime ?? resolvedRealtime;
268
+ const visibleColumns = viewState.config.visibleColumns.length > 0 ? viewState.config.visibleColumns : defaultColumns;
269
+ const outlineRelationNames = React.useMemo(() => extractRelationNamesFromOutline(resolvedListConfig?.outline), [resolvedListConfig?.outline]);
270
+ const visibleColumnsForExpansion = React.useMemo(() => Array.from(new Set([...visibleColumns, ...outlineRelationNames])), [visibleColumns, outlineRelationNames]);
271
+ const expandedFields = React.useMemo(() => autoExpandFields({
272
+ fields: resolvedFields,
273
+ list: resolvedListConfig,
274
+ visibleColumns: visibleColumnsForExpansion,
275
+ relations: collectionMeta?.relations
276
+ }), [
277
+ resolvedFields,
278
+ resolvedListConfig,
279
+ visibleColumnsForExpansion,
280
+ collectionMeta?.relations
281
+ ]);
282
+ const isKnownSortField = React.useCallback((field) => !!field && (field === "_title" || !!resolvedFields?.[field]), [resolvedFields]);
283
+ const effectiveSort = React.useMemo(() => {
284
+ if (isKnownSortField(viewState.config.sortConfig?.field)) return viewState.config.sortConfig;
285
+ if (isKnownSortField(resolvedListConfig?.defaultSort?.field)) return resolvedListConfig.defaultSort;
286
+ return {
287
+ field: "createdAt",
288
+ direction: "desc"
289
+ };
290
+ }, [
291
+ viewState.config.sortConfig,
292
+ resolvedListConfig?.defaultSort,
293
+ isKnownSortField
294
+ ]);
295
+ const queryOptions = React.useMemo(() => {
296
+ const options = {};
297
+ if (collectionMeta?.softDelete) options.includeDeleted = !!viewState.config.includeDeleted;
298
+ if (hasFieldsToExpand(expandedFields)) options.with = expandedFields;
299
+ const where = buildFilterWhere({
300
+ filters: viewState.config.filters,
301
+ resolvedFields,
302
+ relationNames: collectionMeta?.relations ?? []
303
+ });
304
+ if (where) options.where = where;
305
+ const groupBy = viewState.config.groupBy;
306
+ const sortConfig = effectiveSort;
307
+ if (groupBy && sortConfig?.field && sortConfig.field !== groupBy) options.orderBy = [{ [groupBy]: "asc" }, { [sortConfig.field]: sortConfig.direction }];
308
+ else if (groupBy) options.orderBy = { [groupBy]: sortConfig?.direction ?? "asc" };
309
+ else if (sortConfig) options.orderBy = { [sortConfig.field]: sortConfig.direction };
310
+ const pageSize = viewState.config.pagination?.pageSize ?? 25;
311
+ const page = viewState.config.pagination?.page ?? 1;
312
+ options.limit = pageSize;
313
+ options.offset = (page - 1) * pageSize;
314
+ return options;
315
+ }, [
316
+ collectionMeta?.softDelete,
317
+ collectionMeta?.relations,
318
+ viewState.config.includeDeleted,
319
+ viewState.config.filters,
320
+ viewState.config.groupBy,
321
+ viewState.config.pagination?.page,
322
+ viewState.config.pagination?.pageSize,
323
+ expandedFields,
324
+ effectiveSort,
325
+ resolvedFields
326
+ ]);
327
+ const debouncedSearchTerm = useDebouncedValue(searchTerm, 300);
328
+ const isSearching = debouncedSearchTerm.trim().length > 0;
329
+ const { data: searchData, isLoading: searchLoading, isFetching: searchFetching } = useSearch({
330
+ collection,
331
+ query: debouncedSearchTerm,
332
+ limit: 100,
333
+ highlights: true
334
+ }, { enabled: isSearching });
335
+ const { data: listData, isLoading: listLoading, error: listError } = useCollectionList(collection, queryOptions, { enabled: !isSearching }, { realtime: effectiveRealtime });
336
+ const items = React.useMemo(() => isSearching ? searchData?.docs ?? [] : listData?.docs ?? [], [
337
+ isSearching,
338
+ searchData?.docs,
339
+ listData?.docs
340
+ ]);
341
+ const isLoading = isSearching ? searchLoading : listLoading;
342
+ const isSearchActive = isSearching && searchFetching;
343
+ const { isHighlighted } = useRealtimeHighlight(items, { enabled: effectiveRealtime && !isSearching });
344
+ const { getLock, isLocked: isDocLocked } = useLocks({
345
+ resourceType: "collection",
346
+ resource: collection,
347
+ realtime: effectiveRealtime
348
+ });
349
+ const edgeLevels = React.useMemo(() => extractEdgeLevels(resolvedListConfig?.outline), [resolvedListConfig?.outline]);
350
+ const edgeQueries = useQueries({ queries: edgeLevels.map((level) => {
351
+ const collectionQueries = queryOpts.collections?.[level.collection];
352
+ if (!collectionQueries?.find) return {
353
+ queryKey: [
354
+ "questpie",
355
+ "outline-edge-missing",
356
+ level.collection
357
+ ],
358
+ queryFn: async () => ({ docs: [] }),
359
+ enabled: false
360
+ };
361
+ return collectionQueries.find({
362
+ where: level.where,
363
+ with: {
364
+ [level.parentField]: true,
365
+ [level.childField]: true
366
+ },
367
+ limit: 1e3,
368
+ locale
369
+ });
370
+ }) });
371
+ const edgesByCollection = React.useMemo(() => {
372
+ const map = {};
373
+ edgeLevels.forEach((level, index) => {
374
+ const docs = (edgeQueries[index]?.data)?.docs ?? [];
375
+ map[level.collection] = [...map[level.collection] ?? [], ...docs];
376
+ });
377
+ return map;
378
+ }, [edgeLevels, edgeQueries]);
379
+ const effectiveOutline = React.useMemo(() => {
380
+ const outline = resolvedListConfig?.outline;
381
+ const groupBy = viewState.config.groupBy;
382
+ if (!groupBy) return outline;
383
+ const levels = outline?.levels ?? [];
384
+ const first = levels[0];
385
+ if (first?.kind === "field" && first.field === groupBy) return outline;
386
+ return {
387
+ ...outline,
388
+ defaultExpanded: outline?.defaultExpanded ?? true,
389
+ levels: [{
390
+ kind: "field",
391
+ field: groupBy
392
+ }, ...levels]
393
+ };
394
+ }, [resolvedListConfig?.outline, viewState.config.groupBy]);
395
+ const availableFields = React.useMemo(() => getAllAvailableFields(resolvedFields, { meta: collectionMeta }), [resolvedFields, collectionMeta]);
396
+ const fieldByName = React.useMemo(() => new Map(availableFields.map((field) => [field.name, field])), [availableFields]);
397
+ const outlineRows = React.useMemo(() => buildOutlineRows({
398
+ docs: items,
399
+ outline: effectiveOutline,
400
+ edgesByCollection,
401
+ collapsedKeys: collapsedOutlineKeys,
402
+ labelForValue: (value, field) => stringifyGroupValue(value, field ? fieldByName.get(field) : void 0, resolveText, t, uiLocale, t("common.noValue"))
403
+ }), [
404
+ items,
405
+ effectiveOutline,
406
+ edgesByCollection,
407
+ collapsedOutlineKeys,
408
+ fieldByName,
409
+ resolveText,
410
+ t,
411
+ uiLocale
412
+ ]);
413
+ const table = useReactTable({
414
+ data: items,
415
+ columns,
416
+ getCoreRowModel: getCoreRowModel(),
417
+ enableRowSelection: true,
418
+ onRowSelectionChange: setRowSelection,
419
+ getRowId: (row) => String(row.id),
420
+ state: { rowSelection }
421
+ });
422
+ const rowsById = React.useMemo(() => new Map(table.getRowModel().rows.map((row) => [String(row.id), row])), [table, items]);
423
+ const deleteMutation = useCollectionDelete(collection);
424
+ const restoreMutation = useCollectionRestore(collection);
425
+ const { data: savedViewsData, isLoading: savedViewsLoading } = useSavedViews(collection, user?.id);
426
+ const saveViewMutation = useSaveView(collection, user?.id);
427
+ const deleteViewMutation = useDeleteSavedView(collection, user?.id);
428
+ const hasActiveFilters = viewState.config.filters.length > 0;
429
+ const hasViewOptionsState = hasActiveFilters || !!viewState.config.groupBy || viewState.config.visibleColumns.length !== defaultColumns.length || !!viewState.config.includeDeleted;
430
+ const groupableFields = React.useMemo(() => {
431
+ const groupableNames = groupingConfig?.fields ?? [];
432
+ if (groupableNames.length === 0) return [];
433
+ const groupableSet = new Set(groupableNames);
434
+ return availableFields.filter((field) => groupableSet.has(field.name));
435
+ }, [availableFields, groupingConfig?.fields]);
436
+ const layout = resolvedListConfig?.layout;
437
+ const titleField = layout?.titleField ?? (collectionMeta?.title?.type === "field" ? collectionMeta.title.fieldName : void 0);
438
+ const subtitleField = layout?.subtitleField;
439
+ const leadingFields = normalizeFieldList(layout?.leadingFields);
440
+ const badgeFields = normalizeFieldList(layout?.badgeFields);
441
+ const metaFields = normalizeFieldList(layout?.metaFields);
442
+ const density = layout?.density ?? "compact";
443
+ const clearFilters = React.useCallback(() => {
444
+ viewState.setConfig({
445
+ ...viewState.config,
446
+ filters: []
447
+ });
448
+ }, [viewState]);
449
+ const handleSaveView = React.useCallback((name, configuration) => {
450
+ saveViewMutation.mutate({
451
+ name,
452
+ configuration
453
+ });
454
+ }, [saveViewMutation]);
455
+ const handleBulkDelete = React.useCallback(async (ids) => {
456
+ await Promise.allSettled(ids.map((id) => deleteMutation.mutateAsync({ id })));
457
+ actionHelpers.invalidateCollection(collection);
458
+ }, [
459
+ deleteMutation,
460
+ actionHelpers,
461
+ collection
462
+ ]);
463
+ const handleBulkRestore = React.useCallback(async (ids) => {
464
+ await Promise.allSettled(ids.map((id) => restoreMutation.mutateAsync({ id })));
465
+ actionHelpers.invalidateCollection(collection);
466
+ }, [
467
+ restoreMutation,
468
+ actionHelpers,
469
+ collection
470
+ ]);
471
+ const toggleOutlineKey = React.useCallback((key) => {
472
+ setCollapsedOutlineKeys((current) => {
473
+ const next = new Set(current);
474
+ if (next.has(key)) next.delete(key);
475
+ else next.add(key);
476
+ return next;
477
+ });
478
+ }, []);
479
+ const renderField = React.useCallback((row, field, fallback) => {
480
+ const column = columnsByKey.get(field);
481
+ const cell = row?.getAllCells?.().find((candidate) => {
482
+ return getColumnKey(candidate.column.columnDef) === field || candidate.column.id === field;
483
+ });
484
+ if (cell && column) return flexRender(cell.column.columnDef.cell, cell.getContext());
485
+ return /* @__PURE__ */ jsx(SimpleValue, { value: fallback ?? getValueAtPath(row?.original, field) });
486
+ }, [columnsByKey]);
487
+ const handleRowClick = React.useCallback((item) => navigate(`${basePath}/collections/${collection}/${item.id}`), [
488
+ navigate,
489
+ basePath,
490
+ collection
491
+ ]);
492
+ if (listError && !isSearching) {
493
+ const errorMessage = listError instanceof Error ? listError.message : void 0;
494
+ return /* @__PURE__ */ jsx("div", {
495
+ className: "container",
496
+ children: /* @__PURE__ */ jsx(EmptyState, {
497
+ variant: "error",
498
+ iconName: "ph:warning-circle",
499
+ title: t("error.failedToLoad"),
500
+ description: errorMessage,
501
+ height: "h-64",
502
+ action: /* @__PURE__ */ jsxs(Button, {
503
+ variant: "outline",
504
+ size: "sm",
505
+ className: "gap-2",
506
+ onClick: () => window.location.reload(),
507
+ children: [/* @__PURE__ */ jsx(Icon, {
508
+ icon: "ph:arrow-clockwise",
509
+ className: "size-3.5"
510
+ }), t("common.retry")]
511
+ })
512
+ })
513
+ });
514
+ }
515
+ if (isLoading) return /* @__PURE__ */ jsx(TableViewSkeleton, {});
516
+ const emptyStateTitle = isSearching || hasActiveFilters ? t("collectionSearch.noResults") : t("table.noItemsInCollection");
517
+ const emptyStateDescription = isSearching ? t("collectionSearch.noResultsDescription") : hasActiveFilters ? t("viewOptions.noResultsDescription") : t("table.emptyDescription");
518
+ return /* @__PURE__ */ jsx(AdminViewLayout, {
519
+ header: /* @__PURE__ */ jsx(AdminViewHeader, {
520
+ title: resolveText(config?.label ?? schema?.admin?.config?.label, collection),
521
+ titleAccessory: localeOptions.length > 0 ? /* @__PURE__ */ jsx(LocaleSwitcher, {
522
+ locales: localeOptions,
523
+ value: contentLocale,
524
+ onChange: setContentLocale
525
+ }) : void 0,
526
+ description: resolveText(config?.description ?? schema?.admin?.config?.description),
527
+ actions: /* @__PURE__ */ jsxs(Fragment, { children: [
528
+ showSearch && /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, { render: /* @__PURE__ */ jsxs(Button, {
529
+ variant: "outline",
530
+ size: "icon-sm",
531
+ className: "relative",
532
+ onClick: () => setIsSearchPanelOpen((open) => !open),
533
+ "aria-label": t("common.search"),
534
+ 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" })]
535
+ }) }), /* @__PURE__ */ jsx(TooltipContent, {
536
+ side: "bottom",
537
+ align: "end",
538
+ children: t("common.search")
539
+ })] }),
540
+ showFilters && /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, { render: /* @__PURE__ */ jsxs(Button, {
541
+ variant: "outline",
542
+ size: "icon-sm",
543
+ className: cn("relative", hasViewOptionsState && "border-foreground"),
544
+ onClick: () => setIsSheetOpen(true),
545
+ "aria-label": t("viewOptions.title"),
546
+ 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" })]
547
+ }) }), /* @__PURE__ */ jsx(TooltipContent, {
548
+ side: "bottom",
549
+ align: "end",
550
+ children: t("viewOptions.title")
551
+ })] }),
552
+ /* @__PURE__ */ jsxs(Select, {
553
+ value: effectiveSort?.field ?? "",
554
+ onValueChange: (field) => viewState.setSort(field ? {
555
+ field,
556
+ direction: effectiveSort?.direction ?? "asc"
557
+ } : null),
558
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
559
+ className: "h-8 w-40",
560
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Sort" })
561
+ }), /* @__PURE__ */ jsx(SelectContent, { children: availableFields.map((field) => /* @__PURE__ */ jsx(SelectItem, {
562
+ value: field.name,
563
+ children: resolveText(field.label, field.name)
564
+ }, field.name)) })]
565
+ }),
566
+ /* @__PURE__ */ jsx(Button, {
567
+ variant: "outline",
568
+ size: "icon-sm",
569
+ "aria-label": "Toggle sort direction",
570
+ onClick: () => viewState.setSort({
571
+ field: effectiveSort?.field ?? "createdAt",
572
+ direction: effectiveSort?.direction === "asc" ? "desc" : "asc"
573
+ }),
574
+ children: /* @__PURE__ */ jsx(Icon, { icon: effectiveSort?.direction === "asc" ? "ph:sort-ascending" : "ph:sort-descending" })
575
+ }),
576
+ canUploadToCollection && showToolbar && /* @__PURE__ */ jsx(UploadCollectionButton, {
577
+ collection,
578
+ onUploaded: () => actionHelpers.invalidateCollection(collection)
579
+ }),
580
+ /* @__PURE__ */ jsx(HeaderActions, {
581
+ actions: actions.header,
582
+ collection,
583
+ helpers: actionHelpers,
584
+ onOpenDialog: (action) => openDialog(action)
585
+ }),
586
+ headerActions
587
+ ] })
588
+ }),
589
+ children: /* @__PURE__ */ jsxs("div", {
590
+ className: "space-y-3",
591
+ children: [
592
+ showSearch && isSearchPanelOpen && /* @__PURE__ */ jsx("div", {
593
+ className: "border-border-subtle bg-muted/20 rounded-md border p-3",
594
+ children: /* @__PURE__ */ jsx(SearchInput, {
595
+ value: searchTerm,
596
+ onChange: (event) => setSearchTerm(event.target.value),
597
+ onClear: () => setSearchTerm(""),
598
+ placeholder: t("collectionSearch.placeholder"),
599
+ autoFocus: true,
600
+ isLoading: isSearchActive
601
+ })
602
+ }),
603
+ outlineRows.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
604
+ title: emptyStateTitle,
605
+ description: emptyStateDescription,
606
+ height: "h-64",
607
+ action: isSearching || hasActiveFilters ? /* @__PURE__ */ jsxs("div", {
608
+ className: "flex gap-2",
609
+ children: [isSearching && /* @__PURE__ */ jsxs(Button, {
610
+ variant: "outline",
611
+ size: "sm",
612
+ className: "gap-2",
613
+ onClick: () => setSearchTerm(""),
614
+ children: [/* @__PURE__ */ jsx(Icon, {
615
+ icon: "ph:x",
616
+ className: "size-3.5"
617
+ }), t("common.clear")]
618
+ }), hasActiveFilters && /* @__PURE__ */ jsxs(Button, {
619
+ variant: "outline",
620
+ size: "sm",
621
+ className: "gap-2",
622
+ onClick: clearFilters,
623
+ children: [/* @__PURE__ */ jsx(Icon, {
624
+ icon: "ph:funnel-x",
625
+ className: "size-3.5"
626
+ }), t("viewOptions.clearFilters")]
627
+ })]
628
+ }) : void 0
629
+ }) : /* @__PURE__ */ jsx("div", {
630
+ className: "border-border-subtle overflow-hidden rounded-md border",
631
+ children: outlineRows.map((outlineRow) => {
632
+ if (outlineRow.kind !== "record") return /* @__PURE__ */ jsx(OutlineHeaderRow, {
633
+ row: outlineRow,
634
+ showCounts: resolvedListConfig?.outline?.showCounts ?? true,
635
+ onToggle: toggleOutlineKey
636
+ }, outlineRow.key);
637
+ const tableRow = rowsById.get(outlineRow.id);
638
+ if (!tableRow) return null;
639
+ const item = tableRow.original;
640
+ const lock = getLock(item.id);
641
+ const locked = isDocLocked(item.id);
642
+ const lockUser = lock ? getLockUser(lock) : null;
643
+ const isSelected = tableRow.getIsSelected();
644
+ const titleValue = (titleField ? getValueAtPath(item, titleField) : item._title) ?? item.title ?? item.name ?? item.id;
645
+ const subtitleValue = subtitleField ? getValueAtPath(item, subtitleField) : void 0;
646
+ return /* @__PURE__ */ jsxs("div", {
647
+ className: cn("group/list-row border-border-subtle hover:bg-muted/35 flex min-w-0 items-start gap-2 border-b px-3 text-sm transition-colors last:border-b-0", density === "compact" ? "py-2" : "py-3", isHighlighted(item.id) && "bg-info/10", isSelected && "bg-muted/50"),
648
+ style: { paddingLeft: `${12 + outlineRow.depth * 18}px` },
649
+ children: [
650
+ /* @__PURE__ */ jsx("div", {
651
+ role: "presentation",
652
+ className: "mt-0.5 shrink-0",
653
+ onClick: (event) => event.stopPropagation(),
654
+ onKeyDown: (event) => event.stopPropagation(),
655
+ children: /* @__PURE__ */ jsx(Checkbox, {
656
+ checked: isSelected,
657
+ disabled: !tableRow.getCanSelect(),
658
+ onCheckedChange: (checked) => tableRow.toggleSelected(!!checked),
659
+ "aria-label": "Select row"
660
+ })
661
+ }),
662
+ /* @__PURE__ */ jsx("button", {
663
+ type: "button",
664
+ className: cn("text-muted-foreground mt-0.5 flex size-5 shrink-0 items-center justify-center rounded transition-colors", outlineRow.expandable && "hover:bg-muted hover:text-foreground"),
665
+ disabled: !outlineRow.expandable,
666
+ onClick: (event) => {
667
+ event.stopPropagation();
668
+ if (outlineRow.expandable) toggleOutlineKey(outlineRow.key);
669
+ },
670
+ "aria-label": outlineRow.collapsed ? "Expand row" : "Collapse row",
671
+ children: outlineRow.expandable ? /* @__PURE__ */ jsx(Icon, {
672
+ icon: "ph:caret-right-bold",
673
+ className: cn("size-3 transition-transform", !outlineRow.collapsed && "rotate-90")
674
+ }) : null
675
+ }),
676
+ /* @__PURE__ */ jsxs("button", {
677
+ type: "button",
678
+ className: "min-w-0 flex-1 text-left",
679
+ onClick: () => handleRowClick(item),
680
+ children: [/* @__PURE__ */ jsxs("div", {
681
+ className: "flex min-w-0 items-center gap-2",
682
+ children: [
683
+ leadingFields.map((field) => /* @__PURE__ */ jsx("span", {
684
+ className: "shrink-0",
685
+ children: renderField(tableRow, field, getValueAtPath(item, field))
686
+ }, field)),
687
+ /* @__PURE__ */ jsx("span", {
688
+ className: "text-foreground truncate font-medium",
689
+ children: titleField ? renderField(tableRow, titleField, titleValue) : stringifySimpleValue(titleValue)
690
+ }),
691
+ badgeFields.map((field) => /* @__PURE__ */ jsx("span", {
692
+ className: "bg-muted text-muted-foreground inline-flex h-5 max-w-36 shrink-0 items-center rounded px-1.5 font-mono text-[11px]",
693
+ children: /* @__PURE__ */ jsx("span", {
694
+ className: "truncate",
695
+ children: renderField(tableRow, field, getValueAtPath(item, field))
696
+ })
697
+ }, field)),
698
+ locked && /* @__PURE__ */ jsxs("span", {
699
+ className: "text-warning inline-flex items-center gap-1 text-xs",
700
+ children: [/* @__PURE__ */ jsx(Icon, {
701
+ icon: "ph:lock-key",
702
+ className: "size-3"
703
+ }), lockUser?.name ?? t("collection.locked")]
704
+ })
705
+ ]
706
+ }), subtitleValue !== void 0 && /* @__PURE__ */ jsx("div", {
707
+ className: "text-muted-foreground mt-0.5 truncate text-xs",
708
+ children: renderField(tableRow, subtitleField, subtitleValue)
709
+ })]
710
+ }),
711
+ metaFields.length > 0 && /* @__PURE__ */ jsx("div", {
712
+ className: "text-muted-foreground hidden max-w-[42%] shrink-0 items-center justify-end gap-3 overflow-hidden text-right text-xs md:flex",
713
+ children: metaFields.map((field) => /* @__PURE__ */ jsx("span", {
714
+ className: "min-w-0 truncate",
715
+ children: renderField(tableRow, field, getValueAtPath(item, field))
716
+ }, field))
717
+ }),
718
+ actions.row.length > 0 && /* @__PURE__ */ jsx("div", {
719
+ role: "presentation",
720
+ className: "flex shrink-0 justify-end gap-1 opacity-0 transition-opacity group-hover/list-row:opacity-100 focus-within:opacity-100",
721
+ onClick: (event) => event.stopPropagation(),
722
+ onKeyDown: (event) => event.stopPropagation(),
723
+ children: actions.row.map((action) => /* @__PURE__ */ jsx(ActionButton, {
724
+ action,
725
+ collection,
726
+ item,
727
+ helpers: actionHelpers,
728
+ size: "icon-sm",
729
+ iconOnly: true,
730
+ onOpenDialog: (dialogAction$1) => openDialog(dialogAction$1, item)
731
+ }, action.id))
732
+ })
733
+ ]
734
+ }, outlineRow.key);
735
+ })
736
+ }),
737
+ /* @__PURE__ */ jsxs("div", {
738
+ className: "text-muted-foreground flex flex-col gap-2 text-xs sm:flex-row sm:items-center sm:justify-between",
739
+ children: [/* @__PURE__ */ jsx("span", { children: isSearching ? `${items.length} item${items.length === 1 ? "" : "s"}` : `${items.length > 0 ? ((viewState.config.pagination?.page ?? 1) - 1) * (viewState.config.pagination?.pageSize ?? 25) + 1 : 0}-${Math.min(((viewState.config.pagination?.page ?? 1) - 1) * (viewState.config.pagination?.pageSize ?? 25) + items.length, listData?.totalDocs ?? items.length)} ${t("table.of")} ${listData?.totalDocs ?? 0}` }), !isSearching && (listData?.totalPages ?? 1) > 1 && /* @__PURE__ */ jsxs("div", {
740
+ className: "flex items-center gap-2",
741
+ children: [/* @__PURE__ */ jsx(Button, {
742
+ variant: "outline",
743
+ size: "sm",
744
+ disabled: (viewState.config.pagination?.page ?? 1) <= 1,
745
+ onClick: () => viewState.setPage(Math.max(1, (viewState.config.pagination?.page ?? 1) - 1)),
746
+ children: t("common.previous")
747
+ }), /* @__PURE__ */ jsx(Button, {
748
+ variant: "outline",
749
+ size: "sm",
750
+ disabled: (viewState.config.pagination?.page ?? 1) >= (listData?.totalPages ?? 1),
751
+ onClick: () => viewState.setPage((viewState.config.pagination?.page ?? 1) + 1),
752
+ children: t("common.next")
753
+ })]
754
+ })]
755
+ }),
756
+ /* @__PURE__ */ jsx(BulkActionToolbar, {
757
+ table,
758
+ actions: actions.bulk,
759
+ collection,
760
+ helpers: actionHelpers,
761
+ totalCount: isSearching ? items.length : listData?.totalDocs,
762
+ pageCount: items.length,
763
+ onOpenDialog: (action, selectedItems) => openDialog(action, selectedItems),
764
+ onBulkDelete: handleBulkDelete,
765
+ onBulkRestore: handleBulkRestore,
766
+ filterCount: viewState.config.filters.length,
767
+ onClearFilters: clearFilters,
768
+ onOpenFilters: () => setIsSheetOpen(true)
769
+ }),
770
+ /* @__PURE__ */ jsx(FilterBuilderSheet, {
771
+ collection,
772
+ availableFields,
773
+ currentConfig: viewState.config,
774
+ onConfigChange: viewState.setConfig,
775
+ isOpen: isSheetOpen,
776
+ onOpenChange: setIsSheetOpen,
777
+ defaultColumns,
778
+ savedViews: savedViewsData?.docs ?? [],
779
+ savedViewsLoading,
780
+ onSaveView: handleSaveView,
781
+ onDeleteView: (viewId) => deleteViewMutation.mutate(viewId),
782
+ supportsSoftDelete: !!collectionMeta?.softDelete,
783
+ groupableFields,
784
+ defaultGroupBy
785
+ }),
786
+ dialogAction && /* @__PURE__ */ jsx(ActionDialog, {
787
+ open: !!dialogAction,
788
+ onOpenChange: (open) => !open && closeDialog(),
789
+ action: dialogAction,
790
+ collection,
791
+ item: dialogItem,
792
+ helpers: actionHelpers
793
+ })
794
+ ]
795
+ })
796
+ });
797
+ }
798
+ function OutlineHeaderRow({ row, showCounts, onToggle }) {
799
+ return /* @__PURE__ */ jsxs("button", {
800
+ type: "button",
801
+ className: "bg-background/95 border-border-subtle sticky top-0 z-10 flex w-full items-center gap-2 border-b px-3 py-2 text-left backdrop-blur",
802
+ style: { paddingLeft: `${12 + row.depth * 18}px` },
803
+ onClick: () => onToggle(row.key),
804
+ "aria-expanded": !row.collapsed,
805
+ children: [
806
+ /* @__PURE__ */ jsx(Icon, {
807
+ icon: "ph:caret-right-bold",
808
+ className: cn("text-muted-foreground size-3 transition-transform", !row.collapsed && "rotate-90")
809
+ }),
810
+ row.kind === "synthetic" && /* @__PURE__ */ jsx(Icon, {
811
+ icon: "ph:folder-simple",
812
+ className: "text-muted-foreground size-4"
813
+ }),
814
+ /* @__PURE__ */ jsx("span", {
815
+ className: "font-chrome chrome-meta text-muted-foreground truncate text-[11px] font-semibold tracking-[0.12em] uppercase",
816
+ children: row.label
817
+ }),
818
+ showCounts && /* @__PURE__ */ jsx("span", {
819
+ className: "font-chrome chrome-meta text-muted-foreground text-[11px] tabular-nums",
820
+ children: row.count
821
+ })
822
+ ]
823
+ });
824
+ }
825
+ function ListView(props) {
826
+ return /* @__PURE__ */ jsx(ListViewInner, { ...props });
827
+ }
828
+
829
+ //#endregion
830
+ export { ListView as default };