@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,512 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref, watch } from 'vue';
3
+ import { useRoute, useRouter } from 'vue-router';
4
+
5
+ import NgbBadge from '../primitives/NgbBadge.vue';
6
+ import NgbIcon from '../primitives/NgbIcon.vue';
7
+ import NgbTabs from '../primitives/NgbTabs.vue';
8
+ import { useMetadataStore } from '../metadata/store';
9
+ import type { DocumentTypeMetadata } from '../metadata/types';
10
+ import { useToasts } from '../primitives/toast';
11
+ import NgbRegisterGrid from '../components/register/NgbRegisterGrid.vue';
12
+ import NgbPageHeader from '../site/NgbPageHeader.vue';
13
+ import { copyAppLink } from '../router/shareLink';
14
+ import { currentRouteBackTarget, navigateBack, resolveBackTarget, withBackTarget } from '../router/backNavigation';
15
+ import { toErrorMessage } from '../utils/errorMessage';
16
+ import { isEmptyGuid, isNonEmptyGuid, shortGuid } from '../utils/guid';
17
+
18
+ import { getConfiguredNgbEditor, resolveNgbEditorEffectsBehavior } from './config';
19
+ import {
20
+ buildDocumentEffectsPageUrl,
21
+ resolveDocumentReopenTarget,
22
+ } from './documentNavigation';
23
+ import {
24
+ collectAccountingEntryAccountIds,
25
+ finalizeEffectDimensionSummary,
26
+ resolveEffectAccountLabel as resolveDefaultEffectAccountLabel,
27
+ } from './documentEffects';
28
+ import { formatOccurredAtUtcValue } from './documentEffectsDateFormatting';
29
+ import { documentStatusLabel, documentStatusTone } from './documentStatus';
30
+ import type {
31
+ DocumentEffects,
32
+ DocumentRecord,
33
+ EffectAccount,
34
+ EffectDimensionValue,
35
+ EffectResourceValue,
36
+ } from './types';
37
+
38
+ const route = useRoute();
39
+ const router = useRouter();
40
+ const toasts = useToasts();
41
+ const metadataStore = useMetadataStore();
42
+ const editorConfig = getConfiguredNgbEditor();
43
+ const lookupStore = editorConfig.lookupStore ?? null;
44
+
45
+ const loading = ref(false);
46
+ const error = ref<string | null>(null);
47
+ const metadata = ref<DocumentTypeMetadata | null>(null);
48
+ const document = ref<DocumentRecord | null>(null);
49
+ const effects = ref<DocumentEffects | null>(null);
50
+ const activeTab = ref<'accounting' | 'or' | 'rr'>('accounting');
51
+ let loadSequence = 0;
52
+
53
+ const documentType = computed(() => String(route.params.documentType ?? '').trim());
54
+ const documentId = computed(() => String(route.params.id ?? '').trim());
55
+
56
+ const documentRoute = computed(() => {
57
+ if (!documentType.value || !documentId.value) return '/';
58
+ return resolveDocumentReopenTarget(route, documentType.value, documentId.value);
59
+ });
60
+
61
+ const shareRoute = computed(() => {
62
+ if (!documentType.value || !documentId.value) return '/';
63
+ return buildDocumentEffectsPageUrl(documentType.value, documentId.value);
64
+ });
65
+
66
+ const tabs = computed(() => [
67
+ { key: 'accounting', label: `Accounting Entries (${effects.value?.accountingEntries?.length ?? 0})` },
68
+ { key: 'or', label: `Operational Registers (${effects.value?.operationalRegisterMovements?.length ?? 0})` },
69
+ { key: 'rr', label: `Reference Registers (${effects.value?.referenceRegisterWrites?.length ?? 0})` },
70
+ ]);
71
+
72
+ const title = computed(() => {
73
+ const display = String(document.value?.display ?? '').trim();
74
+ if (display) return display;
75
+ return metadata.value?.displayName ?? 'Document effects';
76
+ });
77
+
78
+ const statusTone = computed<'neutral' | 'success' | 'warn'>(() => documentStatusTone(document.value?.status));
79
+ const statusLabel = computed(() => documentStatusLabel(document.value?.status));
80
+ const canShareLink = computed(() => !!documentType.value && !!documentId.value);
81
+
82
+ const accountingColumns = computed(() => [
83
+ { key: 'occurred', title: 'Occurred', width: 190, pinned: 'left' as const },
84
+ { key: 'account', title: 'Account', width: 320 },
85
+ { key: 'debit', title: 'Debit', width: 140, align: 'right' as const },
86
+ { key: 'credit', title: 'Credit', width: 140, align: 'right' as const },
87
+ { key: 'dimensionSet', title: 'Dimension Set' },
88
+ ]);
89
+
90
+ const operationalColumns = computed(() => [
91
+ { key: 'occurred', title: 'Occurred', width: 190, pinned: 'left' as const },
92
+ { key: 'register', title: 'Operational Register', width: 260 },
93
+ { key: 'dimensionSet', title: 'Dimension Set', width: 320 },
94
+ { key: 'resources', title: 'Resources' },
95
+ ]);
96
+
97
+ const referenceColumns = computed(() => [
98
+ { key: 'recorded', title: 'Recorded', width: 190, pinned: 'left' as const },
99
+ { key: 'register', title: 'Reference Register', width: 260 },
100
+ { key: 'status', title: 'Status', width: 140 },
101
+ { key: 'dimensionSet', title: 'Dimension Set', width: 320 },
102
+ { key: 'fields', title: 'Fields' },
103
+ ]);
104
+
105
+ const accountingRows = computed(() => {
106
+ const items = effects.value?.accountingEntries ?? [];
107
+ return items.flatMap((item) => {
108
+ const occurred = formatUtc(item.occurredAtUtc);
109
+ const debitRow = {
110
+ key: `${effectKey(item.entryId)}:debit`,
111
+ occurred,
112
+ account: resolveEffectAccountLabel(item.debitAccount, item.debitAccountId),
113
+ debit: formatMoney(item.amount),
114
+ credit: '—',
115
+ dimensionSet: formatDimensionSummary(item.debitDimensions, item.debitDimensionSetId),
116
+ __status: 'posted',
117
+ };
118
+
119
+ const creditRow = {
120
+ key: `${effectKey(item.entryId)}:credit`,
121
+ occurred,
122
+ account: resolveEffectAccountLabel(item.creditAccount, item.creditAccountId),
123
+ debit: '—',
124
+ credit: formatMoney(item.amount),
125
+ dimensionSet: formatDimensionSummary(item.creditDimensions, item.creditDimensionSetId),
126
+ __status: 'posted',
127
+ };
128
+
129
+ return [debitRow, creditRow];
130
+ });
131
+ });
132
+
133
+ const operationalRows = computed(() => {
134
+ const items = effects.value?.operationalRegisterMovements ?? [];
135
+ return items.map((item) => ({
136
+ key: effectKey(item.movementId),
137
+ occurred: formatUtc(item.occurredAtUtc),
138
+ register: formatRegisterName(item.registerName, item.registerCode),
139
+ dimensionSet: formatDimensionSummary(item.dimensions, item.dimensionSetId),
140
+ resources: formatResourceSummary(item.resources),
141
+ __status: 'posted',
142
+ }));
143
+ });
144
+
145
+ const referenceRows = computed(() => {
146
+ const items = effects.value?.referenceRegisterWrites ?? [];
147
+ return items.map((item) => ({
148
+ key: effectKey(item.recordId),
149
+ recorded: formatUtc(item.recordedAtUtc),
150
+ register: formatRegisterName(item.registerName, item.registerCode),
151
+ status: item.isTombstone ? 'Tombstone' : 'Write',
152
+ dimensionSet: formatDimensionSummary(item.dimensions, item.dimensionSetId),
153
+ fields: formatFieldSummary(item.fields),
154
+ __status: item.isTombstone ? 'marked' : 'posted',
155
+ }));
156
+ });
157
+
158
+ function effectKey(value: string | number | null | undefined): string {
159
+ return String(value ?? '');
160
+ }
161
+
162
+ function formatUtc(value: string | null | undefined): string {
163
+ if (!value) return '—';
164
+ return formatOccurredAtUtcValue(String(value));
165
+ }
166
+
167
+ function formatMoney(value: number | null | undefined): string {
168
+ const amount = Number(value ?? 0);
169
+ return amount.toLocaleString(undefined, {
170
+ minimumFractionDigits: 2,
171
+ maximumFractionDigits: 2,
172
+ });
173
+ }
174
+
175
+ function formatScalar(value: unknown): string {
176
+ if (value == null) return '—';
177
+ if (typeof value === 'string') return value.trim() || '—';
178
+ if (typeof value === 'number') return Number.isFinite(value) ? formatMoney(value) : String(value);
179
+ if (typeof value === 'boolean') return value ? 'Yes' : 'No';
180
+ if (Array.isArray(value)) return value.map((x) => formatScalar(x)).join(' · ');
181
+ if (typeof value === 'object') {
182
+ const entries = Object.entries(value as Record<string, unknown>);
183
+ if (entries.length === 0) return '—';
184
+ return entries.map(([key, item]) => `${humanizeCode(key)}: ${formatScalar(item)}`).join(' · ');
185
+ }
186
+ return String(value);
187
+ }
188
+
189
+ function formatRegisterName(name?: string | null, code?: string | null): string {
190
+ const label = String(name ?? '').trim();
191
+ if (label) return label;
192
+ return humanizeCode(code);
193
+ }
194
+
195
+ function defaultResolveDimensionDisplay(item: EffectDimensionValue | null | undefined): string {
196
+ const display = String(item?.display ?? '').trim();
197
+ if (display) return display;
198
+ const valueId = String(item?.valueId ?? '').trim();
199
+ return valueId ? shortGuid(valueId) : '—';
200
+ }
201
+
202
+ function resolveDimensionDisplay(item: EffectDimensionValue | null | undefined): string {
203
+ return resolveNgbEditorEffectsBehavior().resolveDimensionDisplay?.({
204
+ item,
205
+ lookupStore,
206
+ }) ?? defaultResolveDimensionDisplay(item);
207
+ }
208
+
209
+ function resolveEffectAccountLabel(account: EffectAccount | null | undefined, accountId: string | null | undefined): string {
210
+ return resolveNgbEditorEffectsBehavior().resolveAccountLabel?.({
211
+ account,
212
+ accountId,
213
+ lookupStore,
214
+ }) ?? resolveDefaultEffectAccountLabel(account, accountId, lookupStore ? lookupStore.labelForCoa.bind(lookupStore) : null);
215
+ }
216
+
217
+ function formatDimensionSummary(
218
+ values: EffectDimensionValue[] | null | undefined,
219
+ fallbackDimensionSetId?: string | null,
220
+ ): string | string[] {
221
+ return finalizeEffectDimensionSummary(
222
+ (values ?? []).map((item) => resolveDimensionDisplay(item)),
223
+ fallbackDimensionSetId,
224
+ );
225
+ }
226
+
227
+ function formatResourceSummary(value: EffectResourceValue[] | Record<string, unknown> | null | undefined): string | string[] {
228
+ if (Array.isArray(value)) {
229
+ const parts = value
230
+ .map((item) => {
231
+ const code = humanizeCode(item?.code);
232
+ const formatted = formatMoney(Number(item?.value ?? 0));
233
+ return code ? `${code}: ${formatted}` : formatted;
234
+ })
235
+ .filter((item) => !!item);
236
+
237
+ return parts.length > 0 ? parts : '—';
238
+ }
239
+
240
+ if (value && typeof value === 'object') {
241
+ const parts = Object.entries(value)
242
+ .map(([key, item]) => `${humanizeCode(key)}: ${formatScalar(item)}`)
243
+ .filter((item) => !!item);
244
+
245
+ return parts.length > 0 ? parts : '—';
246
+ }
247
+
248
+ return '—';
249
+ }
250
+
251
+ function formatFieldSummary(fields: Record<string, unknown> | null | undefined): string | string[] {
252
+ if (!fields || typeof fields !== 'object') return '—';
253
+
254
+ const parts = Object.entries(fields)
255
+ .map(([key, value]) => {
256
+ const label = key.endsWith('_document_id')
257
+ ? humanizeCode(key.slice(0, -3))
258
+ : humanizeCode(key)
259
+ const resolved = resolveNgbEditorEffectsBehavior().resolveFieldValue?.({
260
+ documentType: documentType.value,
261
+ documentId: documentId.value,
262
+ document: document.value,
263
+ fieldKey: key,
264
+ value,
265
+ fields,
266
+ lookupStore,
267
+ })
268
+
269
+ return `${label}: ${resolved ?? formatScalar(value)}`
270
+ })
271
+ .filter((item) => !!item);
272
+
273
+ return parts.length > 0 ? parts : '—';
274
+ }
275
+
276
+ function humanizeCode(value: string | null | undefined): string {
277
+ const raw = String(value ?? '').trim();
278
+ if (!raw) return '';
279
+
280
+ const last = raw.split('.').pop() ?? raw;
281
+ return last
282
+ .split(/[_\-\s]+/g)
283
+ .filter((part) => !!part)
284
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
285
+ .join(' ');
286
+ }
287
+
288
+ function openSourceDocument(): void {
289
+ if (!documentType.value || !documentId.value) return;
290
+ void router.push(documentRoute.value);
291
+ }
292
+
293
+ async function goBack(): Promise<void> {
294
+ const explicitBack = resolveBackTarget(route);
295
+ if (explicitBack && explicitBack !== documentRoute.value) {
296
+ await router.replace(documentRoute.value);
297
+ return;
298
+ }
299
+
300
+ await navigateBack(router, route, documentRoute.value);
301
+ }
302
+
303
+ async function copyShare(): Promise<void> {
304
+ if (!canShareLink.value) return;
305
+ await copyAppLink(router, toasts, shareRoute.value, {
306
+ title: 'Effects link copied',
307
+ message: 'Shareable effects page link copied to clipboard.',
308
+ });
309
+ }
310
+
311
+ async function refreshPage(): Promise<void> {
312
+ await load();
313
+ }
314
+
315
+ async function prefetchDefaultAccountLabels(snapshot: DocumentEffects | null | undefined): Promise<void> {
316
+ if (!lookupStore) return;
317
+ const ids = collectAccountingEntryAccountIds(snapshot);
318
+ if (ids.length === 0) return;
319
+ await lookupStore.ensureCoaLabels(ids);
320
+ }
321
+
322
+ async function load(): Promise<void> {
323
+ if (!documentType.value || !documentId.value) {
324
+ loadSequence += 1;
325
+ error.value = 'Document type or id is missing.';
326
+ metadata.value = null;
327
+ document.value = null;
328
+ effects.value = null;
329
+ loading.value = false;
330
+ return;
331
+ }
332
+
333
+ const seq = ++loadSequence;
334
+ loading.value = true;
335
+ error.value = null;
336
+ try {
337
+ const [meta, doc, effectsSnapshot] = await Promise.all([
338
+ metadataStore.ensureDocumentType(documentType.value),
339
+ editorConfig.loadDocumentById(documentType.value, documentId.value),
340
+ editorConfig.loadDocumentEffects(documentType.value, documentId.value),
341
+ ]);
342
+
343
+ const behavior = resolveNgbEditorEffectsBehavior();
344
+ const ancillaryTasks: Promise<unknown>[] = [
345
+ Promise.resolve().then(() => prefetchDefaultAccountLabels(effectsSnapshot)),
346
+ ];
347
+
348
+ if (behavior.prefetchRelatedLabels) {
349
+ ancillaryTasks.push(
350
+ Promise.resolve().then(() => behavior.prefetchRelatedLabels?.({
351
+ documentType: documentType.value,
352
+ documentId: documentId.value,
353
+ effects: effectsSnapshot,
354
+ lookupStore,
355
+ })),
356
+ );
357
+ }
358
+
359
+ await Promise.allSettled(ancillaryTasks);
360
+
361
+ if (seq !== loadSequence) return;
362
+
363
+ metadata.value = meta;
364
+ document.value = doc;
365
+ effects.value = effectsSnapshot;
366
+ } catch (cause) {
367
+ if (seq !== loadSequence) return;
368
+ error.value = toErrorMessage(cause, 'Could not load document effects.');
369
+ metadata.value = null;
370
+ document.value = null;
371
+ effects.value = null;
372
+ } finally {
373
+ if (seq === loadSequence) loading.value = false;
374
+ }
375
+ }
376
+
377
+ watch(
378
+ () => [documentType.value, documentId.value],
379
+ () => {
380
+ void load();
381
+ },
382
+ { immediate: true },
383
+ );
384
+ </script>
385
+
386
+ <template>
387
+ <div class="h-full min-h-0 flex flex-col bg-ngb-bg">
388
+ <NgbPageHeader :title="title" can-back @back="goBack">
389
+ <template #secondary>
390
+ <div class="flex min-w-0 items-center">
391
+ <NgbBadge :tone="statusTone">{{ statusLabel }}</NgbBadge>
392
+ </div>
393
+ </template>
394
+
395
+ <template #actions>
396
+ <button
397
+ class="ngb-iconbtn"
398
+ :disabled="loading || !documentId"
399
+ title="Open document"
400
+ @click="openSourceDocument"
401
+ >
402
+ <NgbIcon name="edit" />
403
+ </button>
404
+
405
+ <button
406
+ v-if="canShareLink"
407
+ class="ngb-iconbtn"
408
+ :disabled="loading"
409
+ title="Share link"
410
+ @click="copyShare"
411
+ >
412
+ <NgbIcon name="share" />
413
+ </button>
414
+
415
+ <button
416
+ class="ngb-iconbtn"
417
+ :disabled="loading"
418
+ title="Refresh"
419
+ @click="refreshPage"
420
+ >
421
+ <NgbIcon name="refresh" />
422
+ </button>
423
+ </template>
424
+ </NgbPageHeader>
425
+
426
+ <div class="p-6 pt-4 flex-1 min-h-0 flex flex-col">
427
+ <div v-if="loading" class="rounded-[var(--ngb-radius)] border border-ngb-border bg-ngb-card p-6 text-sm text-ngb-muted flex-1">
428
+ Loading accounting entries / effects…
429
+ </div>
430
+
431
+ <div v-else-if="error" class="rounded-[var(--ngb-radius)] border border-red-200 bg-red-50 p-4 text-sm text-ngb-danger dark:border-red-900/50 dark:bg-red-950/30">
432
+ {{ error }}
433
+ </div>
434
+
435
+ <NgbTabs v-else v-model="activeTab" :tabs="tabs" fill class="flex-1 min-h-0">
436
+ <template #default="{ active }">
437
+ <section v-if="active === 'accounting'" class="h-full min-h-0 flex flex-col">
438
+ <div
439
+ v-if="!accountingRows.length"
440
+ class="rounded-[var(--ngb-radius)] border border-ngb-border bg-ngb-card p-6 text-sm text-ngb-muted flex-1"
441
+ >
442
+ No accounting entries were returned for this document.
443
+ </div>
444
+
445
+ <div v-else class="flex-1 min-h-0">
446
+ <NgbRegisterGrid
447
+ class="h-full min-h-0"
448
+ fill-height
449
+ :show-panel="false"
450
+ :columns="accountingColumns"
451
+ :rows="accountingRows"
452
+ :show-totals="false"
453
+ :storage-key="`ngb:document-effects:${documentType}:accounting`"
454
+ :row-height-px="80"
455
+ :activate-on-row-click="true"
456
+ @rowActivate="openSourceDocument"
457
+ />
458
+ </div>
459
+ </section>
460
+
461
+ <section v-else-if="active === 'or'" class="h-full min-h-0 flex flex-col">
462
+ <div
463
+ v-if="!operationalRows.length"
464
+ class="rounded-[var(--ngb-radius)] border border-ngb-border bg-ngb-card p-6 text-sm text-ngb-muted flex-1"
465
+ >
466
+ No operational register movements were returned for this document.
467
+ </div>
468
+
469
+ <div v-else class="flex-1 min-h-0">
470
+ <NgbRegisterGrid
471
+ class="h-full min-h-0"
472
+ fill-height
473
+ :show-panel="false"
474
+ :columns="operationalColumns"
475
+ :rows="operationalRows"
476
+ :show-totals="false"
477
+ :storage-key="`ngb:document-effects:${documentType}:or`"
478
+ :row-height-px="80"
479
+ :activate-on-row-click="true"
480
+ @rowActivate="openSourceDocument"
481
+ />
482
+ </div>
483
+ </section>
484
+
485
+ <section v-else class="h-full min-h-0 flex flex-col">
486
+ <div
487
+ v-if="!referenceRows.length"
488
+ class="rounded-[var(--ngb-radius)] border border-ngb-border bg-ngb-card p-6 text-sm text-ngb-muted"
489
+ >
490
+ No reference register writes were returned for this document.
491
+ </div>
492
+
493
+ <div v-else class="flex-1 min-h-0">
494
+ <NgbRegisterGrid
495
+ class="h-full min-h-0"
496
+ fill-height
497
+ :show-panel="false"
498
+ :columns="referenceColumns"
499
+ :rows="referenceRows"
500
+ :show-totals="false"
501
+ :storage-key="`ngb:document-effects:${documentType}:rr`"
502
+ :row-height-px="80"
503
+ :activate-on-row-click="true"
504
+ @rowActivate="openSourceDocument"
505
+ />
506
+ </div>
507
+ </section>
508
+ </template>
509
+ </NgbTabs>
510
+ </div>
511
+ </div>
512
+ </template>