@byline/admin 4.15.0 → 4.17.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 (51) hide show
  1. package/dist/abilities.js +2 -0
  2. package/dist/fields/relation/relation-field.js +3 -2
  3. package/dist/fields/relation/relation-many-field.js +3 -2
  4. package/dist/fields/relation/relation-picker.d.ts +2 -2
  5. package/dist/fields/relation/relation-summary.d.ts +2 -2
  6. package/dist/forms/document-actions.js +30 -25
  7. package/dist/forms/form-renderer.d.ts +17 -3
  8. package/dist/forms/form-renderer.js +18 -9
  9. package/dist/forms/tree-placement-widget.js +3 -2
  10. package/dist/forms/use-form-layout.d.ts +3 -3
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.js +1 -0
  13. package/dist/modules/analytics/abilities.d.ts +14 -0
  14. package/dist/modules/analytics/abilities.js +21 -0
  15. package/dist/modules/analytics/components/dashboard.d.ts +20 -0
  16. package/dist/modules/analytics/components/dashboard.js +308 -0
  17. package/dist/modules/analytics/components/dashboard.module.js +29 -0
  18. package/dist/modules/analytics/components/dashboard.test.node.d.ts +8 -0
  19. package/dist/modules/analytics/components/dashboard_module.css +268 -0
  20. package/dist/modules/analytics/components/timeseries.d.ts +59 -0
  21. package/dist/modules/analytics/components/timeseries.js +253 -0
  22. package/dist/modules/analytics/components/timeseries.module.js +17 -0
  23. package/dist/modules/analytics/components/timeseries_module.css +126 -0
  24. package/dist/modules/analytics/index.d.ts +9 -0
  25. package/dist/modules/analytics/index.js +1 -0
  26. package/dist/modules/analytics/types.d.ts +20 -0
  27. package/dist/modules/analytics/types.js +1 -0
  28. package/package.json +21 -11
  29. package/src/abilities.ts +2 -0
  30. package/src/fields/relation/relation-field.tsx +5 -3
  31. package/src/fields/relation/relation-many-field.tsx +5 -3
  32. package/src/fields/relation/relation-picker.tsx +2 -2
  33. package/src/fields/relation/relation-summary.tsx +2 -2
  34. package/src/forms/document-actions.test.tsx +157 -0
  35. package/src/forms/document-actions.tsx +93 -74
  36. package/src/forms/form-renderer-capabilities.test.tsx +155 -0
  37. package/src/forms/form-renderer-submit.test.tsx +194 -0
  38. package/src/forms/form-renderer.tsx +59 -25
  39. package/src/forms/tree-placement-widget.tsx +3 -2
  40. package/src/forms/use-form-layout.ts +3 -3
  41. package/src/index.ts +1 -0
  42. package/src/modules/analytics/abilities.ts +33 -0
  43. package/src/modules/analytics/components/dashboard.module.css +330 -0
  44. package/src/modules/analytics/components/dashboard.test.node.ts +193 -0
  45. package/src/modules/analytics/components/dashboard.tsx +371 -0
  46. package/src/modules/analytics/components/timeseries.module.css +151 -0
  47. package/src/modules/analytics/components/timeseries.tsx +332 -0
  48. package/src/modules/analytics/index.ts +14 -0
  49. package/src/modules/analytics/types.ts +31 -0
  50. package/dist/forms/__probe-nested.test.node.d.ts +0 -1
  51. package/dist/forms/__probe-two.test.node.d.ts +0 -1
package/dist/abilities.js CHANGED
@@ -2,10 +2,12 @@ import { registerAdminActivityAbilities } from "./modules/admin-activity/abiliti
2
2
  import { registerAdminPermissionsAbilities } from "./modules/admin-permissions/abilities.js";
3
3
  import { registerAdminRolesAbilities } from "./modules/admin-roles/abilities.js";
4
4
  import { registerAdminUsersAbilities } from "./modules/admin-users/abilities.js";
5
+ import { registerAnalyticsAbilities } from "./modules/analytics/abilities.js";
5
6
  function registerAdminAbilities(registry) {
6
7
  registerAdminUsersAbilities(registry);
7
8
  registerAdminRolesAbilities(registry);
8
9
  registerAdminPermissionsAbilities(registry);
9
10
  registerAdminActivityAbilities(registry);
11
+ registerAnalyticsAbilities(registry);
10
12
  }
11
13
  export { registerAdminAbilities };
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
- import { getCollectionAdminConfig, getCollectionDefinition } from "@byline/core";
3
+ import { getCollectionAdminConfig, getCollectionDefinition, isSingleton } from "@byline/core";
4
4
  import { useTranslation } from "@byline/i18n/react";
5
5
  import { Button, CloseIcon, EditIcon, ErrorText, HelpText, IconButton, Label } from "@byline/ui/react";
6
6
  import clsx from "clsx";
@@ -14,7 +14,8 @@ const RelationField = ({ field, value, defaultValue, onChange, id, path })=>{
14
14
  const fieldError = useFieldError(fieldPath);
15
15
  const fieldValue = useFieldValue(fieldPath);
16
16
  const incomingValue = void 0 !== fieldValue ? fieldValue ?? null : value ?? defaultValue ?? null;
17
- const targetDef = getCollectionDefinition(field.targetCollection);
17
+ const targetDefinition = getCollectionDefinition(field.targetCollection);
18
+ const targetDef = null == targetDefinition || isSingleton(targetDefinition) ? null : targetDefinition;
18
19
  const targetAdminConfig = getCollectionAdminConfig(field.targetCollection);
19
20
  const { t } = useTranslation('byline-admin');
20
21
  const [pickerOpen, setPickerOpen] = useState(false);
@@ -1,6 +1,6 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
- import { getCollectionAdminConfig, getCollectionDefinition } from "@byline/core";
3
+ import { getCollectionAdminConfig, getCollectionDefinition, isSingleton } from "@byline/core";
4
4
  import { useTranslation } from "@byline/i18n/react";
5
5
  import { Button, CloseIcon, DraggableSortable, ErrorText, GripperVerticalIcon, HelpText, IconButton, Label, PlusIcon, moveItem, useSortable } from "@byline/ui/react";
6
6
  import clsx from "clsx";
@@ -61,7 +61,8 @@ const RelationManyField = ({ field, defaultValue, id, path })=>{
61
61
  const { t } = useTranslation('byline-admin');
62
62
  const fieldError = useFieldError(fieldPath);
63
63
  const { getFieldValue, setFieldValue } = useFormContext();
64
- const targetDef = getCollectionDefinition(field.targetCollection);
64
+ const targetDefinition = getCollectionDefinition(field.targetCollection);
65
+ const targetDef = null == targetDefinition || isSingleton(targetDefinition) ? null : targetDefinition;
65
66
  const targetAdminConfig = getCollectionAdminConfig(field.targetCollection);
66
67
  const isUnknown = null == targetDef;
67
68
  const targetLabel = targetDef?.labels.singular ?? field.targetCollection;
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- import type { CollectionDefinition } from '@byline/core';
8
+ import type { MultiCollectionDefinition } from '@byline/core';
9
9
  /**
10
10
  * Row rendering strategy, in priority order:
11
11
  * 1. `CollectionAdminConfig.itemView` — a ColumnDefinition[] from the target
@@ -35,7 +35,7 @@ interface RelationPickerBaseProps {
35
35
  /** The target collection path (e.g. `'media'`). */
36
36
  targetCollectionPath: string;
37
37
  /** The target collection definition (used for labels + displayField fallback). */
38
- targetDefinition?: CollectionDefinition | null;
38
+ targetDefinition?: MultiCollectionDefinition | null;
39
39
  /** Explicit display field to render as row label. */
40
40
  displayField?: string;
41
41
  /**
@@ -5,9 +5,9 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- import type { CollectionAdminConfig, CollectionDefinition } from '@byline/core';
8
+ import type { CollectionAdminConfig, MultiCollectionDefinition } from '@byline/core';
9
9
  interface RelationSummaryProps {
10
- targetDefinition: CollectionDefinition;
10
+ targetDefinition: MultiCollectionDefinition;
11
11
  targetAdminConfig: CollectionAdminConfig | null;
12
12
  displayField?: string;
13
13
  /** The raw relation value from the form. May be a plain ref or a populated envelope. */
@@ -49,6 +49,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
49
49
  onSelect: ()=>void onCancelScheduledPublication()
50
50
  });
51
51
  }
52
+ const hasAnyAction = schedulingActions.length > 0 || copyToLocaleAvailable || deleteLocaleAvailable || null != onDuplicate || null != onDelete;
52
53
  const handleOnDelete = ()=>{
53
54
  setShowDeleteConfirm(false);
54
55
  if (onDelete) onDelete();
@@ -109,7 +110,7 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
109
110
  const sourceLocaleLabel = contentLocales?.find((loc)=>loc.code === sourceLocale)?.label ?? sourceLocale ?? '';
110
111
  return /*#__PURE__*/ jsxs(Fragment, {
111
112
  children: [
112
- /*#__PURE__*/ jsxs(Dropdown.Root, {
113
+ hasAnyAction && /*#__PURE__*/ jsxs(Dropdown.Root, {
113
114
  children: [
114
115
  /*#__PURE__*/ jsx(Dropdown.Trigger, {
115
116
  render: /*#__PURE__*/ jsx(IconButton, {
@@ -187,31 +188,35 @@ function DocumentActions({ publishedVersion, onUnpublish, onDelete, onDuplicate,
187
188
  })
188
189
  })
189
190
  }),
190
- /*#__PURE__*/ jsx(Dropdown.Separator, {}),
191
- /*#__PURE__*/ jsx(Dropdown.Item, {
192
- onClick: ()=>{
193
- setShowDeleteConfirm(true);
194
- },
195
- children: /*#__PURE__*/ jsxs("div", {
196
- className: clsx('byline-form-actions-item', document_actions_module.item),
197
- children: [
198
- /*#__PURE__*/ jsx("span", {
199
- className: clsx('byline-form-actions-item-icon', document_actions_module["item-icon"]),
200
- children: /*#__PURE__*/ jsx(DeleteIcon, {
201
- width: "16px",
202
- height: "16px"
203
- })
204
- }),
205
- /*#__PURE__*/ jsx("span", {
206
- className: clsx('byline-form-actions-item-text', document_actions_module["item-text"]),
207
- children: /*#__PURE__*/ jsx("button", {
208
- type: "button",
209
- className: clsx('byline-form-actions-delete', document_actions_module["delete"]),
210
- children: t('common.actions.delete')
211
- })
191
+ onDelete && /*#__PURE__*/ jsxs(Fragment, {
192
+ children: [
193
+ /*#__PURE__*/ jsx(Dropdown.Separator, {}),
194
+ /*#__PURE__*/ jsx(Dropdown.Item, {
195
+ onClick: ()=>{
196
+ setShowDeleteConfirm(true);
197
+ },
198
+ children: /*#__PURE__*/ jsxs("div", {
199
+ className: clsx('byline-form-actions-item', document_actions_module.item),
200
+ children: [
201
+ /*#__PURE__*/ jsx("span", {
202
+ className: clsx('byline-form-actions-item-icon', document_actions_module["item-icon"]),
203
+ children: /*#__PURE__*/ jsx(DeleteIcon, {
204
+ width: "16px",
205
+ height: "16px"
206
+ })
207
+ }),
208
+ /*#__PURE__*/ jsx("span", {
209
+ className: clsx('byline-form-actions-item-text', document_actions_module["item-text"]),
210
+ children: /*#__PURE__*/ jsx("button", {
211
+ type: "button",
212
+ className: clsx('byline-form-actions-delete', document_actions_module["delete"]),
213
+ children: t('common.actions.delete')
214
+ })
215
+ })
216
+ ]
212
217
  })
213
- ]
214
- })
218
+ })
219
+ ]
215
220
  })
216
221
  ]
217
222
  })
@@ -6,7 +6,7 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
  import { type ReactNode } from 'react';
9
- import type { CollectionAdminConfig, Field, WorkflowStatus } from '@byline/core';
9
+ import type { Field, FormAdminConfig, WorkflowStatus } from '@byline/core';
10
10
  import type { DocumentPatch } from '@byline/core/patches';
11
11
  import { type DocumentActionsLocaleOption } from './document-actions';
12
12
  import { type ScheduledPublicationInfo, type SchedulePublicationInput } from './scheduled-publication-control';
@@ -39,7 +39,7 @@ export interface SystemFieldsSubmitPayload {
39
39
  export interface FormRendererProps {
40
40
  mode: 'create' | 'edit';
41
41
  fields: Field[];
42
- onSubmit: (data: any) => void;
42
+ onSubmit: (data: any) => void | Promise<void>;
43
43
  onCancel: () => void;
44
44
  onStatusChange?: (nextStatus: string) => Promise<void>;
45
45
  onUnpublish?: () => Promise<void>;
@@ -86,7 +86,7 @@ export interface FormRendererProps {
86
86
  workflowStatuses?: WorkflowStatus[];
87
87
  publishedVersion?: PublishedVersionInfo | null;
88
88
  initialData?: Record<string, any>;
89
- adminConfig?: CollectionAdminConfig;
89
+ adminConfig?: FormAdminConfig;
90
90
  /**
91
91
  * Name of the schema field to render as the live form heading.
92
92
  * Sourced from `CollectionDefinition.useAsTitle` by the caller.
@@ -98,6 +98,20 @@ export interface FormRendererProps {
98
98
  * present the path widget renders in the sidebar.
99
99
  */
100
100
  useAsPath?: string;
101
+ /**
102
+ * Whether the system path widget may render in the sidebar. Defaults to
103
+ * `true`, which preserves the historical behaviour of showing the widget
104
+ * whenever `useAsPath` is declared or the document envelope carries a
105
+ * `path`. Set `false` for a resource whose path is internal metadata and
106
+ * must never be presented or edited.
107
+ */
108
+ showPath?: boolean;
109
+ /**
110
+ * Explicit form heading, used verbatim. Overrides both `useAsTitle`'s live
111
+ * value and the create/edit wording derived from `mode` — for a resource
112
+ * whose identity does not change when it is first materialised.
113
+ */
114
+ heading?: string;
101
115
  /**
102
116
  * Opts the available-locales widget into the sidebar (below the path
103
117
  * widget). Sourced from `CollectionDefinition.advertiseLocales` by the
@@ -24,13 +24,15 @@ import { computeStatusTransitions } from "./status-transitions.js";
24
24
  import { TreePlacementWidget } from "./tree-placement-widget.js";
25
25
  import { executeUploadsWithProgress } from "./upload-executor.js";
26
26
  import { useFormLayout } from "./use-form-layout.js";
27
- const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpublish, scheduledPublication, onSchedulePublication, onConfirmScheduledPublication, onCancelScheduledPublication, onDelete, onDuplicate, onCopyToLocale, onDeleteLocale, contentLocales, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, advertiseLocales, tree, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale = 'en', useNavigationGuard: useNavigationGuardProp, restoreWarnings, _activeTabBySet, _onTabChange })=>{
27
+ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpublish, scheduledPublication, onSchedulePublication, onConfirmScheduledPublication, onCancelScheduledPublication, onDelete, onDuplicate, onCopyToLocale, onDeleteLocale, contentLocales, nextStatus, workflowStatuses, publishedVersion, initialData, adminConfig, useAsTitle, useAsPath, showPath = true, heading, advertiseLocales, tree, headingLabel, headerSlot, collectionPath, initialLocale, onLocaleChange, defaultLocale = 'en', useNavigationGuard: useNavigationGuardProp, restoreWarnings, _activeTabBySet, _onTabChange })=>{
28
28
  const { getFieldValues, runFieldHooks, validateForm, errors: initialErrors, hasChanges: hasChangesFn, resetHasChanges, getPatches, getDirtyBreakdown, getSystemPath, getSystemAvailableLocales, subscribeErrors, subscribeMeta, setFieldValue, setFieldError, getPendingUploads, clearPendingUploads, setFieldUploading } = useFormContext();
29
29
  const { t } = useTranslation('byline-admin');
30
30
  const [errors, setErrors] = useState(initialErrors);
31
31
  const [hasChanges, setHasChanges] = useState(hasChangesFn());
32
32
  const [statusBusy, setStatusBusy] = useState(false);
33
33
  const [isUploading, setIsUploading] = useState(false);
34
+ const submittingRef = useRef(false);
35
+ const [isSubmitting, setIsSubmitting] = useState(false);
34
36
  const [showUnsavedModal, setShowUnsavedModal] = useState(false);
35
37
  const [pendingSystemFieldsSubmit, setPendingSystemFieldsSubmit] = useState(null);
36
38
  const [contentLocale, setContentLocale] = useState(initialLocale ?? defaultLocale);
@@ -83,7 +85,7 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
83
85
  ]);
84
86
  const [formData, setFormData] = useState(()=>getFieldValues());
85
87
  const liveTitle = useFieldValue(useAsTitle ?? '');
86
- const heading = liveTitle || (headingLabel ? 'create' === mode ? t('forms.heading.createLabel', {
88
+ const computedHeading = heading || liveTitle || (headingLabel ? 'create' === mode ? t('forms.heading.createLabel', {
87
89
  label: headingLabel
88
90
  }) : t('forms.heading.editLabel', {
89
91
  label: headingLabel
@@ -107,10 +109,17 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
107
109
  const handleCancel = ()=>{
108
110
  if (onCancel && 'function' == typeof onCancel) onCancel();
109
111
  };
110
- const submitPayload = useCallback((payload)=>{
111
- if (onSubmit && 'function' == typeof onSubmit) {
112
- onSubmit(payload);
112
+ const submitPayload = useCallback(async (payload)=>{
113
+ if ('function' != typeof onSubmit) return;
114
+ if (submittingRef.current) return;
115
+ submittingRef.current = true;
116
+ setIsSubmitting(true);
117
+ try {
118
+ await onSubmit(payload);
113
119
  resetHasChanges();
120
+ } catch {} finally{
121
+ submittingRef.current = false;
122
+ setIsSubmitting(false);
114
123
  }
115
124
  }, [
116
125
  onSubmit,
@@ -169,7 +178,7 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
169
178
  systemAvailableLocales
170
179
  };
171
180
  if ('edit' === mode && ('direct-write' === reason || 'both' === reason)) return void setPendingSystemFieldsSubmit(payload);
172
- submitPayload(payload);
181
+ await submitPayload(payload);
173
182
  })();
174
183
  };
175
184
  const tabErrorCountsBySet = useMemo(()=>{
@@ -249,7 +258,7 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
249
258
  children: [
250
259
  /*#__PURE__*/ jsx("h1", {
251
260
  className: clsx('byline-form-heading', form_renderer_module.heading),
252
- children: heading
261
+ children: computedHeading
253
262
  }),
254
263
  headerSlot
255
264
  ]
@@ -285,7 +294,7 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
285
294
  className: clsx('byline-form-actions-button', form_renderer_module["actions-button"]),
286
295
  size: "sm",
287
296
  type: "submit",
288
- disabled: false === hasChanges || isUploading,
297
+ disabled: false === hasChanges || isUploading || isSubmitting,
289
298
  children: isUploading ? t('forms.actions.uploading') : t('common.actions.save')
290
299
  }),
291
300
  primaryStatus && onStatusChange && /*#__PURE__*/ jsx("div", {
@@ -386,7 +395,7 @@ const FormContent = ({ mode, fields, onSubmit, onCancel, onStatusChange, onUnpub
386
395
  /*#__PURE__*/ jsxs("div", {
387
396
  className: clsx('byline-form-sidebar', form_renderer_module.sidebar),
388
397
  children: [
389
- (useAsPath || 'string' == typeof initialData?.path && initialData.path.length > 0) && /*#__PURE__*/ jsx(PathWidget, {
398
+ showPath && (useAsPath || 'string' == typeof initialData?.path && initialData.path.length > 0) && /*#__PURE__*/ jsx(PathWidget, {
390
399
  useAsPath: useAsPath,
391
400
  collectionPath: collectionPath ?? '',
392
401
  defaultLocale: defaultLocale,
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useState } from "react";
4
- import { getCollectionDefinition } from "@byline/core";
4
+ import { getCollectionDefinition, isSingleton } from "@byline/core";
5
5
  import { useTranslation } from "@byline/i18n/react";
6
6
  import { Button } from "@byline/ui/react";
7
7
  import clsx from "clsx";
@@ -11,7 +11,8 @@ import tree_placement_widget_module from "./tree-placement-widget.module.js";
11
11
  const TreePlacementWidget = ({ collectionPath, documentId, useAsTitle })=>{
12
12
  const { t } = useTranslation('byline-admin');
13
13
  const { getTreeAncestors, getTreeParent, placeTreeNode } = useBylineFieldServices();
14
- const targetDefinition = getCollectionDefinition(collectionPath);
14
+ const definition = getCollectionDefinition(collectionPath);
15
+ const targetDefinition = null == definition || isSingleton(definition) ? null : definition;
15
16
  const [parent, setParent] = useState(null);
16
17
  const [placed, setPlaced] = useState(true);
17
18
  const [loading, setLoading] = useState(true);
@@ -1,4 +1,4 @@
1
- import type { CollectionAdminConfig, Field, GroupDefinition, LayoutDefinition, RowDefinition, TabSetDefinition } from '@byline/core';
1
+ import type { Field, FormAdminConfig, GroupDefinition, LayoutDefinition, RowDefinition, TabSetDefinition } from '@byline/core';
2
2
  /** Which tab set + tab a schema field is placed under. */
3
3
  export interface TabPath {
4
4
  tabSetName: string;
@@ -27,9 +27,9 @@ export interface FormLayout {
27
27
  * `layout.main`) are absent from the map. Rows and groups are recursed into;
28
28
  * the `seen` set guards against a config that references a row/group cycle.
29
29
  */
30
- export declare function buildFieldToTabPath(adminConfig: CollectionAdminConfig | undefined, fieldByName: Map<string, Field>, rowByName: Map<string, RowDefinition>, groupByName: Map<string, GroupDefinition>): Map<string, TabPath>;
30
+ export declare function buildFieldToTabPath(adminConfig: FormAdminConfig | undefined, fieldByName: Map<string, Field>, rowByName: Map<string, RowDefinition>, groupByName: Map<string, GroupDefinition>): Map<string, TabPath>;
31
31
  /**
32
32
  * Memoise the layout primitives + lookup tables the form renderer walks.
33
33
  * Rebuilt only when `adminConfig` / `fields` change.
34
34
  */
35
- export declare function useFormLayout(adminConfig: CollectionAdminConfig | undefined, fields: Field[]): FormLayout;
35
+ export declare function useFormLayout(adminConfig: FormAdminConfig | undefined, fields: Field[]): FormLayout;
package/dist/index.d.ts CHANGED
@@ -28,5 +28,6 @@ export * from './modules/admin-activity/index.js';
28
28
  export * from './modules/admin-permissions/index.js';
29
29
  export * from './modules/admin-roles/index.js';
30
30
  export * from './modules/admin-users/index.js';
31
+ export * from './modules/analytics/index.js';
31
32
  export * from './modules/auth/index.js';
32
33
  export type { AdminStore } from './store.js';
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ export * from "./modules/admin-activity/index.js";
3
3
  export * from "./modules/admin-permissions/index.js";
4
4
  export * from "./modules/admin-roles/index.js";
5
5
  export * from "./modules/admin-users/index.js";
6
+ export * from "./modules/analytics/index.js";
6
7
  export * from "./modules/auth/index.js";
7
8
  export { registerAdminAbilities } from "./abilities.js";
8
9
  export { assertAdminActor, requireAdminActor } from "./lib/assert-admin-actor.js";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ import type { AbilityRegistry } from '@byline/auth';
9
+ export declare const ANALYTICS_ABILITIES: {
10
+ readonly read: 'analytics.read';
11
+ readonly maintain: 'analytics.maintain';
12
+ };
13
+ export type AnalyticsAbilityKey = (typeof ANALYTICS_ABILITIES)[keyof typeof ANALYTICS_ABILITIES];
14
+ export declare function registerAnalyticsAbilities(registry: AbilityRegistry): void;
@@ -0,0 +1,21 @@
1
+ const ANALYTICS_ABILITIES = {
2
+ read: 'analytics.read',
3
+ maintain: 'analytics.maintain'
4
+ };
5
+ function registerAnalyticsAbilities(registry) {
6
+ registry.register({
7
+ key: ANALYTICS_ABILITIES.read,
8
+ label: 'Read analytics',
9
+ description: 'View installation-level page, visitor, download, referrer, and country totals.',
10
+ group: 'analytics',
11
+ source: 'admin'
12
+ });
13
+ registry.register({
14
+ key: ANALYTICS_ABILITIES.maintain,
15
+ label: 'Maintain analytics',
16
+ description: 'Delete retained analytics events and rebuild affected daily aggregates.',
17
+ group: 'analytics',
18
+ source: 'admin'
19
+ });
20
+ }
21
+ export { ANALYTICS_ABILITIES, registerAnalyticsAbilities };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+ import type React from 'react';
9
+ import type { AnalyticsDashboardData, AnalyticsDashboardPeriod } from '../types.js';
10
+ export interface AnalyticsDashboardProps {
11
+ data: AnalyticsDashboardData;
12
+ period: AnalyticsDashboardPeriod;
13
+ onPeriodChange(period: AnalyticsDashboardPeriod): void;
14
+ }
15
+ export declare function AnalyticsDashboard({ data, period, onPeriodChange, }: AnalyticsDashboardProps): React.JSX.Element;
16
+ /** Never collapse the bar entirely: a visible sliver still encodes "smallest". */
17
+ export declare function shareWidth(value: number, ceiling: number): number;
18
+ export declare function formatShare(part: number, whole: number, locale: string): string;
19
+ /** Return the retained boundary only when it truncates the selected range. */
20
+ export declare function partialCoverageFrom(rangeFrom: string, coverageFrom: string | null): string | undefined;