@ngbplatform/ui 1.3.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 (217) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +44 -0
  3. package/package.json +61 -0
  4. package/public/favicon.svg +6 -0
  5. package/public/silent-check-sso.html +8 -0
  6. package/src/assets/ngb_logo.svg +17 -0
  7. package/src/assets/ngb_logo_color.svg +17 -0
  8. package/src/index.ts +1023 -0
  9. package/src/ngb/README.md +27 -0
  10. package/src/ngb/accounting/NgbAccountingPeriodClosingPage.vue +1157 -0
  11. package/src/ngb/accounting/NgbChartOfAccountEditor.vue +590 -0
  12. package/src/ngb/accounting/NgbChartOfAccountsPage.vue +420 -0
  13. package/src/ngb/accounting/NgbGeneralJournalEntryEditPage.vue +796 -0
  14. package/src/ngb/accounting/NgbGeneralJournalEntryLinesEditor.vue +454 -0
  15. package/src/ngb/accounting/NgbGeneralJournalEntryListPage.vue +186 -0
  16. package/src/ngb/accounting/api.ts +79 -0
  17. package/src/ngb/accounting/generalJournalEntry.ts +127 -0
  18. package/src/ngb/accounting/generalJournalEntryApi.ts +112 -0
  19. package/src/ngb/accounting/generalJournalEntryTypes.ts +155 -0
  20. package/src/ngb/accounting/navigation.ts +83 -0
  21. package/src/ngb/accounting/periodClosing.ts +75 -0
  22. package/src/ngb/accounting/periodClosingApi.ts +60 -0
  23. package/src/ngb/accounting/periodClosingTypes.ts +73 -0
  24. package/src/ngb/accounting/types.ts +57 -0
  25. package/src/ngb/api/audit.ts +23 -0
  26. package/src/ngb/api/catalogs.ts +50 -0
  27. package/src/ngb/api/contracts.ts +285 -0
  28. package/src/ngb/api/documents.ts +170 -0
  29. package/src/ngb/api/http.ts +446 -0
  30. package/src/ngb/api/lookups.ts +14 -0
  31. package/src/ngb/api/types.ts +6 -0
  32. package/src/ngb/auth/index.ts +13 -0
  33. package/src/ngb/auth/keycloak.ts +374 -0
  34. package/src/ngb/auth/router.ts +35 -0
  35. package/src/ngb/auth/types.ts +12 -0
  36. package/src/ngb/auth/useAuthStore.ts +209 -0
  37. package/src/ngb/command-palette/NgbCommandPaletteDialog.vue +342 -0
  38. package/src/ngb/command-palette/NgbCommandPaletteEmptyState.vue +55 -0
  39. package/src/ngb/command-palette/NgbCommandPaletteFooterHints.vue +29 -0
  40. package/src/ngb/command-palette/api.ts +10 -0
  41. package/src/ngb/command-palette/config.ts +16 -0
  42. package/src/ngb/command-palette/presentation.ts +85 -0
  43. package/src/ngb/command-palette/search.ts +130 -0
  44. package/src/ngb/command-palette/staticItems.ts +243 -0
  45. package/src/ngb/command-palette/storage.ts +45 -0
  46. package/src/ngb/command-palette/store.ts +716 -0
  47. package/src/ngb/command-palette/types.ts +120 -0
  48. package/src/ngb/command-palette/useCommandPaletteHotkeys.ts +27 -0
  49. package/src/ngb/command-palette/useCommandPalettePageContext.ts +18 -0
  50. package/src/ngb/components/NgbConfirmDialog.vue +63 -0
  51. package/src/ngb/components/NgbDialog.vue +44 -0
  52. package/src/ngb/components/NgbDrawer.vue +156 -0
  53. package/src/ngb/components/NgbHeaderActionCluster.vue +140 -0
  54. package/src/ngb/components/NgbModalShell.vue +99 -0
  55. package/src/ngb/components/NgbToolbar.vue +14 -0
  56. package/src/ngb/components/forms/NgbFormLayout.vue +13 -0
  57. package/src/ngb/components/forms/NgbFormRow.vue +21 -0
  58. package/src/ngb/components/forms/NgbFormSection.vue +16 -0
  59. package/src/ngb/components/forms/NgbValidationSummary.vue +18 -0
  60. package/src/ngb/components/navigation/NgbNavigationTree.vue +68 -0
  61. package/src/ngb/components/navigation/NgbTreeNode.vue +94 -0
  62. package/src/ngb/components/register/NgbColumnChooser.vue +101 -0
  63. package/src/ngb/components/register/NgbRegisterGrid.vue +473 -0
  64. package/src/ngb/components/register/registerTypes.ts +51 -0
  65. package/src/ngb/components/register/useRegisterColumnResize.ts +99 -0
  66. package/src/ngb/components/register/useRegisterColumnState.ts +220 -0
  67. package/src/ngb/components/register/useRegisterRows.ts +209 -0
  68. package/src/ngb/components/register/useRegisterViewport.ts +56 -0
  69. package/src/ngb/editor/NgbDocumentEffectsPage.vue +512 -0
  70. package/src/ngb/editor/NgbDocumentFlowPage.vue +409 -0
  71. package/src/ngb/editor/NgbDocumentPrintPage.vue +712 -0
  72. package/src/ngb/editor/NgbEditorDiscardDialog.vue +40 -0
  73. package/src/ngb/editor/NgbEntityAuditSidebar.vue +288 -0
  74. package/src/ngb/editor/NgbEntityEditor.vue +250 -0
  75. package/src/ngb/editor/NgbEntityEditorDrawerActions.vue +88 -0
  76. package/src/ngb/editor/NgbEntityEditorHeader.vue +114 -0
  77. package/src/ngb/editor/catalogNavigation.ts +13 -0
  78. package/src/ngb/editor/config.ts +252 -0
  79. package/src/ngb/editor/documentCopyDraft.ts +245 -0
  80. package/src/ngb/editor/documentEffects.ts +43 -0
  81. package/src/ngb/editor/documentEffectsDateFormatting.ts +18 -0
  82. package/src/ngb/editor/documentNavigation.ts +115 -0
  83. package/src/ngb/editor/documentStatus.ts +45 -0
  84. package/src/ngb/editor/entityEditorErrors.ts +162 -0
  85. package/src/ngb/editor/entityEditorPersistence.ts +241 -0
  86. package/src/ngb/editor/extensions.ts +40 -0
  87. package/src/ngb/editor/types.ts +295 -0
  88. package/src/ngb/editor/useConfiguredEntityEditorDocumentActions.ts +183 -0
  89. package/src/ngb/editor/useDocumentEditorDrawerState.ts +152 -0
  90. package/src/ngb/editor/useEditorDrawerState.ts +88 -0
  91. package/src/ngb/editor/useEntityEditorBusinessContext.ts +97 -0
  92. package/src/ngb/editor/useEntityEditorCapabilities.ts +190 -0
  93. package/src/ngb/editor/useEntityEditorCommandPalette.ts +154 -0
  94. package/src/ngb/editor/useEntityEditorCommitHandlers.ts +102 -0
  95. package/src/ngb/editor/useEntityEditorHeaderActions.ts +253 -0
  96. package/src/ngb/editor/useEntityEditorLeaveGuard.ts +66 -0
  97. package/src/ngb/editor/useEntityEditorNavigationActions.ts +235 -0
  98. package/src/ngb/editor/useEntityEditorOutputs.ts +59 -0
  99. package/src/ngb/editor/useEntityEditorPageActions.ts +57 -0
  100. package/src/ngb/editor/useRouteQueryEditorDrawer.ts +173 -0
  101. package/src/ngb/env/runtimeConfig.ts +25 -0
  102. package/src/ngb/lookup/config.ts +42 -0
  103. package/src/ngb/lookup/defaultConfig.ts +136 -0
  104. package/src/ngb/lookup/navigation.ts +76 -0
  105. package/src/ngb/lookup/prefetch.ts +40 -0
  106. package/src/ngb/lookup/store.ts +261 -0
  107. package/src/ngb/metadata/NgbDocumentListFiltersDrawer.vue +78 -0
  108. package/src/ngb/metadata/NgbDocumentPeriodFilter.vue +53 -0
  109. package/src/ngb/metadata/NgbEntityForm.vue +84 -0
  110. package/src/ngb/metadata/NgbEntityFormFieldsBlock.vue +110 -0
  111. package/src/ngb/metadata/NgbEntityListPageHeader.vue +80 -0
  112. package/src/ngb/metadata/NgbFilterFieldControl.vue +104 -0
  113. package/src/ngb/metadata/NgbMetadataCatalogEditPage.vue +42 -0
  114. package/src/ngb/metadata/NgbMetadataCatalogListPage.vue +269 -0
  115. package/src/ngb/metadata/NgbMetadataDocumentEditPage.vue +68 -0
  116. package/src/ngb/metadata/NgbMetadataDocumentListPage.vue +353 -0
  117. package/src/ngb/metadata/NgbMetadataFieldRenderer.vue +124 -0
  118. package/src/ngb/metadata/NgbMetadataLookupControl.vue +92 -0
  119. package/src/ngb/metadata/NgbRecycleBinFilter.vue +53 -0
  120. package/src/ngb/metadata/NgbRegisterPageLayout.vue +143 -0
  121. package/src/ngb/metadata/config.ts +28 -0
  122. package/src/ngb/metadata/dataTypes.ts +27 -0
  123. package/src/ngb/metadata/entityForm.ts +114 -0
  124. package/src/ngb/metadata/entityModel.ts +34 -0
  125. package/src/ngb/metadata/entityValueFormatting.ts +83 -0
  126. package/src/ngb/metadata/fieldRendererState.ts +84 -0
  127. package/src/ngb/metadata/filtering.ts +153 -0
  128. package/src/ngb/metadata/lookup.ts +25 -0
  129. package/src/ngb/metadata/monthPagedListQuery.ts +108 -0
  130. package/src/ngb/metadata/normalization.ts +100 -0
  131. package/src/ngb/metadata/referenceHydration.ts +99 -0
  132. package/src/ngb/metadata/register.ts +135 -0
  133. package/src/ngb/metadata/routePages.ts +86 -0
  134. package/src/ngb/metadata/store.ts +35 -0
  135. package/src/ngb/metadata/types.ts +276 -0
  136. package/src/ngb/metadata/useMetadataListFilters.ts +306 -0
  137. package/src/ngb/metadata/useMetadataRegisterPageData.ts +246 -0
  138. package/src/ngb/metadata/useValidationFocus.ts +49 -0
  139. package/src/ngb/primitives/NgbBadge.vue +31 -0
  140. package/src/ngb/primitives/NgbButton.vue +53 -0
  141. package/src/ngb/primitives/NgbCheckbox.vue +33 -0
  142. package/src/ngb/primitives/NgbDatePicker.vue +141 -0
  143. package/src/ngb/primitives/NgbIcon.vue +545 -0
  144. package/src/ngb/primitives/NgbInput.vue +57 -0
  145. package/src/ngb/primitives/NgbLookup.vue +264 -0
  146. package/src/ngb/primitives/NgbMonthPicker.vue +132 -0
  147. package/src/ngb/primitives/NgbMultiSelect.vue +179 -0
  148. package/src/ngb/primitives/NgbPeriodPicker.vue +125 -0
  149. package/src/ngb/primitives/NgbPickerNavButton.vue +31 -0
  150. package/src/ngb/primitives/NgbPickerPopover.vue +79 -0
  151. package/src/ngb/primitives/NgbRadioGroup.vue +53 -0
  152. package/src/ngb/primitives/NgbSelect.vue +108 -0
  153. package/src/ngb/primitives/NgbStatusIcon.vue +63 -0
  154. package/src/ngb/primitives/NgbSwitch.vue +42 -0
  155. package/src/ngb/primitives/NgbTabs.vue +116 -0
  156. package/src/ngb/primitives/NgbToastHost.vue +49 -0
  157. package/src/ngb/primitives/NgbTooltip.vue +27 -0
  158. package/src/ngb/primitives/iconNames.ts +83 -0
  159. package/src/ngb/primitives/toast.ts +66 -0
  160. package/src/ngb/primitives/useAsyncComboboxQuery.ts +85 -0
  161. package/src/ngb/primitives/useFloatingDropdownPosition.ts +129 -0
  162. package/src/ngb/reporting/NgbReportComposerCollectionSection.vue +106 -0
  163. package/src/ngb/reporting/NgbReportComposerPanel.vue +1187 -0
  164. package/src/ngb/reporting/NgbReportDateRangeFilter.vue +54 -0
  165. package/src/ngb/reporting/NgbReportPage.vue +1124 -0
  166. package/src/ngb/reporting/NgbReportSheet.vue +467 -0
  167. package/src/ngb/reporting/api.ts +35 -0
  168. package/src/ngb/reporting/composer.ts +904 -0
  169. package/src/ngb/reporting/config.ts +120 -0
  170. package/src/ngb/reporting/defaultConfig.ts +15 -0
  171. package/src/ngb/reporting/lookupFilters.ts +43 -0
  172. package/src/ngb/reporting/navigation.ts +181 -0
  173. package/src/ngb/reporting/pageHelpers.ts +73 -0
  174. package/src/ngb/reporting/pageSession.ts +92 -0
  175. package/src/ngb/reporting/paging.ts +66 -0
  176. package/src/ngb/reporting/types.ts +333 -0
  177. package/src/ngb/router/backNavigation.ts +122 -0
  178. package/src/ngb/router/queryParams.ts +129 -0
  179. package/src/ngb/router/queryState.ts +121 -0
  180. package/src/ngb/router/routeAliases.ts +66 -0
  181. package/src/ngb/router/routeParams.ts +15 -0
  182. package/src/ngb/router/shareLink.ts +39 -0
  183. package/src/ngb/security/NgbAccessAuditPanel.vue +32 -0
  184. package/src/ngb/security/NgbAccessDeniedState.vue +27 -0
  185. package/src/ngb/security/NgbEffectiveAccessPanel.vue +60 -0
  186. package/src/ngb/security/NgbPermissionMatrix.vue +160 -0
  187. package/src/ngb/security/NgbRoleEditorPage.vue +319 -0
  188. package/src/ngb/security/NgbRolesPage.vue +125 -0
  189. package/src/ngb/security/NgbUserEditorPage.vue +615 -0
  190. package/src/ngb/security/NgbUsersPage.vue +125 -0
  191. package/src/ngb/security/api.ts +91 -0
  192. package/src/ngb/security/audit.ts +21 -0
  193. package/src/ngb/security/permissions.ts +65 -0
  194. package/src/ngb/security/types.ts +157 -0
  195. package/src/ngb/security/useAccessStore.ts +69 -0
  196. package/src/ngb/site/NgbDashboardAsOfToolbar.vue +100 -0
  197. package/src/ngb/site/NgbDashboardStatusBanner.vue +91 -0
  198. package/src/ngb/site/NgbLogo.vue +44 -0
  199. package/src/ngb/site/NgbPageHeader.vue +49 -0
  200. package/src/ngb/site/NgbSiteShell.vue +286 -0
  201. package/src/ngb/site/NgbSiteSidebar.vue +270 -0
  202. package/src/ngb/site/NgbTopBar.vue +425 -0
  203. package/src/ngb/site/NgbTrendChart.vue +224 -0
  204. package/src/ngb/site/dashboardData.ts +324 -0
  205. package/src/ngb/site/mainMenuStore.ts +49 -0
  206. package/src/ngb/site/types.ts +31 -0
  207. package/src/ngb/site/useDashboardPageState.ts +98 -0
  208. package/src/ngb/site/useTheme.ts +95 -0
  209. package/src/ngb/utils/clone.ts +43 -0
  210. package/src/ngb/utils/dateValues.ts +107 -0
  211. package/src/ngb/utils/errorMessage.ts +14 -0
  212. package/src/ngb/utils/guid.ts +21 -0
  213. package/src/ngb/utils/stableValue.ts +21 -0
  214. package/src/ngb/utils/storage.ts +135 -0
  215. package/src/styles/tailwind.css +165 -0
  216. package/src/vite/publicAssets.ts +104 -0
  217. package/vite-public-assets.js +68 -0
@@ -0,0 +1,97 @@
1
+ import { computed, watch, type ComputedRef, type Ref } from 'vue';
2
+
3
+ import type { EntityFormModel } from '../metadata/types';
4
+ import {
5
+ resolveNgbEditorEntityProfile,
6
+ sanitizeNgbEditorModelForEditing,
7
+ syncNgbEditorComputedDisplay,
8
+ } from './config';
9
+ import type { EntityEditorContext, EditorKind } from './types';
10
+
11
+ type DocumentMetaWithParts = {
12
+ parts?: unknown[] | null;
13
+ };
14
+
15
+ export type UseEntityEditorBusinessContextArgs<
16
+ TDocumentMeta extends DocumentMetaWithParts | null = DocumentMetaWithParts | null,
17
+ > = {
18
+ kind: ComputedRef<EditorKind>;
19
+ typeCode: ComputedRef<string>;
20
+ model: Ref<EntityFormModel>;
21
+ docMeta: Ref<TDocumentMeta>;
22
+ loading: Ref<boolean>;
23
+ isNew: ComputedRef<boolean>;
24
+ isDraft: ComputedRef<boolean>;
25
+ isMarkedForDeletion: ComputedRef<boolean>;
26
+ };
27
+
28
+ function watchedFieldValues(model: EntityFormModel, fieldKeys: string[] | undefined): unknown[] {
29
+ return (fieldKeys ?? []).map((fieldKey) => model[fieldKey]);
30
+ }
31
+
32
+ export function useEntityEditorBusinessContext<
33
+ TDocumentMeta extends DocumentMetaWithParts | null = DocumentMetaWithParts | null,
34
+ >(args: UseEntityEditorBusinessContextArgs<TDocumentMeta>) {
35
+ const currentContext = computed<EntityEditorContext>(() => ({
36
+ kind: args.kind.value,
37
+ typeCode: args.typeCode.value,
38
+ }));
39
+
40
+ const entityProfile = computed(() => resolveNgbEditorEntityProfile(currentContext.value));
41
+ const tags = computed(() => Array.from(new Set(entityProfile.value.tags ?? [])));
42
+
43
+ const hasDocumentTables = computed(() =>
44
+ args.kind.value === 'document' && (args.docMeta.value?.parts?.length ?? 0) > 0,
45
+ );
46
+
47
+ function currentEditorContext(): EntityEditorContext {
48
+ return currentContext.value;
49
+ }
50
+
51
+ function hasTag(tag: string): boolean {
52
+ const normalized = String(tag ?? '').trim();
53
+ if (!normalized) return false;
54
+ return tags.value.includes(normalized);
55
+ }
56
+
57
+ watch(
58
+ () => [
59
+ args.kind.value,
60
+ args.typeCode.value,
61
+ ...watchedFieldValues(args.model.value, entityProfile.value.sanitizeWatchFields),
62
+ ],
63
+ () => {
64
+ if (!entityProfile.value.sanitizeModelForEditing) return;
65
+ sanitizeNgbEditorModelForEditing(currentContext.value, args.model.value);
66
+ },
67
+ { deep: false },
68
+ );
69
+
70
+ watch(
71
+ () => [
72
+ args.kind.value,
73
+ args.typeCode.value,
74
+ args.loading.value,
75
+ args.isNew.value,
76
+ args.isDraft.value,
77
+ args.isMarkedForDeletion.value,
78
+ ...watchedFieldValues(args.model.value, entityProfile.value.computedDisplayWatchFields),
79
+ ],
80
+ () => {
81
+ if (!entityProfile.value.syncComputedDisplay) return;
82
+ if (args.loading.value) return;
83
+ if (args.isMarkedForDeletion.value) return;
84
+ if (entityProfile.value.computedDisplayMode === 'new_or_draft' && !args.isNew.value && !args.isDraft.value) return;
85
+ syncNgbEditorComputedDisplay(currentContext.value, args.model.value);
86
+ },
87
+ { deep: false },
88
+ );
89
+
90
+ return {
91
+ currentEditorContext,
92
+ entityProfile,
93
+ tags,
94
+ hasTag,
95
+ hasDocumentTables,
96
+ };
97
+ }
@@ -0,0 +1,190 @@
1
+ import { computed, type ComputedRef, type Ref } from 'vue';
2
+
3
+ import type { EntityFormModel, DocumentStatusValue } from '../metadata/types';
4
+ import { documentStatusLabel, documentStatusTone } from './documentStatus';
5
+ import type { EditorKind } from './types';
6
+
7
+ type EntityEditorCapabilitiesMetadata = {
8
+ displayName?: string | null;
9
+ form?: unknown | null;
10
+ capabilities?: {
11
+ canCreate?: boolean;
12
+ canEdit?: boolean;
13
+ canEditDraft?: boolean;
14
+ canDelete?: boolean;
15
+ canDeleteDraft?: boolean;
16
+ canPost?: boolean;
17
+ canUnpost?: boolean;
18
+ canMarkForDeletion?: boolean;
19
+ canViewEffects?: boolean;
20
+ canViewFlow?: boolean;
21
+ } | null;
22
+ };
23
+
24
+ export type UseEntityEditorCapabilitiesArgs<
25
+ TMetadata extends EntityEditorCapabilitiesMetadata = EntityEditorCapabilitiesMetadata,
26
+ > = {
27
+ kind: ComputedRef<EditorKind>;
28
+ currentId: Ref<string | null>;
29
+ metadata: ComputedRef<TMetadata | null>;
30
+ model: Ref<EntityFormModel>;
31
+ loading: Ref<boolean>;
32
+ saving: Ref<boolean>;
33
+ isNew: ComputedRef<boolean>;
34
+ isDraft: ComputedRef<boolean>;
35
+ isMarkedForDeletion: ComputedRef<boolean>;
36
+ status: ComputedRef<DocumentStatusValue>;
37
+ };
38
+
39
+ export function useEntityEditorCapabilities<
40
+ TMetadata extends EntityEditorCapabilitiesMetadata = EntityEditorCapabilitiesMetadata,
41
+ >(args: UseEntityEditorCapabilitiesArgs<TMetadata>) {
42
+ const capabilities = computed(() => args.metadata.value?.capabilities ?? null);
43
+ const canOpenAudit = computed(() => !args.isNew.value && !!args.currentId.value);
44
+ const canShareLink = computed(() => !args.isNew.value && !!args.currentId.value);
45
+
46
+ const canOpenEffectsPage = computed(() =>
47
+ args.kind.value === 'document'
48
+ && capabilities.value?.canViewEffects !== false
49
+ && !args.isNew.value
50
+ && !!args.currentId.value
51
+ && !args.loading.value
52
+ && !args.saving.value,
53
+ );
54
+
55
+ const canOpenDocumentFlowPage = computed(() =>
56
+ args.kind.value === 'document'
57
+ && capabilities.value?.canViewFlow !== false
58
+ && !args.isNew.value
59
+ && !!args.currentId.value
60
+ && !args.loading.value
61
+ && !args.saving.value,
62
+ );
63
+
64
+ const canPrintDocument = computed(() =>
65
+ args.kind.value === 'document' && !args.isNew.value && !!args.currentId.value && !args.loading.value && !args.saving.value,
66
+ );
67
+
68
+ const canMarkForDeletion = computed(() => {
69
+ if (args.isNew.value) return false;
70
+ if (args.loading.value || args.saving.value) return false;
71
+ if (args.isMarkedForDeletion.value) return false;
72
+ if (capabilities.value?.canMarkForDeletion === false) return false;
73
+ if (args.kind.value === 'document' && !args.isDraft.value) return false;
74
+ return true;
75
+ });
76
+
77
+ const canUnmarkForDeletion = computed(() => {
78
+ if (args.isNew.value) return false;
79
+ if (args.loading.value || args.saving.value) return false;
80
+ if (!args.isMarkedForDeletion.value) return false;
81
+ if (capabilities.value?.canMarkForDeletion === false) return false;
82
+ return true;
83
+ });
84
+
85
+ const canDelete = computed(() =>
86
+ args.kind.value === 'catalog'
87
+ && capabilities.value?.canDelete !== false
88
+ && !args.isNew.value
89
+ && !args.loading.value
90
+ && !args.saving.value
91
+ && !args.isMarkedForDeletion.value,
92
+ );
93
+
94
+ const canPost = computed(() => {
95
+ if (args.kind.value !== 'document') return false;
96
+ if (capabilities.value?.canPost === false) return false;
97
+ if (args.isNew.value) return false;
98
+ if (args.loading.value || args.saving.value) return false;
99
+ return args.isDraft.value && !args.isMarkedForDeletion.value;
100
+ });
101
+
102
+ const canUnpost = computed(() => {
103
+ if (args.kind.value !== 'document') return false;
104
+ if (capabilities.value?.canUnpost === false) return false;
105
+ if (args.isNew.value) return false;
106
+ if (args.loading.value || args.saving.value) return false;
107
+ return args.status.value === 2;
108
+ });
109
+
110
+ const canSave = computed(() => {
111
+ if (!args.metadata.value?.form) return false;
112
+ if (!args.isNew.value && args.isMarkedForDeletion.value) return false;
113
+ if (args.kind.value !== 'document') {
114
+ return args.isNew.value
115
+ ? capabilities.value?.canCreate !== false
116
+ : capabilities.value?.canEdit !== false;
117
+ }
118
+ if (args.isNew.value) return capabilities.value?.canCreate !== false;
119
+ if (capabilities.value?.canEditDraft === false) return false;
120
+ return args.isDraft.value;
121
+ });
122
+
123
+ const documentDisplayTitle = computed(() => {
124
+ if (args.kind.value !== 'document') return '';
125
+
126
+ const display = String(args.model.value.display ?? '').trim();
127
+ if (display) return display;
128
+
129
+ const base = args.metadata.value?.displayName ?? 'Document';
130
+ return args.isNew.value ? `New ${base}` : base;
131
+ });
132
+
133
+ const resolvedDocumentStatusLabel = computed(() => {
134
+ if (args.kind.value !== 'document') return 'Draft';
135
+ if (args.isNew.value) return 'Draft';
136
+ return documentStatusLabel(args.status.value);
137
+ });
138
+
139
+ const resolvedDocumentStatusTone = computed(() => {
140
+ if (args.isNew.value) return 'neutral' as const;
141
+ return documentStatusTone(args.status.value);
142
+ });
143
+
144
+ const title = computed(() => {
145
+ if (args.kind.value === 'document') return documentDisplayTitle.value;
146
+
147
+ const base = args.metadata.value?.displayName ?? 'Catalog record';
148
+ return args.isNew.value ? `New ${base}` : base;
149
+ });
150
+
151
+ const subtitle = computed(() => {
152
+ if (args.kind.value === 'document') return resolvedDocumentStatusLabel.value;
153
+
154
+ const display = String(args.model.value.display ?? '').trim();
155
+ if (display) return display;
156
+
157
+ return args.isNew.value ? 'New record' : undefined;
158
+ });
159
+
160
+ const auditEntityKind = computed(() => (args.kind.value === 'catalog' ? 2 : 1));
161
+ const auditEntityId = computed(() => args.currentId.value);
162
+ const auditEntityTitle = computed(() => String(args.model.value.display ?? title.value ?? '').trim());
163
+
164
+ const isReadOnly = computed(() =>
165
+ (!args.isNew.value && args.isMarkedForDeletion.value)
166
+ || (args.kind.value === 'document' && !args.isNew.value && !args.isDraft.value),
167
+ );
168
+
169
+ return {
170
+ canOpenAudit,
171
+ canShareLink,
172
+ canOpenEffectsPage,
173
+ canOpenDocumentFlowPage,
174
+ canPrintDocument,
175
+ canMarkForDeletion,
176
+ canUnmarkForDeletion,
177
+ canDelete,
178
+ canPost,
179
+ canUnpost,
180
+ canSave,
181
+ documentStatusLabel: resolvedDocumentStatusLabel,
182
+ documentStatusTone: resolvedDocumentStatusTone,
183
+ title,
184
+ subtitle,
185
+ auditEntityKind,
186
+ auditEntityId,
187
+ auditEntityTitle,
188
+ isReadOnly,
189
+ };
190
+ }
@@ -0,0 +1,154 @@
1
+ import { computed, type ComputedRef } from 'vue';
2
+
3
+ import { useCommandPalettePageContext } from '../command-palette/useCommandPalettePageContext';
4
+ import type { CommandPaletteItemSeed } from '../command-palette/types';
5
+ import type { EditorKind, EditorMode } from './types';
6
+
7
+ type UseEntityEditorCommandPaletteArgs = {
8
+ mode: ComputedRef<EditorMode>;
9
+ kind: ComputedRef<EditorKind>;
10
+ typeCode: ComputedRef<string>;
11
+ currentId: ComputedRef<string | null>;
12
+ title: ComputedRef<string>;
13
+ canOpenDocumentFlowPage: ComputedRef<boolean>;
14
+ canOpenEffectsPage: ComputedRef<boolean>;
15
+ canPrintDocument: ComputedRef<boolean>;
16
+ canPost: ComputedRef<boolean>;
17
+ canUnpost: ComputedRef<boolean>;
18
+ openDocumentFlowPage: () => void;
19
+ openDocumentEffectsPage: () => void;
20
+ openDocumentPrintPage: () => void;
21
+ post: () => Promise<void>;
22
+ unpost: () => Promise<void>;
23
+ };
24
+
25
+ export function useEntityEditorCommandPalette(args: UseEntityEditorCommandPaletteArgs) {
26
+ const commandPaletteActions = computed<CommandPaletteItemSeed[]>(() => {
27
+ if (args.mode.value !== 'page') return [];
28
+
29
+ const actions: CommandPaletteItemSeed[] = [];
30
+
31
+ if (args.kind.value === 'document' && args.currentId.value && args.canOpenDocumentFlowPage.value) {
32
+ actions.push({
33
+ key: `current:flow:${args.typeCode.value}:${args.currentId.value}`,
34
+ group: 'actions',
35
+ kind: 'command',
36
+ scope: 'commands',
37
+ title: 'Open document flow',
38
+ subtitle: 'Open workflow for this document',
39
+ icon: 'document-flow',
40
+ badge: 'Flow',
41
+ hint: null,
42
+ route: null,
43
+ commandCode: 'document-flow',
44
+ status: null,
45
+ openInNewTabSupported: false,
46
+ keywords: ['flow', 'document flow'],
47
+ defaultRank: 988,
48
+ isCurrentContext: true,
49
+ perform: args.openDocumentFlowPage,
50
+ });
51
+ }
52
+
53
+ if (args.kind.value === 'document' && args.currentId.value && args.canOpenEffectsPage.value) {
54
+ actions.push({
55
+ key: `current:effects:${args.typeCode.value}:${args.currentId.value}`,
56
+ group: 'actions',
57
+ kind: 'command',
58
+ scope: 'commands',
59
+ title: 'Open accounting effects',
60
+ subtitle: 'Review ledger impact for this document',
61
+ icon: 'effects-flow',
62
+ badge: 'Effects',
63
+ hint: null,
64
+ route: null,
65
+ commandCode: 'accounting-effects',
66
+ status: null,
67
+ openInNewTabSupported: false,
68
+ keywords: ['effects', 'accounting effects', 'posting'],
69
+ defaultRank: 986,
70
+ isCurrentContext: true,
71
+ perform: args.openDocumentEffectsPage,
72
+ });
73
+ }
74
+
75
+ if (args.kind.value === 'document' && args.currentId.value && args.canPrintDocument.value) {
76
+ actions.push({
77
+ key: `current:print:${args.typeCode.value}:${args.currentId.value}`,
78
+ group: 'actions',
79
+ kind: 'command',
80
+ scope: 'commands',
81
+ title: 'Print document',
82
+ subtitle: 'Open a print-friendly version of this document',
83
+ icon: 'printer',
84
+ badge: 'Print',
85
+ hint: null,
86
+ route: null,
87
+ commandCode: 'print-document',
88
+ status: null,
89
+ openInNewTabSupported: false,
90
+ keywords: ['print', 'print document', 'paper'],
91
+ defaultRank: 985,
92
+ isCurrentContext: true,
93
+ perform: args.openDocumentPrintPage,
94
+ });
95
+ }
96
+
97
+ if (args.kind.value === 'document' && args.canPost.value) {
98
+ actions.push({
99
+ key: `current:post:${args.typeCode.value}:${args.currentId.value ?? 'new'}`,
100
+ group: 'actions',
101
+ kind: 'command',
102
+ scope: 'commands',
103
+ title: 'Post document',
104
+ subtitle: 'Post this document to the ledger',
105
+ icon: 'check',
106
+ badge: 'Post',
107
+ hint: null,
108
+ route: null,
109
+ commandCode: 'post-document',
110
+ status: null,
111
+ openInNewTabSupported: false,
112
+ keywords: ['post', 'post document'],
113
+ defaultRank: 984,
114
+ isCurrentContext: true,
115
+ perform: args.post,
116
+ });
117
+ } else if (args.kind.value === 'document' && args.canUnpost.value) {
118
+ actions.push({
119
+ key: `current:unpost:${args.typeCode.value}:${args.currentId.value ?? 'new'}`,
120
+ group: 'actions',
121
+ kind: 'command',
122
+ scope: 'commands',
123
+ title: 'Unpost document',
124
+ subtitle: 'Reverse the posted ledger impact',
125
+ icon: 'undo',
126
+ badge: 'Unpost',
127
+ hint: null,
128
+ route: null,
129
+ commandCode: 'unpost-document',
130
+ status: null,
131
+ openInNewTabSupported: false,
132
+ keywords: ['unpost', 'unpost document'],
133
+ defaultRank: 984,
134
+ isCurrentContext: true,
135
+ perform: args.unpost,
136
+ });
137
+ }
138
+
139
+ return actions;
140
+ });
141
+
142
+ useCommandPalettePageContext(() => {
143
+ if (args.mode.value !== 'page') return null;
144
+
145
+ return {
146
+ entityType: args.kind.value,
147
+ documentType: args.kind.value === 'document' ? args.typeCode.value : null,
148
+ catalogType: args.kind.value === 'catalog' ? args.typeCode.value : null,
149
+ entityId: args.currentId.value,
150
+ title: args.title.value,
151
+ actions: commandPaletteActions.value,
152
+ };
153
+ });
154
+ }
@@ -0,0 +1,102 @@
1
+ import type { Awaitable } from '../metadata/types';
2
+ import type { EditorChangeReason } from './types';
3
+
4
+ export type EntityEditorCommitContext = {
5
+ reloadSucceeded: boolean;
6
+ };
7
+
8
+ export type EntityEditorCreatedContext = EntityEditorCommitContext & {
9
+ id: string;
10
+ };
11
+
12
+ export type EntityEditorChangedContext = EntityEditorCommitContext & {
13
+ reason?: EditorChangeReason | (string & {});
14
+ };
15
+
16
+ type EntityEditorCommitDecision<TContext> =
17
+ | boolean
18
+ | ((context: TContext) => boolean);
19
+
20
+ export type UseEntityEditorCommitHandlersArgs = {
21
+ reload: () => Awaitable<boolean | void>;
22
+ closeDrawer?: () => Awaitable<void>;
23
+ onCreated?: (context: EntityEditorCreatedContext) => Awaitable<void>;
24
+ onSaved?: (context: EntityEditorCommitContext) => Awaitable<void>;
25
+ onChanged?: (context: EntityEditorChangedContext) => Awaitable<void>;
26
+ onDeleted?: (context: EntityEditorCommitContext) => Awaitable<void>;
27
+ closeOnCreated?: EntityEditorCommitDecision<EntityEditorCreatedContext>;
28
+ closeOnSaved?: EntityEditorCommitDecision<EntityEditorCommitContext>;
29
+ closeOnChanged?: EntityEditorCommitDecision<EntityEditorChangedContext>;
30
+ closeOnDeleted?: EntityEditorCommitDecision<EntityEditorCommitContext>;
31
+ };
32
+
33
+ function shouldClose<TContext>(
34
+ decision: EntityEditorCommitDecision<TContext> | undefined,
35
+ context: TContext,
36
+ ): boolean {
37
+ if (typeof decision === 'function') return decision(context);
38
+ return decision ?? true;
39
+ }
40
+
41
+ async function reloadWithStatus(
42
+ reload: UseEntityEditorCommitHandlersArgs['reload'],
43
+ ): Promise<boolean> {
44
+ return await reload() !== false;
45
+ }
46
+
47
+ export function useEntityEditorCommitHandlers(
48
+ args: UseEntityEditorCommitHandlersArgs,
49
+ ) {
50
+ async function closeIfRequested<TContext>(
51
+ decision: EntityEditorCommitDecision<TContext> | undefined,
52
+ context: TContext,
53
+ ) {
54
+ if (!args.closeDrawer || !shouldClose(decision, context)) return;
55
+ await args.closeDrawer();
56
+ }
57
+
58
+ async function handleCreated(id: string) {
59
+ const context: EntityEditorCreatedContext = {
60
+ id,
61
+ reloadSucceeded: await reloadWithStatus(args.reload),
62
+ };
63
+
64
+ await args.onCreated?.(context);
65
+ await closeIfRequested(args.closeOnCreated, context);
66
+ }
67
+
68
+ async function handleSaved() {
69
+ const context: EntityEditorCommitContext = {
70
+ reloadSucceeded: await reloadWithStatus(args.reload),
71
+ };
72
+
73
+ await args.onSaved?.(context);
74
+ await closeIfRequested(args.closeOnSaved, context);
75
+ }
76
+
77
+ async function handleChanged(reason?: EditorChangeReason | (string & {})) {
78
+ const context: EntityEditorChangedContext = {
79
+ reason,
80
+ reloadSucceeded: await reloadWithStatus(args.reload),
81
+ };
82
+
83
+ await args.onChanged?.(context);
84
+ await closeIfRequested(args.closeOnChanged, context);
85
+ }
86
+
87
+ async function handleDeleted() {
88
+ const context: EntityEditorCommitContext = {
89
+ reloadSucceeded: await reloadWithStatus(args.reload),
90
+ };
91
+
92
+ await args.onDeleted?.(context);
93
+ await closeIfRequested(args.closeOnDeleted, context);
94
+ }
95
+
96
+ return {
97
+ handleCreated,
98
+ handleSaved,
99
+ handleChanged,
100
+ handleDeleted,
101
+ };
102
+ }