@questpie/admin 3.5.2 → 3.5.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.
- package/README.md +8 -0
- package/dist/client/blocks/block-renderer.d.mts +2 -2
- package/dist/client/builder/index.d.mts +1 -1
- package/dist/client/builder/types/collection-types.d.mts +89 -5
- package/dist/client/builder/types/common.d.mts +5 -0
- package/dist/client/builder/types/field-types.d.mts +41 -1
- package/dist/client/builder/view/view.d.mts +3 -2
- package/dist/client/components/actions/action-dialog.mjs +5 -0
- package/dist/client/components/admin-link.d.mts +2 -2
- package/dist/client/components/fields/boolean-field.mjs +2 -1
- package/dist/client/components/fields/date-field.mjs +2 -1
- package/dist/client/components/fields/datetime-field.mjs +2 -1
- package/dist/client/components/fields/email-field.mjs +2 -1
- package/dist/client/components/fields/field-utils.d.mts +11 -0
- package/dist/client/components/fields/field-utils.mjs +3 -1
- package/dist/client/components/fields/field-wrapper.mjs +3 -3
- package/dist/client/components/fields/number-field.mjs +2 -1
- package/dist/client/components/fields/object-field.mjs +2 -1
- package/dist/client/components/fields/relation/displays/types.mjs +3 -3
- package/dist/client/components/fields/rich-text-editor/bubble-menu.mjs +7 -0
- package/dist/client/components/fields/rich-text-editor/extensions.mjs +19 -2
- package/dist/client/components/fields/rich-text-editor/image-popover.mjs +6 -2
- package/dist/client/components/fields/rich-text-editor/image-upload.mjs +2 -1
- package/dist/client/components/fields/rich-text-editor/index.d.mts +5 -3
- package/dist/client/components/fields/rich-text-editor/index.mjs +38 -76
- package/dist/client/components/fields/rich-text-editor/slash-commands.mjs +30 -7
- package/dist/client/components/fields/rich-text-editor/toolbar.mjs +1 -312
- package/dist/client/components/fields/rich-text-editor/types.d.mts +4 -0
- package/dist/client/components/fields/rich-text-editor/types.mjs +1 -1
- package/dist/client/components/fields/rich-text-editor/utils.mjs +6 -12
- package/dist/client/components/fields/select-field.mjs +2 -1
- package/dist/client/components/fields/text-field.mjs +2 -1
- package/dist/client/components/fields/textarea-field.mjs +2 -1
- package/dist/client/components/fields/time-field.mjs +2 -1
- package/dist/client/components/filter-builder/filter-builder-sheet.mjs +75 -22
- package/dist/client/components/layout/field-layout-renderer.mjs +4 -4
- package/dist/client/components/media/media-grid.mjs +2 -1
- package/dist/client/components/primitives/asset-preview.mjs +4 -2
- package/dist/client/components/primitives/dropzone.d.mts +100 -0
- package/dist/client/components/primitives/field-select-control.mjs +2 -1
- package/dist/client/components/ui/button.d.mts +23 -0
- package/dist/client/components/ui/button.mjs +2 -2
- package/dist/client/components/ui/dropdown-menu.d.mts +49 -0
- package/dist/client/components/ui/dropdown-menu.mjs +7 -19
- package/dist/client/components/ui/popover.mjs +1 -1
- package/dist/client/components/ui/search-input.d.mts +56 -0
- package/dist/client/components/ui/select.mjs +2 -2
- package/dist/client/components/ui/sheet.d.mts +40 -0
- package/dist/client/components/ui/table.d.mts +49 -0
- package/dist/client/components/ui/table.mjs +15 -1
- package/dist/client/components/ui/tooltip.d.mts +21 -0
- package/dist/client/contexts/focus-context.d.mts +2 -2
- package/dist/client/hooks/query-access.d.mts +9 -0
- package/dist/client/hooks/query-access.mjs +20 -0
- package/dist/client/hooks/typed-hooks.d.mts +4 -2
- package/dist/client/hooks/typed-hooks.mjs +30 -29
- package/dist/client/hooks/use-admin-config.mjs +20 -1
- package/dist/client/hooks/use-autosave.mjs +91 -0
- package/dist/client/hooks/use-collection.mjs +65 -23
- package/dist/client/hooks/use-reactive-fields.d.mts +1 -0
- package/dist/client/hooks/use-reactive-fields.mjs +16 -1
- package/dist/client/hooks/use-server-actions.mjs +12 -1
- package/dist/client/hooks/use-upload.d.mts +40 -0
- package/dist/client/hooks/use-upload.mjs +4 -2
- package/dist/client/hooks/use-view-state.mjs +15 -7
- package/dist/client/i18n/hooks.d.mts +20 -0
- package/dist/client/lib/utils.d.mts +6 -0
- package/dist/client/lib/view-filter-utils.mjs +30 -0
- package/dist/client/preview/block-scope-context.d.mts +2 -2
- package/dist/client/preview/preview-banner.d.mts +2 -2
- package/dist/client/preview/preview-field.d.mts +4 -4
- package/dist/client/runtime/provider.mjs +22 -3
- package/dist/client/scope/picker.d.mts +2 -2
- package/dist/client/scope/provider.d.mts +2 -2
- package/dist/client/styles/base.css +75 -79
- package/dist/client/utils/asset-url.mjs +27 -0
- package/dist/client/utils/build-field-definitions-from-schema.mjs +1 -0
- package/dist/client/views/auth/accept-invite-form.d.mts +2 -2
- package/dist/client/views/auth/auth-layout.d.mts +3 -3
- package/dist/client/views/auth/forgot-password-form.d.mts +2 -2
- package/dist/client/views/auth/login-form.d.mts +2 -2
- package/dist/client/views/auth/reset-password-form.d.mts +2 -2
- package/dist/client/views/auth/setup-form.d.mts +2 -2
- package/dist/client/views/collection/auto-form-fields.mjs +7 -6
- package/dist/client/views/collection/cells/primitive-cells.mjs +9 -6
- package/dist/client/views/collection/cells/shared/asset-thumbnail.d.mts +7 -0
- package/dist/client/views/collection/cells/shared/asset-thumbnail.mjs +3 -2
- package/dist/client/views/collection/cells/shared/cell-helpers.mjs +3 -2
- package/dist/client/views/collection/cells/upload-cells.mjs +2 -1
- package/dist/client/views/collection/columns/build-columns.mjs +3 -1
- package/dist/client/views/collection/document-view.d.mts +30 -0
- package/dist/client/views/collection/document-view.mjs +377 -0
- package/dist/client/views/collection/field-context.mjs +3 -2
- package/dist/client/views/collection/field-renderer.mjs +13 -5
- package/dist/client/views/collection/form-view.mjs +221 -282
- package/dist/client/views/collection/list-view.mjs +592 -190
- package/dist/client/views/collection/outline.mjs +44 -19
- package/dist/client/views/collection/quick-filter-bar.mjs +45 -0
- package/dist/client/views/collection/table-view.mjs +61 -17
- package/dist/client/views/globals/global-form-view.mjs +12 -9
- package/dist/client/views/layout/admin-layout-provider.mjs +4 -3
- package/dist/client/views/layout/admin-layout.mjs +108 -21
- package/dist/client/views/layout/admin-router.mjs +19 -3
- package/dist/client/views/layout/admin-sidebar.mjs +70 -20
- package/dist/client/views/layout/admin-theme.mjs +5 -4
- package/dist/client/views/layout/admin-view-layout.d.mts +36 -0
- package/dist/client/views/pages/accept-invite-page.d.mts +2 -2
- package/dist/client/views/pages/dashboard-page.d.mts +2 -2
- package/dist/client/views/pages/forgot-password-page.d.mts +2 -2
- package/dist/client/views/pages/invite-page.d.mts +2 -2
- package/dist/client/views/pages/login-page.d.mts +2 -2
- package/dist/client/views/pages/reset-password-page.d.mts +2 -2
- package/dist/client/views/pages/setup-page.d.mts +2 -2
- package/dist/client.d.mts +17 -2
- package/dist/client.mjs +17 -2
- package/dist/components/rich-text/rich-text-renderer.d.mts +5 -5
- package/dist/components/rich-text/rich-text-renderer.mjs +5 -2
- package/dist/factories.d.mts +4 -2
- package/dist/factories.mjs +2 -2
- package/dist/index.d.mts +17 -3
- package/dist/index.mjs +17 -2
- package/dist/modules/admin.d.mts +1 -1
- package/dist/server/adapters/index.d.mts +2 -0
- package/dist/server/adapters/nextjs.d.mts +1 -0
- package/dist/server/augmentation/actions.d.mts +9 -3
- package/dist/server/augmentation/dashboard.d.mts +11 -11
- package/dist/server/augmentation/form-layout.d.mts +16 -6
- package/dist/server/augmentation/index.d.mts +7 -0
- package/dist/server/augmentation/sidebar.d.mts +8 -8
- package/dist/server/augmentation/views.d.mts +4 -1
- package/dist/server/auth-helpers.d.mts +1 -0
- package/dist/server/codegen/admin-client-template.mjs +7 -6
- package/dist/server/fields/blocks.mjs +4 -1
- package/dist/server/fields/index.d.mts +1 -1
- package/dist/server/fields/reactive-runtime.mjs +3 -0
- package/dist/server/fields/rich-text.d.mts +16 -17
- package/dist/server/fields/rich-text.mjs +18 -7
- package/dist/server/i18n/messages/cs.mjs +2 -0
- package/dist/server/i18n/messages/de.mjs +2 -0
- package/dist/server/i18n/messages/en.mjs +4 -0
- package/dist/server/i18n/messages/es.mjs +2 -0
- package/dist/server/i18n/messages/fr.mjs +2 -0
- package/dist/server/i18n/messages/pl.mjs +2 -0
- package/dist/server/i18n/messages/pt.mjs +2 -0
- package/dist/server/i18n/messages/sk.mjs +2 -0
- package/dist/server/modules/admin/.generated/module.d.mts +1 -1
- package/dist/server/modules/admin/auth-helpers.mjs +7 -1
- package/dist/server/modules/admin/block/block-builder.d.mts +0 -8
- package/dist/server/modules/admin/block/introspection.d.mts +2 -2
- package/dist/server/modules/admin/block/introspection.mjs +28 -4
- package/dist/server/modules/admin/block/prefetch.d.mts +11 -0
- package/dist/server/modules/admin/block/prefetch.mjs +108 -27
- package/dist/server/modules/admin/client/.generated/module.d.mts +68 -67
- package/dist/server/modules/admin/client/.generated/module.mjs +2 -0
- package/dist/server/modules/admin/client/views/collection-document.d.mts +6 -0
- package/dist/server/modules/admin/client/views/collection-document.mjs +10 -0
- package/dist/server/modules/admin/collections/account.d.mts +53 -52
- package/dist/server/modules/admin/collections/admin-locks.d.mts +57 -56
- package/dist/server/modules/admin/collections/admin-preferences.d.mts +38 -37
- package/dist/server/modules/admin/collections/admin-saved-views.d.mts +50 -49
- package/dist/server/modules/admin/collections/apikey.d.mts +76 -67
- package/dist/server/modules/admin/collections/assets.d.mts +37 -36
- package/dist/server/modules/admin/collections/session.d.mts +42 -41
- package/dist/server/modules/admin/collections/user.d.mts +57 -56
- package/dist/server/modules/admin/collections/verification.d.mts +34 -33
- package/dist/server/modules/admin/dto/admin-config.dto.mjs +34 -4
- package/dist/server/modules/admin/factories.mjs +4 -34
- package/dist/server/modules/admin/index.d.mts +3 -3
- package/dist/server/modules/admin/routes/admin-config.d.mts +4 -2
- package/dist/server/modules/admin/routes/admin-config.mjs +56 -24
- package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
- package/dist/server/modules/admin/routes/execute-action.mjs +35 -9
- package/dist/server/modules/admin/routes/locales.mjs +1 -1
- package/dist/server/modules/admin/routes/preview.d.mts +11 -11
- package/dist/server/modules/admin/routes/preview.mjs +6 -5
- package/dist/server/modules/admin/routes/reactive.d.mts +9 -9
- package/dist/server/modules/admin/routes/reactive.mjs +2 -2
- package/dist/server/modules/admin/routes/route-helpers.d.mts +11 -7
- package/dist/server/modules/admin/routes/route-helpers.mjs +1 -1
- package/dist/server/modules/admin/routes/setup.d.mts +7 -7
- package/dist/server/modules/admin/routes/translations.d.mts +4 -4
- package/dist/server/modules/admin/routes/widget-data.d.mts +5 -5
- package/dist/server/modules/admin/routes/widget-data.mjs +12 -4
- package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +45 -45
- package/dist/server/modules/audit/.generated/module.d.mts +6 -6
- package/dist/server/modules/audit/collections/audit-log.d.mts +81 -80
- package/dist/server/modules/audit/jobs/audit-cleanup.d.mts +2 -2
- package/dist/server/plugin.mjs +10 -5
- package/dist/server/proxy-factories.d.mts +8 -1
- package/dist/server/proxy-factories.mjs +33 -1
- package/dist/server.d.mts +3 -1
- package/dist/shared/types/index.d.mts +1 -0
- package/dist/shared/types/saved-views.types.d.mts +14 -7
- package/dist/shared.d.mts +3 -2
- package/package.json +5 -4
|
@@ -5,13 +5,16 @@ import { cn } from "../../lib/utils.mjs";
|
|
|
5
5
|
import { selectRealtime, useAdminStore } from "../../runtime/provider.mjs";
|
|
6
6
|
import { useSafeContentLocales } from "../../runtime/content-locales-provider.mjs";
|
|
7
7
|
import { useScopedLocale } from "../../runtime/locale-scope.mjs";
|
|
8
|
+
import { resolveIconElement } from "../../components/component-renderer.mjs";
|
|
8
9
|
import { flattenOptions } from "../../components/primitives/types.mjs";
|
|
9
10
|
import { Button } from "../../components/ui/button.mjs";
|
|
10
11
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../components/ui/select.mjs";
|
|
11
12
|
import { LocaleSwitcher } from "../../components/locale-switcher.mjs";
|
|
13
|
+
import { Badge } from "../../components/ui/badge.mjs";
|
|
12
14
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../../components/ui/tooltip.mjs";
|
|
13
15
|
import { Checkbox } from "../../components/ui/checkbox.mjs";
|
|
14
16
|
import { ActionButton } from "../../components/actions/action-button.mjs";
|
|
17
|
+
import { adminCollectionKey, getCollectionQueryApi } from "../../hooks/query-access.mjs";
|
|
15
18
|
import { useCollectionFields } from "../../hooks/use-collection-fields.mjs";
|
|
16
19
|
import { useSuspenseCollectionMeta } from "../../hooks/use-collection-meta.mjs";
|
|
17
20
|
import { ActionDialog } from "../../components/actions/action-dialog.mjs";
|
|
@@ -26,18 +29,19 @@ import { AdminViewHeader, AdminViewLayout } from "../layout/admin-view-layout.mj
|
|
|
26
29
|
import { TableViewSkeleton } from "./view-skeletons.mjs";
|
|
27
30
|
import { useUploadCollection } from "../../hooks/use-upload-collection.mjs";
|
|
28
31
|
import { useDebouncedValue, useSearch } from "../../hooks/use-search.mjs";
|
|
32
|
+
import { SearchInput } from "../../components/ui/search-input.mjs";
|
|
29
33
|
import { autoExpandFields, hasFieldsToExpand } from "../../utils/auto-expand-fields.mjs";
|
|
30
34
|
import { computeDefaultColumns, getAllAvailableFields } from "./columns/column-defaults.mjs";
|
|
31
35
|
import { buildColumns } from "./columns/build-columns.mjs";
|
|
32
36
|
import { HeaderActions } from "../../components/actions/header-actions.mjs";
|
|
33
37
|
import { FilterBuilderSheet } from "../../components/filter-builder/filter-builder-sheet.mjs";
|
|
34
|
-
import { SearchInput } from "../../components/ui/search-input.mjs";
|
|
35
38
|
import { useActions } from "../../hooks/use-action.mjs";
|
|
36
39
|
import { useRealtimeHighlight } from "../../hooks/use-realtime-highlight.mjs";
|
|
37
40
|
import { useDeleteSavedView, useSaveView, useSavedViews } from "../../hooks/use-saved-views.mjs";
|
|
38
41
|
import { useViewState } from "../../hooks/use-view-state.mjs";
|
|
39
42
|
import { BulkActionToolbar } from "./bulk-action-toolbar.mjs";
|
|
40
43
|
import { buildOutlineRows } from "./outline.mjs";
|
|
44
|
+
import { QuickFilterBar } from "./quick-filter-bar.mjs";
|
|
41
45
|
import { UploadCollectionButton, mapListSchemaToConfig, stringifyGroupValue } from "./table-view.mjs";
|
|
42
46
|
import { Icon } from "@iconify/react";
|
|
43
47
|
import * as React from "react";
|
|
@@ -194,16 +198,30 @@ function SimpleValue({ value }) {
|
|
|
194
198
|
if (Array.isArray(value)) return /* @__PURE__ */ jsx(Fragment, { children: value.map((item) => stringifySimpleValue(item)).join(", ") });
|
|
195
199
|
return /* @__PURE__ */ jsx(Fragment, { children: stringifySimpleValue(value) });
|
|
196
200
|
}
|
|
201
|
+
function formatShortDate(date) {
|
|
202
|
+
const now = /* @__PURE__ */ new Date();
|
|
203
|
+
const sameYear = date.getFullYear() === now.getFullYear();
|
|
204
|
+
const month = date.toLocaleDateString(void 0, { month: "short" });
|
|
205
|
+
const day = date.getDate();
|
|
206
|
+
return sameYear ? `${month} ${day}` : `${month} ${day}, ${date.getFullYear()}`;
|
|
207
|
+
}
|
|
208
|
+
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
|
|
197
209
|
function stringifySimpleValue(value) {
|
|
198
210
|
if (value === null || value === void 0 || value === "") return "-";
|
|
199
211
|
if (typeof value === "object") {
|
|
212
|
+
if (value instanceof Date && !Number.isNaN(value.getTime())) return formatShortDate(value);
|
|
200
213
|
const record = value;
|
|
201
214
|
return String(record.title ?? record.name ?? record.label ?? record.id ?? "-");
|
|
202
215
|
}
|
|
216
|
+
if (typeof value === "string" && ISO_DATE_RE.test(value)) {
|
|
217
|
+
const date = new Date(value);
|
|
218
|
+
if (!Number.isNaN(date.getTime())) return formatShortDate(date);
|
|
219
|
+
}
|
|
203
220
|
return String(value);
|
|
204
221
|
}
|
|
205
222
|
function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/admin", showSearch = true, showFilters = true, showToolbar = true, realtime, headerActions, actionsConfig }) {
|
|
206
223
|
"use no memo";
|
|
224
|
+
const collectionKey = adminCollectionKey(collection);
|
|
207
225
|
const globalRealtimeConfig = useAdminStore(selectRealtime);
|
|
208
226
|
const { fields: resolvedFields, schema } = useCollectionFields(collection, { fallbackFields: config?.fields });
|
|
209
227
|
const { collections: uploadCollections } = useUploadCollection();
|
|
@@ -248,7 +266,7 @@ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/
|
|
|
248
266
|
const [isSheetOpen, setIsSheetOpen] = useSidebarSearchParam("view-options", { legacyKey: "viewOptions" });
|
|
249
267
|
const [searchTerm, setSearchTerm] = React.useState("");
|
|
250
268
|
const [isSearchPanelOpen, setIsSearchPanelOpen] = React.useState(false);
|
|
251
|
-
const [
|
|
269
|
+
const [toggledOutlineKeys, setToggledOutlineKeys] = React.useState(() => /* @__PURE__ */ new Set());
|
|
252
270
|
const [rowSelection, setRowSelection] = React.useState({});
|
|
253
271
|
const defaultColumns = React.useMemo(() => computeDefaultColumns(resolvedFields, {
|
|
254
272
|
meta: collectionMeta,
|
|
@@ -260,14 +278,39 @@ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/
|
|
|
260
278
|
]);
|
|
261
279
|
const groupingConfig = resolvedListConfig?.grouping;
|
|
262
280
|
const defaultGroupBy = groupingConfig?.defaultField ?? null;
|
|
263
|
-
const
|
|
281
|
+
const defaultFilters = React.useMemo(() => resolvedListConfig?.defaultFilters ?? [], [resolvedListConfig?.defaultFilters]);
|
|
282
|
+
const viewState = useViewState(defaultColumns, React.useMemo(() => ({
|
|
264
283
|
realtime: resolvedRealtime,
|
|
265
|
-
groupBy: defaultGroupBy
|
|
266
|
-
|
|
284
|
+
groupBy: defaultGroupBy,
|
|
285
|
+
filters: defaultFilters
|
|
286
|
+
}), [
|
|
287
|
+
resolvedRealtime,
|
|
288
|
+
defaultGroupBy,
|
|
289
|
+
defaultFilters
|
|
290
|
+
]), collection, user?.id);
|
|
267
291
|
const effectiveRealtime = viewState.config.realtime ?? resolvedRealtime;
|
|
268
292
|
const visibleColumns = viewState.config.visibleColumns.length > 0 ? viewState.config.visibleColumns : defaultColumns;
|
|
293
|
+
const layout = resolvedListConfig?.layout;
|
|
294
|
+
const titleField = layout?.titleField ?? (collectionMeta?.title?.type === "field" ? collectionMeta.title.fieldName : void 0);
|
|
295
|
+
const subtitleField = layout?.subtitleField;
|
|
296
|
+
const leadingFields = normalizeFieldList(layout?.leadingFields);
|
|
297
|
+
const badgeFields = normalizeFieldList(layout?.badgeFields);
|
|
298
|
+
const metaFields = normalizeFieldList(layout?.metaFields);
|
|
299
|
+
const density = layout?.density ?? "compact";
|
|
269
300
|
const outlineRelationNames = React.useMemo(() => extractRelationNamesFromOutline(resolvedListConfig?.outline), [resolvedListConfig?.outline]);
|
|
270
|
-
const visibleColumnsForExpansion = React.useMemo(() => Array.from(new Set([
|
|
301
|
+
const visibleColumnsForExpansion = React.useMemo(() => Array.from(new Set([
|
|
302
|
+
...visibleColumns,
|
|
303
|
+
...outlineRelationNames,
|
|
304
|
+
...metaFields,
|
|
305
|
+
...leadingFields,
|
|
306
|
+
...badgeFields
|
|
307
|
+
])), [
|
|
308
|
+
visibleColumns,
|
|
309
|
+
outlineRelationNames,
|
|
310
|
+
metaFields,
|
|
311
|
+
leadingFields,
|
|
312
|
+
badgeFields
|
|
313
|
+
]);
|
|
271
314
|
const expandedFields = React.useMemo(() => autoExpandFields({
|
|
272
315
|
fields: resolvedFields,
|
|
273
316
|
list: resolvedListConfig,
|
|
@@ -279,7 +322,7 @@ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/
|
|
|
279
322
|
visibleColumnsForExpansion,
|
|
280
323
|
collectionMeta?.relations
|
|
281
324
|
]);
|
|
282
|
-
const isKnownSortField = React.useCallback((field) => !!field && (field === "_title" || !!resolvedFields?.[field]), [resolvedFields]);
|
|
325
|
+
const isKnownSortField = React.useCallback((field) => !!field && (field === "_title" || field === "createdAt" || field === "updatedAt" || !!resolvedFields?.[field]), [resolvedFields]);
|
|
283
326
|
const effectiveSort = React.useMemo(() => {
|
|
284
327
|
if (isKnownSortField(viewState.config.sortConfig?.field)) return viewState.config.sortConfig;
|
|
285
328
|
if (isKnownSortField(resolvedListConfig?.defaultSort?.field)) return resolvedListConfig.defaultSort;
|
|
@@ -292,7 +335,79 @@ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/
|
|
|
292
335
|
resolvedListConfig?.defaultSort,
|
|
293
336
|
isKnownSortField
|
|
294
337
|
]);
|
|
295
|
-
const
|
|
338
|
+
const availableFields = React.useMemo(() => getAllAvailableFields(resolvedFields, { meta: collectionMeta }), [resolvedFields, collectionMeta]);
|
|
339
|
+
const fieldByName = React.useMemo(() => new Map(availableFields.map((field) => [field.name, field])), [availableFields]);
|
|
340
|
+
const effectiveOutline = React.useMemo(() => {
|
|
341
|
+
const outline = resolvedListConfig?.outline;
|
|
342
|
+
const groupBy = viewState.config.groupBy;
|
|
343
|
+
if (!groupBy) return outline;
|
|
344
|
+
const levels = outline?.levels ?? [];
|
|
345
|
+
const first = levels[0];
|
|
346
|
+
if (first?.kind === "field" && first.field === groupBy) return outline;
|
|
347
|
+
return {
|
|
348
|
+
...outline,
|
|
349
|
+
defaultExpanded: outline?.defaultExpanded ?? true,
|
|
350
|
+
levels: [{
|
|
351
|
+
kind: "field",
|
|
352
|
+
field: groupBy
|
|
353
|
+
}, ...levels]
|
|
354
|
+
};
|
|
355
|
+
}, [resolvedListConfig?.outline, viewState.config.groupBy]);
|
|
356
|
+
const hasOutline = (resolvedListConfig?.outline?.levels?.length ?? 0) > 0;
|
|
357
|
+
const firstFieldLevel = React.useMemo(() => {
|
|
358
|
+
const first = effectiveOutline?.levels?.[0];
|
|
359
|
+
return first?.kind === "field" ? first : null;
|
|
360
|
+
}, [effectiveOutline]);
|
|
361
|
+
const groupValues = React.useMemo(() => {
|
|
362
|
+
if (!firstFieldLevel) return null;
|
|
363
|
+
const fieldDef = fieldByName.get(firstFieldLevel.field);
|
|
364
|
+
if (fieldDef?.type !== "select" || !fieldDef.options?.options) return null;
|
|
365
|
+
const flat = flattenOptions(fieldDef.options.options);
|
|
366
|
+
const order = firstFieldLevel.order;
|
|
367
|
+
if (Array.isArray(order)) return order.map((v) => flat.find((o) => String(o.value) === String(v)) ?? {
|
|
368
|
+
value: v,
|
|
369
|
+
label: { en: String(v) }
|
|
370
|
+
});
|
|
371
|
+
return flat;
|
|
372
|
+
}, [firstFieldLevel, fieldByName]);
|
|
373
|
+
const filteredGroupValues = React.useMemo(() => {
|
|
374
|
+
if (!groupValues || !firstFieldLevel) return groupValues;
|
|
375
|
+
const groupField = firstFieldLevel.field;
|
|
376
|
+
const relevant = viewState.config.filters.filter((f) => f.field === groupField);
|
|
377
|
+
if (relevant.length === 0) return groupValues;
|
|
378
|
+
return groupValues.filter((gv) => {
|
|
379
|
+
const val = String(gv.value);
|
|
380
|
+
for (const filter of relevant) switch (filter.operator) {
|
|
381
|
+
case "equals":
|
|
382
|
+
if (String(filter.value) !== val) return false;
|
|
383
|
+
break;
|
|
384
|
+
case "not_equals":
|
|
385
|
+
if (String(filter.value) === val) return false;
|
|
386
|
+
break;
|
|
387
|
+
case "in":
|
|
388
|
+
if (!Array.isArray(filter.value) || !filter.value.map(String).includes(val)) return false;
|
|
389
|
+
break;
|
|
390
|
+
case "not_in":
|
|
391
|
+
if (Array.isArray(filter.value) && filter.value.map(String).includes(val)) return false;
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
return true;
|
|
395
|
+
});
|
|
396
|
+
}, [
|
|
397
|
+
groupValues,
|
|
398
|
+
firstFieldLevel,
|
|
399
|
+
viewState.config.filters
|
|
400
|
+
]);
|
|
401
|
+
const isGrouped = filteredGroupValues != null && filteredGroupValues.length > 0;
|
|
402
|
+
const [groupLimits, setGroupLimits] = React.useState({});
|
|
403
|
+
const DEFAULT_GROUP_LIMIT = 20;
|
|
404
|
+
const loadMoreInGroup = React.useCallback((groupKey) => {
|
|
405
|
+
setGroupLimits((prev) => ({
|
|
406
|
+
...prev,
|
|
407
|
+
[groupKey]: (prev[groupKey] ?? DEFAULT_GROUP_LIMIT) + DEFAULT_GROUP_LIMIT
|
|
408
|
+
}));
|
|
409
|
+
}, []);
|
|
410
|
+
const baseQueryOptions = React.useMemo(() => {
|
|
296
411
|
const options = {};
|
|
297
412
|
if (collectionMeta?.softDelete) options.includeDeleted = !!viewState.config.includeDeleted;
|
|
298
413
|
if (hasFieldsToExpand(expandedFields)) options.with = expandedFields;
|
|
@@ -302,28 +417,41 @@ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/
|
|
|
302
417
|
relationNames: collectionMeta?.relations ?? []
|
|
303
418
|
});
|
|
304
419
|
if (where) options.where = where;
|
|
305
|
-
const groupBy = viewState.config.groupBy;
|
|
306
420
|
const sortConfig = effectiveSort;
|
|
307
|
-
if (
|
|
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;
|
|
421
|
+
if (sortConfig) options.orderBy = { [sortConfig.field]: sortConfig.direction };
|
|
314
422
|
return options;
|
|
315
423
|
}, [
|
|
316
424
|
collectionMeta?.softDelete,
|
|
317
425
|
collectionMeta?.relations,
|
|
318
426
|
viewState.config.includeDeleted,
|
|
319
427
|
viewState.config.filters,
|
|
320
|
-
viewState.config.groupBy,
|
|
321
|
-
viewState.config.pagination?.page,
|
|
322
|
-
viewState.config.pagination?.pageSize,
|
|
323
428
|
expandedFields,
|
|
324
429
|
effectiveSort,
|
|
325
430
|
resolvedFields
|
|
326
431
|
]);
|
|
432
|
+
const flatQueryOptions = React.useMemo(() => {
|
|
433
|
+
if (isGrouped) return baseQueryOptions;
|
|
434
|
+
const options = { ...baseQueryOptions };
|
|
435
|
+
const groupBy = viewState.config.groupBy;
|
|
436
|
+
const sortConfig = effectiveSort;
|
|
437
|
+
if (groupBy && sortConfig?.field && sortConfig.field !== groupBy) options.orderBy = [{ [groupBy]: "asc" }, { [sortConfig.field]: sortConfig.direction }];
|
|
438
|
+
if (hasOutline && !isGrouped) options.limit = 500;
|
|
439
|
+
else {
|
|
440
|
+
const pageSize = viewState.config.pagination?.pageSize ?? 25;
|
|
441
|
+
const page = viewState.config.pagination?.page ?? 1;
|
|
442
|
+
options.limit = pageSize;
|
|
443
|
+
options.offset = (page - 1) * pageSize;
|
|
444
|
+
}
|
|
445
|
+
return options;
|
|
446
|
+
}, [
|
|
447
|
+
baseQueryOptions,
|
|
448
|
+
isGrouped,
|
|
449
|
+
hasOutline,
|
|
450
|
+
viewState.config.groupBy,
|
|
451
|
+
viewState.config.pagination?.page,
|
|
452
|
+
viewState.config.pagination?.pageSize,
|
|
453
|
+
effectiveSort
|
|
454
|
+
]);
|
|
327
455
|
const debouncedSearchTerm = useDebouncedValue(searchTerm, 300);
|
|
328
456
|
const isSearching = debouncedSearchTerm.trim().length > 0;
|
|
329
457
|
const { data: searchData, isLoading: searchLoading, isFetching: searchFetching } = useSearch({
|
|
@@ -332,13 +460,34 @@ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/
|
|
|
332
460
|
limit: 100,
|
|
333
461
|
highlights: true
|
|
334
462
|
}, { enabled: isSearching });
|
|
335
|
-
const
|
|
336
|
-
|
|
463
|
+
const groupQueries = useQueries({ queries: isGrouped && !isSearching ? filteredGroupValues.map((gv) => {
|
|
464
|
+
const limit = groupLimits[String(gv.value)] ?? DEFAULT_GROUP_LIMIT;
|
|
465
|
+
const groupWhere = {
|
|
466
|
+
...baseQueryOptions.where,
|
|
467
|
+
[firstFieldLevel.field]: gv.value
|
|
468
|
+
};
|
|
469
|
+
return getCollectionQueryApi(queryOpts, collectionKey).find({
|
|
470
|
+
...baseQueryOptions,
|
|
471
|
+
where: groupWhere,
|
|
472
|
+
limit,
|
|
473
|
+
offset: 0,
|
|
474
|
+
locale
|
|
475
|
+
}, { realtime: false });
|
|
476
|
+
}) : [] });
|
|
477
|
+
const { data: listData, isLoading: listLoading, error: listError } = useCollectionList(collectionKey, flatQueryOptions, { enabled: !isSearching && !isGrouped }, { realtime: effectiveRealtime });
|
|
478
|
+
const groupDataVersion = isGrouped ? groupQueries.map((q) => q.dataUpdatedAt).join(",") : "";
|
|
479
|
+
const items = React.useMemo(() => {
|
|
480
|
+
if (isSearching) return searchData?.docs ?? [];
|
|
481
|
+
if (isGrouped) return groupQueries.flatMap((q) => q.data?.docs ?? []);
|
|
482
|
+
return listData?.docs ?? [];
|
|
483
|
+
}, [
|
|
337
484
|
isSearching,
|
|
485
|
+
isGrouped,
|
|
338
486
|
searchData?.docs,
|
|
487
|
+
groupDataVersion,
|
|
339
488
|
listData?.docs
|
|
340
489
|
]);
|
|
341
|
-
const isLoading = isSearching ? searchLoading : listLoading;
|
|
490
|
+
const isLoading = isSearching ? searchLoading : isGrouped ? groupQueries.some((q) => q.isLoading) : listLoading;
|
|
342
491
|
const isSearchActive = isSearching && searchFetching;
|
|
343
492
|
const { isHighlighted } = useRealtimeHighlight(items, { enabled: effectiveRealtime && !isSearching });
|
|
344
493
|
const { getLock, isLocked: isDocLocked } = useLocks({
|
|
@@ -348,17 +497,7 @@ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/
|
|
|
348
497
|
});
|
|
349
498
|
const edgeLevels = React.useMemo(() => extractEdgeLevels(resolvedListConfig?.outline), [resolvedListConfig?.outline]);
|
|
350
499
|
const edgeQueries = useQueries({ queries: edgeLevels.map((level) => {
|
|
351
|
-
|
|
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({
|
|
500
|
+
return getCollectionQueryApi(queryOpts, adminCollectionKey(level.collection)).find({
|
|
362
501
|
where: level.where,
|
|
363
502
|
with: {
|
|
364
503
|
[level.parentField]: true,
|
|
@@ -376,39 +515,86 @@ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/
|
|
|
376
515
|
});
|
|
377
516
|
return map;
|
|
378
517
|
}, [edgeLevels, edgeQueries]);
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
const
|
|
382
|
-
if (
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
518
|
+
const metaForValue = React.useCallback((value, field) => {
|
|
519
|
+
if (!field) return void 0;
|
|
520
|
+
const fieldDef = fieldByName.get(field);
|
|
521
|
+
if (fieldDef?.type !== "select") return void 0;
|
|
522
|
+
const options = fieldDef?.options?.options;
|
|
523
|
+
if (!Array.isArray(options)) return void 0;
|
|
524
|
+
const option = flattenOptions(options).find((opt) => String(opt.value) === String(value));
|
|
525
|
+
if (!option) return void 0;
|
|
526
|
+
if (!option.icon && !option.className) return void 0;
|
|
386
527
|
return {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
levels: [{
|
|
390
|
-
kind: "field",
|
|
391
|
-
field: groupBy
|
|
392
|
-
}, ...levels]
|
|
528
|
+
icon: option.icon,
|
|
529
|
+
className: option.className
|
|
393
530
|
};
|
|
394
|
-
}, [
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
531
|
+
}, [fieldByName]);
|
|
532
|
+
const remainingOutline = React.useMemo(() => {
|
|
533
|
+
if (!isGrouped || !effectiveOutline) return effectiveOutline;
|
|
534
|
+
return {
|
|
535
|
+
...effectiveOutline,
|
|
536
|
+
levels: effectiveOutline.levels.slice(1)
|
|
537
|
+
};
|
|
538
|
+
}, [isGrouped, effectiveOutline]);
|
|
539
|
+
const labelForValue = React.useCallback((value, field) => stringifyGroupValue(value, field ? fieldByName.get(field) : void 0, resolveText, t, uiLocale, t("common.noValue")), [
|
|
540
|
+
fieldByName,
|
|
541
|
+
resolveText,
|
|
542
|
+
t,
|
|
543
|
+
uiLocale
|
|
544
|
+
]);
|
|
545
|
+
const outlineRows = React.useMemo(() => isGrouped ? [] : buildOutlineRows({
|
|
398
546
|
docs: items,
|
|
399
547
|
outline: effectiveOutline,
|
|
400
548
|
edgesByCollection,
|
|
401
|
-
|
|
402
|
-
labelForValue
|
|
549
|
+
toggledKeys: toggledOutlineKeys,
|
|
550
|
+
labelForValue,
|
|
551
|
+
metaForValue
|
|
403
552
|
}), [
|
|
553
|
+
isGrouped,
|
|
404
554
|
items,
|
|
405
555
|
effectiveOutline,
|
|
406
556
|
edgesByCollection,
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
557
|
+
toggledOutlineKeys,
|
|
558
|
+
labelForValue,
|
|
559
|
+
metaForValue
|
|
560
|
+
]);
|
|
561
|
+
const allGroupDocs = React.useMemo(() => {
|
|
562
|
+
if (!isGrouped) return void 0;
|
|
563
|
+
return groupQueries.flatMap((q) => q.data?.docs ?? []);
|
|
564
|
+
}, [isGrouped, groupDataVersion]);
|
|
565
|
+
const groupOutlineRows = React.useMemo(() => {
|
|
566
|
+
if (!isGrouped) return [];
|
|
567
|
+
return filteredGroupValues.map((gv, i) => {
|
|
568
|
+
const docs = (groupQueries[i]?.data)?.docs ?? [];
|
|
569
|
+
if (docs.length === 0) return [];
|
|
570
|
+
const outline = remainingOutline;
|
|
571
|
+
if (!outline || outline.levels.length === 0) return docs.map((doc) => ({
|
|
572
|
+
kind: "record",
|
|
573
|
+
key: `record:${doc.id}`,
|
|
574
|
+
id: String(doc.id),
|
|
575
|
+
doc,
|
|
576
|
+
depth: 0
|
|
577
|
+
}));
|
|
578
|
+
return buildOutlineRows({
|
|
579
|
+
docs,
|
|
580
|
+
outline,
|
|
581
|
+
edgesByCollection,
|
|
582
|
+
toggledKeys: toggledOutlineKeys,
|
|
583
|
+
labelForValue,
|
|
584
|
+
metaForValue,
|
|
585
|
+
allDocs: allGroupDocs
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
}, [
|
|
589
|
+
isGrouped,
|
|
590
|
+
filteredGroupValues,
|
|
591
|
+
groupDataVersion,
|
|
592
|
+
remainingOutline,
|
|
593
|
+
edgesByCollection,
|
|
594
|
+
toggledOutlineKeys,
|
|
595
|
+
labelForValue,
|
|
596
|
+
metaForValue,
|
|
597
|
+
allGroupDocs
|
|
412
598
|
]);
|
|
413
599
|
const table = useReactTable({
|
|
414
600
|
data: items,
|
|
@@ -419,33 +605,44 @@ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/
|
|
|
419
605
|
getRowId: (row) => String(row.id),
|
|
420
606
|
state: { rowSelection }
|
|
421
607
|
});
|
|
422
|
-
const rowsById = React.useMemo(() =>
|
|
423
|
-
|
|
424
|
-
|
|
608
|
+
const rowsById = React.useMemo(() => {
|
|
609
|
+
const map = /* @__PURE__ */ new Map();
|
|
610
|
+
for (const row of table.getRowModel().rows) {
|
|
611
|
+
map.set(String(row.id), row);
|
|
612
|
+
const originalId = row.original?.id;
|
|
613
|
+
if (originalId !== void 0 && originalId !== null) map.set(String(originalId), row);
|
|
614
|
+
}
|
|
615
|
+
return map;
|
|
616
|
+
}, [table]);
|
|
617
|
+
const deleteMutation = useCollectionDelete(collectionKey);
|
|
618
|
+
const restoreMutation = useCollectionRestore(collectionKey);
|
|
425
619
|
const { data: savedViewsData, isLoading: savedViewsLoading } = useSavedViews(collection, user?.id);
|
|
426
620
|
const saveViewMutation = useSaveView(collection, user?.id);
|
|
427
621
|
const deleteViewMutation = useDeleteSavedView(collection, user?.id);
|
|
428
622
|
const hasActiveFilters = viewState.config.filters.length > 0;
|
|
429
|
-
const hasViewOptionsState = hasActiveFilters || !!viewState.config.
|
|
623
|
+
const hasViewOptionsState = hasActiveFilters || !!viewState.config.sortConfig || !!viewState.config.groupBy || !!viewState.config.includeDeleted;
|
|
430
624
|
const groupableFields = React.useMemo(() => {
|
|
431
625
|
const groupableNames = groupingConfig?.fields ?? [];
|
|
432
626
|
if (groupableNames.length === 0) return [];
|
|
433
627
|
const groupableSet = new Set(groupableNames);
|
|
434
628
|
return availableFields.filter((field) => groupableSet.has(field.name));
|
|
435
629
|
}, [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
630
|
const clearFilters = React.useCallback(() => {
|
|
444
631
|
viewState.setConfig({
|
|
445
632
|
...viewState.config,
|
|
446
633
|
filters: []
|
|
447
634
|
});
|
|
448
635
|
}, [viewState]);
|
|
636
|
+
const applyQuickFilters = React.useCallback((filters) => {
|
|
637
|
+
viewState.setConfig((current) => ({
|
|
638
|
+
...current,
|
|
639
|
+
filters,
|
|
640
|
+
pagination: {
|
|
641
|
+
...current.pagination ?? { pageSize: 25 },
|
|
642
|
+
page: 1
|
|
643
|
+
}
|
|
644
|
+
}));
|
|
645
|
+
}, [viewState]);
|
|
449
646
|
const handleSaveView = React.useCallback((name, configuration) => {
|
|
450
647
|
saveViewMutation.mutate({
|
|
451
648
|
name,
|
|
@@ -469,7 +666,7 @@ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/
|
|
|
469
666
|
collection
|
|
470
667
|
]);
|
|
471
668
|
const toggleOutlineKey = React.useCallback((key) => {
|
|
472
|
-
|
|
669
|
+
setToggledOutlineKeys((current) => {
|
|
473
670
|
const next = new Set(current);
|
|
474
671
|
if (next.has(key)) next.delete(key);
|
|
475
672
|
else next.add(key);
|
|
@@ -484,6 +681,37 @@ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/
|
|
|
484
681
|
if (cell && column) return flexRender(cell.column.columnDef.cell, cell.getContext());
|
|
485
682
|
return /* @__PURE__ */ jsx(SimpleValue, { value: fallback ?? getValueAtPath(row?.original, field) });
|
|
486
683
|
}, [columnsByKey]);
|
|
684
|
+
const resolveFieldValue = React.useCallback((item, field) => {
|
|
685
|
+
const value = getValueAtPath(item, field);
|
|
686
|
+
if (value != null && typeof value === "object") return value;
|
|
687
|
+
const fieldDef = fieldByName.get(field);
|
|
688
|
+
if (fieldDef?.type === "relation") {
|
|
689
|
+
const expanded = getValueAtPath(item, fieldDef.options?.relationName ?? field);
|
|
690
|
+
if (expanded && typeof expanded === "object") return expanded;
|
|
691
|
+
}
|
|
692
|
+
return value;
|
|
693
|
+
}, [fieldByName]);
|
|
694
|
+
const renderMetaField = React.useCallback((field, value) => {
|
|
695
|
+
if (value === null || value === void 0 || value === "") return /* @__PURE__ */ jsx("span", {
|
|
696
|
+
className: "text-foreground-subtle",
|
|
697
|
+
children: "-"
|
|
698
|
+
});
|
|
699
|
+
const fieldDef = fieldByName.get(field);
|
|
700
|
+
if (fieldDef?.type === "datetime" || fieldDef?.type === "date" || field === "updatedAt" || field === "createdAt") return /* @__PURE__ */ jsx("span", { children: typeof value === "string" && ISO_DATE_RE.test(value) ? formatShortDate(new Date(value)) : value instanceof Date ? formatShortDate(value) : String(value) });
|
|
701
|
+
return /* @__PURE__ */ jsx(Badge, {
|
|
702
|
+
variant: "secondary",
|
|
703
|
+
className: "max-w-full truncate",
|
|
704
|
+
children: typeof value === "object" ? String(value.title ?? value.name ?? value.label ?? value.id ?? "-") : String(value)
|
|
705
|
+
});
|
|
706
|
+
}, [fieldByName]);
|
|
707
|
+
const renderLeadingIcon = React.useCallback((field, value) => {
|
|
708
|
+
const fieldDef = fieldByName.get(field);
|
|
709
|
+
if (fieldDef?.type === "select" && fieldDef.options?.options) {
|
|
710
|
+
const option = flattenOptions(fieldDef.options.options).find((opt) => String(opt.value) === String(value));
|
|
711
|
+
if (option?.icon) return resolveIconElement(option.icon);
|
|
712
|
+
}
|
|
713
|
+
return null;
|
|
714
|
+
}, [fieldByName]);
|
|
487
715
|
const handleRowClick = React.useCallback((item) => navigate(`${basePath}/collections/${collection}/${item.id}`), [
|
|
488
716
|
navigate,
|
|
489
717
|
basePath,
|
|
@@ -540,7 +768,7 @@ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/
|
|
|
540
768
|
showFilters && /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, { render: /* @__PURE__ */ jsxs(Button, {
|
|
541
769
|
variant: "outline",
|
|
542
770
|
size: "icon-sm",
|
|
543
|
-
className:
|
|
771
|
+
className: "relative",
|
|
544
772
|
onClick: () => setIsSheetOpen(true),
|
|
545
773
|
"aria-label": t("viewOptions.title"),
|
|
546
774
|
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" })]
|
|
@@ -549,30 +777,6 @@ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/
|
|
|
549
777
|
align: "end",
|
|
550
778
|
children: t("viewOptions.title")
|
|
551
779
|
})] }),
|
|
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
780
|
canUploadToCollection && showToolbar && /* @__PURE__ */ jsx(UploadCollectionButton, {
|
|
577
781
|
collection,
|
|
578
782
|
onUploaded: () => actionHelpers.invalidateCollection(collection)
|
|
@@ -587,120 +791,140 @@ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/
|
|
|
587
791
|
] })
|
|
588
792
|
}),
|
|
589
793
|
children: /* @__PURE__ */ jsxs("div", {
|
|
590
|
-
className: "space-y-
|
|
794
|
+
className: "space-y-4",
|
|
591
795
|
children: [
|
|
592
796
|
showSearch && isSearchPanelOpen && /* @__PURE__ */ jsx("div", {
|
|
593
|
-
className: "
|
|
797
|
+
className: "max-w-xl",
|
|
594
798
|
children: /* @__PURE__ */ jsx(SearchInput, {
|
|
595
799
|
value: searchTerm,
|
|
596
800
|
onChange: (event) => setSearchTerm(event.target.value),
|
|
597
801
|
onClear: () => setSearchTerm(""),
|
|
598
802
|
placeholder: t("collectionSearch.placeholder"),
|
|
599
|
-
|
|
600
|
-
|
|
803
|
+
isLoading: isSearchActive,
|
|
804
|
+
containerClassName: "h-10"
|
|
601
805
|
})
|
|
602
806
|
}),
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
807
|
+
/* @__PURE__ */ jsx(QuickFilterBar, {
|
|
808
|
+
quickFilters: resolvedListConfig?.quickFilters,
|
|
809
|
+
currentFilters: viewState.config.filters,
|
|
810
|
+
onApply: applyQuickFilters
|
|
811
|
+
}),
|
|
812
|
+
(() => {
|
|
813
|
+
if (isGrouped ? groupOutlineRows.every((g) => g.length === 0) : outlineRows.length === 0) return /* @__PURE__ */ jsx(EmptyState, {
|
|
814
|
+
title: emptyStateTitle,
|
|
815
|
+
description: emptyStateDescription,
|
|
816
|
+
height: "h-64",
|
|
817
|
+
action: isSearching || hasActiveFilters ? /* @__PURE__ */ jsxs("div", {
|
|
818
|
+
className: "flex gap-2",
|
|
819
|
+
children: [isSearching && /* @__PURE__ */ jsxs(Button, {
|
|
820
|
+
variant: "outline",
|
|
821
|
+
size: "sm",
|
|
822
|
+
className: "gap-2",
|
|
823
|
+
onClick: () => setSearchTerm(""),
|
|
824
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
825
|
+
icon: "ph:x",
|
|
826
|
+
className: "size-3.5"
|
|
827
|
+
}), t("common.clear")]
|
|
828
|
+
}), hasActiveFilters && /* @__PURE__ */ jsxs(Button, {
|
|
829
|
+
variant: "outline",
|
|
830
|
+
size: "sm",
|
|
831
|
+
className: "gap-2",
|
|
832
|
+
onClick: clearFilters,
|
|
833
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
834
|
+
icon: "ph:funnel-x",
|
|
835
|
+
className: "size-3.5"
|
|
836
|
+
}), t("viewOptions.clearFilters")]
|
|
837
|
+
})]
|
|
838
|
+
}) : void 0
|
|
839
|
+
});
|
|
840
|
+
const renderRecordRow = (outlineRow, groupContext) => {
|
|
637
841
|
const tableRow = rowsById.get(outlineRow.id);
|
|
638
|
-
|
|
639
|
-
const item = tableRow.original;
|
|
842
|
+
const item = tableRow?.original ?? outlineRow.doc;
|
|
640
843
|
const lock = getLock(item.id);
|
|
641
844
|
const locked = isDocLocked(item.id);
|
|
642
845
|
const lockUser = lock ? getLockUser(lock) : null;
|
|
643
|
-
const isSelected = tableRow
|
|
644
|
-
const titleValue = (titleField ? getValueAtPath(item, titleField) : item
|
|
846
|
+
const isSelected = tableRow?.getIsSelected() ?? false;
|
|
847
|
+
const titleValue = (titleField ? getValueAtPath(item, titleField) : item["_title"]) ?? item.title ?? item.name ?? item.id;
|
|
645
848
|
const subtitleValue = subtitleField ? getValueAtPath(item, subtitleField) : void 0;
|
|
849
|
+
const isGroupMismatch = groupContext != null && getValueAtPath(item, groupContext.field) !== groupContext.value;
|
|
646
850
|
return /* @__PURE__ */ jsxs("div", {
|
|
647
|
-
|
|
648
|
-
|
|
851
|
+
"data-state": isSelected ? "selected" : void 0,
|
|
852
|
+
className: cn("group/list-row hover:bg-accent data-[state=selected]:bg-muted relative flex min-w-0 cursor-pointer items-center gap-2 rounded-md px-4 text-sm transition-colors", density === "compact" ? "min-h-9 py-1" : "min-h-11 py-2", isHighlighted(item.id) && "animate-realtime-pulse", isGroupMismatch && "opacity-45"),
|
|
649
853
|
children: [
|
|
854
|
+
outlineRow.depth > 0 && /* @__PURE__ */ jsx("span", {
|
|
855
|
+
className: "bg-border-subtle absolute top-0 bottom-0 w-px",
|
|
856
|
+
style: { left: `${52 + (outlineRow.depth - 1) * 18}px` },
|
|
857
|
+
"aria-hidden": "true"
|
|
858
|
+
}),
|
|
650
859
|
/* @__PURE__ */ jsx("div", {
|
|
651
860
|
role: "presentation",
|
|
652
|
-
|
|
861
|
+
"data-state": isSelected ? "selected" : void 0,
|
|
862
|
+
className: "shrink-0 opacity-0 transition-opacity duration-[var(--motion-duration-fast)] group-hover/list-row:opacity-100 data-[state=selected]:opacity-100",
|
|
653
863
|
onClick: (event) => event.stopPropagation(),
|
|
654
864
|
onKeyDown: (event) => event.stopPropagation(),
|
|
655
865
|
children: /* @__PURE__ */ jsx(Checkbox, {
|
|
656
866
|
checked: isSelected,
|
|
657
|
-
disabled: !tableRow.getCanSelect(),
|
|
658
|
-
onCheckedChange: (checked) => tableRow
|
|
659
|
-
"aria-label": "
|
|
867
|
+
disabled: !tableRow || !tableRow.getCanSelect(),
|
|
868
|
+
onCheckedChange: (checked) => tableRow?.toggleSelected(!!checked),
|
|
869
|
+
"aria-label": t("table.selectRow")
|
|
660
870
|
})
|
|
661
871
|
}),
|
|
662
|
-
/* @__PURE__ */ jsx("button", {
|
|
872
|
+
outlineRow.expandable ? /* @__PURE__ */ jsx("button", {
|
|
663
873
|
type: "button",
|
|
664
|
-
className:
|
|
665
|
-
disabled: !outlineRow.expandable,
|
|
874
|
+
className: "text-muted-foreground hover:bg-surface-high hover:text-foreground focus-visible:ring-ring/40 -ml-1 flex size-7 shrink-0 items-center justify-center rounded-[var(--control-radius-inner)] transition-[background-color,color,box-shadow] focus-visible:ring-2 focus-visible:outline-none",
|
|
666
875
|
onClick: (event) => {
|
|
667
876
|
event.stopPropagation();
|
|
668
|
-
|
|
877
|
+
toggleOutlineKey(outlineRow.key);
|
|
669
878
|
},
|
|
670
|
-
"aria-label": outlineRow.collapsed ? "
|
|
671
|
-
children:
|
|
879
|
+
"aria-label": outlineRow.collapsed ? t("a11y.expand") : t("a11y.collapse"),
|
|
880
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
672
881
|
icon: "ph:caret-right-bold",
|
|
673
882
|
className: cn("size-3 transition-transform", !outlineRow.collapsed && "rotate-90")
|
|
674
|
-
})
|
|
675
|
-
}),
|
|
883
|
+
})
|
|
884
|
+
}) : null,
|
|
676
885
|
/* @__PURE__ */ jsxs("button", {
|
|
677
886
|
type: "button",
|
|
678
|
-
className: "min-w-0 flex-1 text-left",
|
|
887
|
+
className: "focus-visible:ring-ring/40 -my-1 min-w-0 flex-1 rounded-[var(--control-radius-inner)] py-1 text-left focus-visible:ring-2 focus-visible:outline-none",
|
|
888
|
+
style: outlineRow.depth > 0 ? { paddingLeft: `${outlineRow.depth * 18}px` } : void 0,
|
|
679
889
|
onClick: () => handleRowClick(item),
|
|
680
890
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
681
891
|
className: "flex min-w-0 items-center gap-2",
|
|
682
892
|
children: [
|
|
683
|
-
leadingFields.map((field) =>
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
893
|
+
leadingFields.map((field) => {
|
|
894
|
+
const fieldValue = getValueAtPath(item, field);
|
|
895
|
+
return /* @__PURE__ */ jsx("span", {
|
|
896
|
+
className: "flex size-4 shrink-0 items-center justify-center",
|
|
897
|
+
children: renderLeadingIcon(field, fieldValue) || renderField(tableRow, field, fieldValue)
|
|
898
|
+
}, field);
|
|
899
|
+
}),
|
|
687
900
|
/* @__PURE__ */ jsx("span", {
|
|
688
|
-
className: "text-foreground
|
|
901
|
+
className: cn("truncate", outlineRow.depth > 0 ? "text-muted-foreground font-normal" : "text-foreground font-medium"),
|
|
689
902
|
children: titleField ? renderField(tableRow, titleField, titleValue) : stringifySimpleValue(titleValue)
|
|
690
903
|
}),
|
|
691
|
-
badgeFields.map((field) =>
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
904
|
+
badgeFields.map((field) => {
|
|
905
|
+
const fieldValue = getValueAtPath(item, field);
|
|
906
|
+
const icon = renderLeadingIcon(field, fieldValue);
|
|
907
|
+
if (icon) return /* @__PURE__ */ jsx("span", {
|
|
908
|
+
className: "flex size-4 shrink-0 items-center justify-center",
|
|
909
|
+
children: icon
|
|
910
|
+
}, field);
|
|
911
|
+
return /* @__PURE__ */ jsx("span", {
|
|
912
|
+
className: "text-muted-foreground inline-flex max-w-36 shrink-0 items-center text-xs",
|
|
913
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
914
|
+
className: "truncate",
|
|
915
|
+
children: renderField(tableRow, field, fieldValue)
|
|
916
|
+
})
|
|
917
|
+
}, field);
|
|
918
|
+
}),
|
|
698
919
|
locked && /* @__PURE__ */ jsxs("span", {
|
|
699
|
-
className: "text-
|
|
920
|
+
className: "text-muted-foreground bg-muted inline-flex shrink-0 items-center gap-1 rounded-full px-1.5 py-0.5 text-xs",
|
|
700
921
|
children: [/* @__PURE__ */ jsx(Icon, {
|
|
701
922
|
icon: "ph:lock-key",
|
|
702
923
|
className: "size-3"
|
|
703
|
-
}),
|
|
924
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
925
|
+
className: "max-w-20 truncate",
|
|
926
|
+
children: lockUser?.name ?? t("collection.locked")
|
|
927
|
+
})]
|
|
704
928
|
})
|
|
705
929
|
]
|
|
706
930
|
}), subtitleValue !== void 0 && /* @__PURE__ */ jsx("div", {
|
|
@@ -709,10 +933,10 @@ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/
|
|
|
709
933
|
})]
|
|
710
934
|
}),
|
|
711
935
|
metaFields.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
712
|
-
className: "text-muted-foreground hidden
|
|
936
|
+
className: "text-muted-foreground hidden shrink-0 items-center gap-2 text-xs md:flex",
|
|
713
937
|
children: metaFields.map((field) => /* @__PURE__ */ jsx("span", {
|
|
714
|
-
className: "min-w-0
|
|
715
|
-
children:
|
|
938
|
+
className: "flex w-28 min-w-0 items-center justify-end",
|
|
939
|
+
children: renderMetaField(field, resolveFieldValue(item, field))
|
|
716
940
|
}, field))
|
|
717
941
|
}),
|
|
718
942
|
actions.row.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
@@ -732,27 +956,198 @@ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/
|
|
|
732
956
|
})
|
|
733
957
|
]
|
|
734
958
|
}, outlineRow.key);
|
|
735
|
-
}
|
|
959
|
+
};
|
|
960
|
+
if (isGrouped) return /* @__PURE__ */ jsx("div", {
|
|
961
|
+
className: "flex flex-col gap-1.5 overflow-hidden",
|
|
962
|
+
children: filteredGroupValues.map((gv, i) => {
|
|
963
|
+
const rows = groupOutlineRows[i] ?? [];
|
|
964
|
+
const queryData = groupQueries[i]?.data;
|
|
965
|
+
const totalDocs = queryData?.totalDocs ?? 0;
|
|
966
|
+
const loadedCount = (queryData?.docs ?? []).length;
|
|
967
|
+
const groupKey = String(gv.value);
|
|
968
|
+
const groupLabel = labelForValue(gv.value, firstFieldLevel?.field);
|
|
969
|
+
const meta = metaForValue(gv.value, firstFieldLevel?.field);
|
|
970
|
+
const groupIcon = meta?.icon ? resolveIconElement(meta.icon) : null;
|
|
971
|
+
const isGroupLoading = groupQueries[i]?.isLoading ?? false;
|
|
972
|
+
if (totalDocs === 0 && !isGroupLoading) return null;
|
|
973
|
+
const groupToggleKey = `group:${groupKey}`;
|
|
974
|
+
const isGroupCollapsed = toggledOutlineKeys.has(groupToggleKey);
|
|
975
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
976
|
+
className: "flex flex-col",
|
|
977
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
978
|
+
type: "button",
|
|
979
|
+
className: "hover:bg-surface-mid focus-visible:ring-ring/40 flex min-h-8 items-center gap-2 rounded-md px-3 py-1 text-left transition-colors focus-visible:ring-2 focus-visible:outline-none",
|
|
980
|
+
onClick: () => toggleOutlineKey(groupToggleKey),
|
|
981
|
+
"aria-expanded": !isGroupCollapsed,
|
|
982
|
+
children: [
|
|
983
|
+
/* @__PURE__ */ jsx(Icon, {
|
|
984
|
+
icon: "ph:caret-right-bold",
|
|
985
|
+
className: cn("text-muted-foreground size-3 shrink-0 transition-transform", !isGroupCollapsed && "rotate-90")
|
|
986
|
+
}),
|
|
987
|
+
groupIcon && /* @__PURE__ */ jsx("span", {
|
|
988
|
+
className: "size-4 shrink-0",
|
|
989
|
+
children: groupIcon
|
|
990
|
+
}),
|
|
991
|
+
/* @__PURE__ */ jsx("span", {
|
|
992
|
+
className: "text-muted-foreground text-xs font-medium",
|
|
993
|
+
children: groupLabel
|
|
994
|
+
}),
|
|
995
|
+
/* @__PURE__ */ jsx("span", {
|
|
996
|
+
className: "text-muted-foreground/60 text-xs tabular-nums",
|
|
997
|
+
children: totalDocs
|
|
998
|
+
})
|
|
999
|
+
]
|
|
1000
|
+
}), !isGroupCollapsed && (isGroupLoading ? /* @__PURE__ */ jsx("div", {
|
|
1001
|
+
className: "flex items-center justify-center py-4",
|
|
1002
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
1003
|
+
icon: "ph:spinner-gap",
|
|
1004
|
+
className: "text-muted-foreground size-4 animate-spin"
|
|
1005
|
+
})
|
|
1006
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
1007
|
+
className: "flex flex-col gap-px",
|
|
1008
|
+
children: [rows.map((outlineRow) => {
|
|
1009
|
+
if (outlineRow.kind !== "record") return /* @__PURE__ */ jsx(OutlineHeaderRow, {
|
|
1010
|
+
row: outlineRow,
|
|
1011
|
+
showCounts: resolvedListConfig?.outline?.showCounts ?? true,
|
|
1012
|
+
onToggle: toggleOutlineKey
|
|
1013
|
+
}, outlineRow.key);
|
|
1014
|
+
return renderRecordRow(outlineRow, {
|
|
1015
|
+
field: firstFieldLevel.field,
|
|
1016
|
+
value: gv.value
|
|
1017
|
+
});
|
|
1018
|
+
}), totalDocs > loadedCount && /* @__PURE__ */ jsxs("button", {
|
|
1019
|
+
type: "button",
|
|
1020
|
+
className: "text-muted-foreground hover:text-foreground hover:bg-surface-mid focus-visible:ring-ring/40 flex w-full items-center justify-center gap-2 rounded-md py-2 text-xs transition-colors focus-visible:ring-2 focus-visible:outline-none",
|
|
1021
|
+
onClick: () => loadMoreInGroup(groupKey),
|
|
1022
|
+
children: [/* @__PURE__ */ jsx(Icon, {
|
|
1023
|
+
icon: "ph:caret-down",
|
|
1024
|
+
className: "size-3"
|
|
1025
|
+
}), /* @__PURE__ */ jsxs("span", { children: [
|
|
1026
|
+
t("table.loadMore"),
|
|
1027
|
+
" (",
|
|
1028
|
+
totalDocs - loadedCount,
|
|
1029
|
+
")"
|
|
1030
|
+
] })]
|
|
1031
|
+
})]
|
|
1032
|
+
}))]
|
|
1033
|
+
}, groupKey);
|
|
1034
|
+
})
|
|
1035
|
+
});
|
|
1036
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1037
|
+
className: "flex flex-col gap-px overflow-hidden",
|
|
1038
|
+
children: outlineRows.map((outlineRow) => {
|
|
1039
|
+
if (outlineRow.kind !== "record") return /* @__PURE__ */ jsx(OutlineHeaderRow, {
|
|
1040
|
+
row: outlineRow,
|
|
1041
|
+
showCounts: resolvedListConfig?.outline?.showCounts ?? true,
|
|
1042
|
+
onToggle: toggleOutlineKey
|
|
1043
|
+
}, outlineRow.key);
|
|
1044
|
+
return renderRecordRow(outlineRow);
|
|
1045
|
+
})
|
|
1046
|
+
});
|
|
1047
|
+
})(),
|
|
1048
|
+
!isSearching && (hasOutline || isGrouped) && /* @__PURE__ */ jsx("div", {
|
|
1049
|
+
className: "text-muted-foreground flex items-center gap-2 py-2 text-sm tabular-nums",
|
|
1050
|
+
"aria-live": "polite",
|
|
1051
|
+
"aria-atomic": "true",
|
|
1052
|
+
children: /* @__PURE__ */ jsxs("span", { children: [
|
|
1053
|
+
isGrouped ? groupQueries.reduce((sum, q) => sum + (q.data?.totalDocs ?? 0), 0) : items.length,
|
|
1054
|
+
" ",
|
|
1055
|
+
t("table.items")
|
|
1056
|
+
] })
|
|
736
1057
|
}),
|
|
737
|
-
/* @__PURE__ */ jsxs("div", {
|
|
738
|
-
className: "
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
1058
|
+
!isSearching && !hasOutline && !isGrouped && /* @__PURE__ */ jsxs("div", {
|
|
1059
|
+
className: "qa-list-view__pagination flex items-center justify-between gap-4 py-2 tabular-nums",
|
|
1060
|
+
role: "navigation",
|
|
1061
|
+
"aria-label": t("table.pagination"),
|
|
1062
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1063
|
+
className: "text-muted-foreground flex items-center gap-4 text-sm",
|
|
1064
|
+
"aria-live": "polite",
|
|
1065
|
+
"aria-atomic": "true",
|
|
1066
|
+
children: [/* @__PURE__ */ jsxs("span", { children: [
|
|
1067
|
+
items.length > 0 ? `${((viewState.config.pagination?.page ?? 1) - 1) * (viewState.config.pagination?.pageSize ?? 25) + 1}-${Math.min(((viewState.config.pagination?.page ?? 1) - 1) * (viewState.config.pagination?.pageSize ?? 25) + items.length, listData?.totalDocs ?? items.length)}` : "0",
|
|
1068
|
+
" ",
|
|
1069
|
+
t("table.of"),
|
|
1070
|
+
" ",
|
|
1071
|
+
listData?.totalDocs ?? 0
|
|
1072
|
+
] }), /* @__PURE__ */ jsxs("div", {
|
|
1073
|
+
className: "flex items-center gap-2",
|
|
1074
|
+
children: [/* @__PURE__ */ jsx("span", { children: t("table.show") }), /* @__PURE__ */ jsxs(Select, {
|
|
1075
|
+
value: String(viewState.config.pagination?.pageSize ?? 25),
|
|
1076
|
+
onValueChange: (value) => viewState.setPageSize(Number(value)),
|
|
1077
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger, {
|
|
1078
|
+
className: "h-8 w-[70px]",
|
|
1079
|
+
children: /* @__PURE__ */ jsx(SelectValue, {})
|
|
1080
|
+
}), /* @__PURE__ */ jsx(SelectContent, {
|
|
1081
|
+
side: "top",
|
|
1082
|
+
children: [
|
|
1083
|
+
10,
|
|
1084
|
+
25,
|
|
1085
|
+
50,
|
|
1086
|
+
100
|
|
1087
|
+
].map((size) => /* @__PURE__ */ jsx(SelectItem, {
|
|
1088
|
+
value: String(size),
|
|
1089
|
+
children: size
|
|
1090
|
+
}, size))
|
|
1091
|
+
})]
|
|
1092
|
+
})]
|
|
753
1093
|
})]
|
|
1094
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1095
|
+
className: "flex items-center gap-1",
|
|
1096
|
+
children: [
|
|
1097
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1098
|
+
variant: "ghost",
|
|
1099
|
+
size: "sm",
|
|
1100
|
+
className: "size-8 p-0",
|
|
1101
|
+
disabled: (viewState.config.pagination?.page ?? 1) <= 1,
|
|
1102
|
+
onClick: () => viewState.setPage((viewState.config.pagination?.page ?? 1) - 1),
|
|
1103
|
+
"aria-label": t("table.previousPage"),
|
|
1104
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
1105
|
+
icon: "ph:caret-left",
|
|
1106
|
+
className: "size-4"
|
|
1107
|
+
})
|
|
1108
|
+
}),
|
|
1109
|
+
Array.from({ length: Math.min(5, listData?.totalPages ?? 1) }, (_, i) => {
|
|
1110
|
+
const currentPage = viewState.config.pagination?.page ?? 1;
|
|
1111
|
+
const totalPages = listData?.totalPages ?? 1;
|
|
1112
|
+
let pageNum;
|
|
1113
|
+
if (totalPages <= 5) pageNum = i + 1;
|
|
1114
|
+
else if (currentPage <= 3) pageNum = i + 1;
|
|
1115
|
+
else if (currentPage >= totalPages - 2) pageNum = totalPages - 4 + i;
|
|
1116
|
+
else pageNum = currentPage - 2 + i;
|
|
1117
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
1118
|
+
variant: currentPage === pageNum ? "secondary" : "ghost",
|
|
1119
|
+
size: "sm",
|
|
1120
|
+
className: "size-8 min-w-8 p-0 tabular-nums",
|
|
1121
|
+
onClick: () => viewState.setPage(pageNum),
|
|
1122
|
+
"aria-label": t("table.page", { page: pageNum }),
|
|
1123
|
+
"aria-current": currentPage === pageNum ? "page" : void 0,
|
|
1124
|
+
children: pageNum
|
|
1125
|
+
}, pageNum);
|
|
1126
|
+
}),
|
|
1127
|
+
/* @__PURE__ */ jsx(Button, {
|
|
1128
|
+
variant: "ghost",
|
|
1129
|
+
size: "sm",
|
|
1130
|
+
className: "size-8 p-0",
|
|
1131
|
+
disabled: (viewState.config.pagination?.page ?? 1) >= (listData?.totalPages ?? 1),
|
|
1132
|
+
onClick: () => viewState.setPage((viewState.config.pagination?.page ?? 1) + 1),
|
|
1133
|
+
"aria-label": t("table.nextPage"),
|
|
1134
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
1135
|
+
icon: "ph:caret-right",
|
|
1136
|
+
className: "size-4"
|
|
1137
|
+
})
|
|
1138
|
+
})
|
|
1139
|
+
]
|
|
754
1140
|
})]
|
|
755
1141
|
}),
|
|
1142
|
+
isSearching && /* @__PURE__ */ jsxs("div", {
|
|
1143
|
+
className: "text-muted-foreground flex items-center gap-2 py-2 text-sm tabular-nums",
|
|
1144
|
+
"aria-live": "polite",
|
|
1145
|
+
"aria-atomic": "true",
|
|
1146
|
+
children: [isSearchActive && /* @__PURE__ */ jsx(Icon, {
|
|
1147
|
+
icon: "ph:spinner-gap",
|
|
1148
|
+
className: "size-3 animate-spin"
|
|
1149
|
+
}), t("cell.item", { count: items.length })]
|
|
1150
|
+
}),
|
|
756
1151
|
/* @__PURE__ */ jsx(BulkActionToolbar, {
|
|
757
1152
|
table,
|
|
758
1153
|
actions: actions.bulk,
|
|
@@ -781,7 +1176,9 @@ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/
|
|
|
781
1176
|
onDeleteView: (viewId) => deleteViewMutation.mutate(viewId),
|
|
782
1177
|
supportsSoftDelete: !!collectionMeta?.softDelete,
|
|
783
1178
|
groupableFields,
|
|
784
|
-
defaultGroupBy
|
|
1179
|
+
defaultGroupBy,
|
|
1180
|
+
defaultFilters,
|
|
1181
|
+
panels: { columns: false }
|
|
785
1182
|
}),
|
|
786
1183
|
dialogAction && /* @__PURE__ */ jsx(ActionDialog, {
|
|
787
1184
|
open: !!dialogAction,
|
|
@@ -796,10 +1193,10 @@ function ListViewInner({ collection, config, viewConfig, navigate, basePath = "/
|
|
|
796
1193
|
});
|
|
797
1194
|
}
|
|
798
1195
|
function OutlineHeaderRow({ row, showCounts, onToggle }) {
|
|
1196
|
+
const groupIcon = row.kind === "group" && "icon" in row ? resolveIconElement(row.icon) : null;
|
|
799
1197
|
return /* @__PURE__ */ jsxs("button", {
|
|
800
1198
|
type: "button",
|
|
801
|
-
className: "bg-
|
|
802
|
-
style: { paddingLeft: `${12 + row.depth * 18}px` },
|
|
1199
|
+
className: "hover:bg-surface-mid focus-visible:ring-ring/40 flex min-h-8 w-full items-center gap-2 rounded-md px-3 py-1 text-left transition-[background-color,color,box-shadow] focus-visible:ring-2 focus-visible:outline-none",
|
|
803
1200
|
onClick: () => onToggle(row.key),
|
|
804
1201
|
"aria-expanded": !row.collapsed,
|
|
805
1202
|
children: [
|
|
@@ -811,12 +1208,17 @@ function OutlineHeaderRow({ row, showCounts, onToggle }) {
|
|
|
811
1208
|
icon: "ph:folder-simple",
|
|
812
1209
|
className: "text-muted-foreground size-4"
|
|
813
1210
|
}),
|
|
1211
|
+
groupIcon && /* @__PURE__ */ jsx("span", {
|
|
1212
|
+
className: "size-4 shrink-0",
|
|
1213
|
+
children: groupIcon
|
|
1214
|
+
}),
|
|
814
1215
|
/* @__PURE__ */ jsx("span", {
|
|
815
|
-
className: "
|
|
1216
|
+
className: "text-muted-foreground truncate text-xs font-medium",
|
|
1217
|
+
style: row.depth > 0 ? { paddingLeft: `${row.depth * 18}px` } : void 0,
|
|
816
1218
|
children: row.label
|
|
817
1219
|
}),
|
|
818
1220
|
showCounts && /* @__PURE__ */ jsx("span", {
|
|
819
|
-
className: "
|
|
1221
|
+
className: "text-muted-foreground/60 ml-1 text-xs tabular-nums",
|
|
820
1222
|
children: row.count
|
|
821
1223
|
})
|
|
822
1224
|
]
|