@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,173 @@
1
+ import { computed, getCurrentInstance, inject } from 'vue';
2
+ import {
3
+ matchedRouteKey,
4
+ onBeforeRouteUpdate,
5
+ type LocationQuery,
6
+ type RouteLocationNormalizedLoaded,
7
+ type Router,
8
+ } from 'vue-router';
9
+
10
+ import type { Awaitable } from '../metadata/types';
11
+ import {
12
+ normalizeSingleQueryValue,
13
+ pushCleanRouteQuery,
14
+ replaceCleanRouteQuery,
15
+ } from '../router/queryParams';
16
+
17
+ export type RouteQueryEditorDrawerMode = 'new' | 'edit' | null;
18
+
19
+ export type RouteQueryEditorDrawerState = {
20
+ mode: RouteQueryEditorDrawerMode;
21
+ id: string | null;
22
+ };
23
+
24
+ export type RouteQueryEditorDrawerOpenState = {
25
+ mode: 'new' | 'edit';
26
+ id: string | null;
27
+ };
28
+
29
+ export type RouteQueryEditorDrawerMutationOptions = {
30
+ patch?: Record<string, unknown>;
31
+ onCommit?: () => void;
32
+ };
33
+
34
+ export type UseRouteQueryEditorDrawerArgs = {
35
+ route: RouteLocationNormalizedLoaded;
36
+ router: Router;
37
+ panelKey?: string;
38
+ idKey?: string;
39
+ clearKeys?: string[];
40
+ idImpliesEdit?: boolean;
41
+ onBeforeOpen?: (
42
+ next: RouteQueryEditorDrawerOpenState,
43
+ current: RouteQueryEditorDrawerState,
44
+ ) => Awaitable<boolean>;
45
+ onBeforeClose?: (
46
+ current: RouteQueryEditorDrawerState,
47
+ next: RouteQueryEditorDrawerState,
48
+ ) => Awaitable<boolean>;
49
+ };
50
+
51
+ export function useRouteQueryEditorDrawer(args: UseRouteQueryEditorDrawerArgs) {
52
+ const panelKey = args.panelKey ?? 'panel';
53
+ const idKey = args.idKey ?? 'id';
54
+ const clearKeys = args.clearKeys ?? [];
55
+ let suppressedRouteGuards = 0;
56
+
57
+ function resolveState(query: LocationQuery): RouteQueryEditorDrawerState {
58
+ const id = normalizeSingleQueryValue(query[idKey]) || null;
59
+ const panel = normalizeSingleQueryValue(query[panelKey]).toLowerCase();
60
+ if (panel === 'new') return { mode: 'new', id: null };
61
+ if (panel === 'edit' && id) return { mode: 'edit', id };
62
+ if (args.idImpliesEdit && id) return { mode: 'edit', id };
63
+ return { mode: null, id: null };
64
+ }
65
+
66
+ function sameState(left: RouteQueryEditorDrawerState, right: RouteQueryEditorDrawerState) {
67
+ return left.mode === right.mode && left.id === right.id;
68
+ }
69
+
70
+ const panelState = computed(() => resolveState(args.route.query));
71
+ const panelId = computed(() => panelState.value.id);
72
+ const panelMode = computed<RouteQueryEditorDrawerMode>(() => panelState.value.mode);
73
+ const currentId = computed(() => (panelMode.value === 'edit' ? panelId.value : null));
74
+ const isPanelOpen = computed(() => panelMode.value !== null);
75
+
76
+ function currentState(): RouteQueryEditorDrawerState {
77
+ return panelState.value;
78
+ }
79
+
80
+ async function commitRouteMutation(run: () => Promise<unknown>) {
81
+ suppressedRouteGuards += 1;
82
+ try {
83
+ await run();
84
+ } finally {
85
+ suppressedRouteGuards = Math.max(0, suppressedRouteGuards - 1);
86
+ }
87
+ }
88
+
89
+ function buildPatch(
90
+ mode: RouteQueryEditorDrawerMode,
91
+ id: string | null,
92
+ patch?: Record<string, unknown>,
93
+ ) {
94
+ const nextPatch: Record<string, unknown> = {};
95
+ for (const key of clearKeys) nextPatch[key] = undefined;
96
+ nextPatch[panelKey] = mode ?? undefined;
97
+ nextPatch[idKey] = mode === 'edit' ? id ?? undefined : undefined;
98
+ return {
99
+ ...nextPatch,
100
+ ...(patch ?? {}),
101
+ };
102
+ }
103
+
104
+ async function openCreateDrawer(options: RouteQueryEditorDrawerMutationOptions = {}): Promise<boolean> {
105
+ const current = currentState();
106
+ if (current.mode === 'new') return false;
107
+
108
+ const next: RouteQueryEditorDrawerOpenState = { mode: 'new', id: null };
109
+ if (await args.onBeforeOpen?.(next, current) === false) return false;
110
+
111
+ options.onCommit?.();
112
+ await commitRouteMutation(async () => {
113
+ await pushCleanRouteQuery(args.route, args.router, buildPatch('new', null, options.patch));
114
+ });
115
+ return true;
116
+ }
117
+
118
+ async function openEditDrawer(
119
+ id: string,
120
+ options: RouteQueryEditorDrawerMutationOptions = {},
121
+ ): Promise<boolean> {
122
+ const normalizedId = String(id ?? '').trim();
123
+ if (!normalizedId) return false;
124
+
125
+ const current = currentState();
126
+ if (current.mode === 'edit' && current.id === normalizedId) return false;
127
+
128
+ const next: RouteQueryEditorDrawerOpenState = { mode: 'edit', id: normalizedId };
129
+ if (await args.onBeforeOpen?.(next, current) === false) return false;
130
+
131
+ options.onCommit?.();
132
+ await commitRouteMutation(async () => {
133
+ await pushCleanRouteQuery(args.route, args.router, buildPatch('edit', normalizedId, options.patch));
134
+ });
135
+ return true;
136
+ }
137
+
138
+ async function closeDrawer(options: RouteQueryEditorDrawerMutationOptions = {}): Promise<void> {
139
+ options.onCommit?.();
140
+ await commitRouteMutation(async () => {
141
+ await replaceCleanRouteQuery(args.route, args.router, buildPatch(null, null, options.patch));
142
+ });
143
+ }
144
+
145
+ const activeMatchedRoute = getCurrentInstance() ? inject(matchedRouteKey, null) : null;
146
+
147
+ if (activeMatchedRoute) {
148
+ onBeforeRouteUpdate(async (to) => {
149
+ if (suppressedRouteGuards > 0) return true;
150
+
151
+ const current = currentState();
152
+ const next = resolveState(to.query);
153
+ if (sameState(current, next)) return true;
154
+
155
+ if (next.mode === null) {
156
+ if (current.mode === null) return true;
157
+ return await args.onBeforeClose?.(current, next) !== false;
158
+ }
159
+
160
+ return await args.onBeforeOpen?.(next, current) !== false;
161
+ });
162
+ }
163
+
164
+ return {
165
+ panelId,
166
+ panelMode,
167
+ currentId,
168
+ isPanelOpen,
169
+ openCreateDrawer,
170
+ openEditDrawer,
171
+ closeDrawer,
172
+ };
173
+ }
@@ -0,0 +1,25 @@
1
+ type RuntimeConfigValue = string | boolean | number | null | undefined
2
+
3
+ declare global {
4
+ interface Window {
5
+ __NGB_RUNTIME_CONFIG__?: Record<string, RuntimeConfigValue>
6
+ }
7
+ }
8
+
9
+ function readWindowRuntimeConfig(): Record<string, RuntimeConfigValue> | null {
10
+ if (typeof window === 'undefined') return null
11
+
12
+ const runtimeConfig = window.__NGB_RUNTIME_CONFIG__
13
+ if (!runtimeConfig || typeof runtimeConfig !== 'object') return null
14
+
15
+ return runtimeConfig
16
+ }
17
+
18
+ export function readAppEnv(name: string): string {
19
+ const runtimeConfig = readWindowRuntimeConfig()
20
+ if (runtimeConfig && Object.prototype.hasOwnProperty.call(runtimeConfig, name)) {
21
+ return String(runtimeConfig[name] ?? '').trim()
22
+ }
23
+
24
+ return String(import.meta.env[name as keyof ImportMetaEnv] ?? '').trim()
25
+ }
@@ -0,0 +1,42 @@
1
+ import type { Awaitable, LookupItem } from '../metadata/types'
2
+
3
+ export type LookupSearchOptions = {
4
+ filters?: Record<string, string>
5
+ }
6
+
7
+ export type ResolvedDocumentLookupItem = LookupItem & {
8
+ documentType: string
9
+ }
10
+
11
+ export type LookupFrameworkConfig = {
12
+ loadCatalogItemsByIds: (catalogType: string, ids: string[]) => Awaitable<LookupItem[]>
13
+ searchCatalog: (catalogType: string, query: string, options?: LookupSearchOptions) => Awaitable<LookupItem[]>
14
+ loadCoaItem: (id: string) => Awaitable<LookupItem | null>
15
+ loadCoaItemsByIds: (ids: string[]) => Awaitable<LookupItem[]>
16
+ searchCoa: (query: string) => Awaitable<LookupItem[]>
17
+ loadDocumentItem: (documentType: string, id: string) => Awaitable<LookupItem | null>
18
+ loadDocumentItemsByIds: (documentTypes: string[], ids: string[]) => Awaitable<ResolvedDocumentLookupItem[]>
19
+ searchDocument: (documentType: string, query: string) => Awaitable<LookupItem[]>
20
+ searchDocumentsAcrossTypes: (documentTypes: string[], query: string) => Awaitable<ResolvedDocumentLookupItem[]>
21
+ buildCatalogUrl: (catalogType: string, id: string) => string
22
+ buildCoaUrl: (id: string) => string
23
+ buildDocumentUrl: (documentType: string, id: string) => string
24
+ }
25
+
26
+ let lookupFrameworkConfig: LookupFrameworkConfig | null = null
27
+
28
+ export function configureNgbLookup(config: LookupFrameworkConfig): void {
29
+ lookupFrameworkConfig = config
30
+ }
31
+
32
+ export function getConfiguredNgbLookup(): LookupFrameworkConfig {
33
+ if (!lookupFrameworkConfig) {
34
+ throw new Error('NGB lookup framework is not configured. Call configureNgbLookup(...) during app bootstrap.')
35
+ }
36
+
37
+ return lookupFrameworkConfig
38
+ }
39
+
40
+ export function maybeGetConfiguredNgbLookup(): LookupFrameworkConfig | null {
41
+ return lookupFrameworkConfig
42
+ }
@@ -0,0 +1,136 @@
1
+ import type {
2
+ DocumentLookupDto,
3
+ LookupItemDto,
4
+ } from '../api/contracts'
5
+ import { getCatalogPage } from '../api/catalogs'
6
+ import {
7
+ getDocumentById,
8
+ getDocumentLookupByIds,
9
+ getDocumentPage,
10
+ lookupDocumentsAcrossTypes,
11
+ } from '../api/documents'
12
+ import { getCatalogLookupByIds, lookupCatalog } from '../api/lookups'
13
+ import {
14
+ getChartOfAccountById,
15
+ getChartOfAccountsByIds,
16
+ getChartOfAccountsPage,
17
+ } from '../accounting/api'
18
+ import { buildChartOfAccountsPath } from '../accounting/navigation'
19
+ import { buildCatalogFullPageUrl } from '../editor/catalogNavigation'
20
+ import { buildDocumentFullPageUrl } from '../editor/documentNavigation'
21
+ import type { LookupItem } from '../metadata/types'
22
+ import { shortGuid } from '../utils/guid'
23
+ import type { LookupFrameworkConfig, ResolvedDocumentLookupItem } from './config'
24
+
25
+ function toMetaString(meta: LookupItemDto['meta'] | string | undefined | null): string | undefined {
26
+ if (!meta) return undefined
27
+ if (typeof meta === 'string') return meta
28
+ if (typeof meta === 'object') {
29
+ const pairs = Object.entries(meta)
30
+ .slice(0, 4)
31
+ .map(([key, value]) => `${key}: ${String(value)}`)
32
+ return pairs.length > 0 ? pairs.join(' · ') : undefined
33
+ }
34
+ return String(meta)
35
+ }
36
+
37
+ function asLookupItem(item: { id: string; label: string; meta?: string | undefined }): LookupItem {
38
+ return {
39
+ id: item.id,
40
+ label: item.label,
41
+ meta: item.meta,
42
+ }
43
+ }
44
+
45
+ function asResolvedDocumentLookupItem(item: DocumentLookupDto): ResolvedDocumentLookupItem {
46
+ const label = String(item.display ?? '').trim() || shortGuid(item.id)
47
+ return {
48
+ id: item.id,
49
+ label,
50
+ documentType: item.documentType,
51
+ }
52
+ }
53
+
54
+ function mapLookupItemDto(item: LookupItemDto): LookupItem {
55
+ return asLookupItem({
56
+ id: item.id,
57
+ label: item.label,
58
+ meta: toMetaString(item.meta),
59
+ })
60
+ }
61
+
62
+ export function createDefaultNgbLookupConfig(): LookupFrameworkConfig {
63
+ return {
64
+ loadCatalogItemsByIds: async (catalogType, ids) => (await getCatalogLookupByIds(catalogType, ids)).map(mapLookupItemDto),
65
+ searchCatalog: async (catalogType, query, options) => {
66
+ const filters = options?.filters ?? null
67
+
68
+ if (!filters || Object.keys(filters).length === 0) {
69
+ return (await lookupCatalog(catalogType, query, 25)).map(mapLookupItemDto)
70
+ }
71
+
72
+ const page = await getCatalogPage(catalogType, {
73
+ offset: 0,
74
+ limit: 25,
75
+ search: query,
76
+ filters: {
77
+ deleted: 'active',
78
+ ...filters,
79
+ },
80
+ })
81
+
82
+ return (page.items ?? []).map((item) =>
83
+ asLookupItem({
84
+ id: item.id,
85
+ label: item.display ?? shortGuid(item.id),
86
+ }))
87
+ },
88
+ loadCoaItemsByIds: async (ids) => (await getChartOfAccountsByIds(ids)).map(mapLookupItemDto),
89
+ loadCoaItem: async (id) => {
90
+ const account = await getChartOfAccountById(id)
91
+ return asLookupItem({
92
+ id: account.accountId,
93
+ label: `${account.code} — ${account.name}`,
94
+ })
95
+ },
96
+ searchCoa: async (query) => {
97
+ const page = await getChartOfAccountsPage({ search: query, limit: 25, onlyActive: true, includeDeleted: false })
98
+ return (page.items ?? []).map((account) =>
99
+ asLookupItem({
100
+ id: account.accountId,
101
+ label: `${account.code} — ${account.name}`,
102
+ }))
103
+ },
104
+ loadDocumentItemsByIds: async (documentTypes, ids) => {
105
+ if (documentTypes.length === 0 || ids.length === 0) return []
106
+ return (await getDocumentLookupByIds({ documentTypes, ids })).map(asResolvedDocumentLookupItem)
107
+ },
108
+ searchDocumentsAcrossTypes: async (documentTypes, query) => {
109
+ if (documentTypes.length === 0) return []
110
+ return (await lookupDocumentsAcrossTypes({
111
+ documentTypes,
112
+ query,
113
+ perTypeLimit: 25,
114
+ activeOnly: true,
115
+ })).map(asResolvedDocumentLookupItem)
116
+ },
117
+ loadDocumentItem: async (documentType, id) => {
118
+ const document = await getDocumentById(documentType, id)
119
+ return asLookupItem({
120
+ id: document.id,
121
+ label: document.display ?? shortGuid(document.id),
122
+ })
123
+ },
124
+ searchDocument: async (documentType, query) => {
125
+ const page = await getDocumentPage(documentType, { offset: 0, limit: 25, search: query })
126
+ return (page.items ?? []).map((document) =>
127
+ asLookupItem({
128
+ id: document.id,
129
+ label: document.display ?? shortGuid(document.id),
130
+ }))
131
+ },
132
+ buildCatalogUrl: (catalogType, id) => buildCatalogFullPageUrl(catalogType, id),
133
+ buildCoaUrl: (id) => buildChartOfAccountsPath({ panel: 'edit', id }),
134
+ buildDocumentUrl: (documentType, id) => buildDocumentFullPageUrl(documentType, id),
135
+ }
136
+ }
@@ -0,0 +1,76 @@
1
+ import { currentRouteBackTarget, withBackTarget } from '../router/backNavigation'
2
+ import type { LookupHint, LookupSource } from '../metadata/types'
3
+ import { getConfiguredNgbLookup } from './config'
4
+
5
+ export type LookupValueLike =
6
+ | string
7
+ | {
8
+ id?: string | null
9
+ }
10
+ | null
11
+ | undefined
12
+
13
+ export type LookupNavigationSource = LookupHint | LookupSource
14
+
15
+ export function lookupValueId(value: LookupValueLike): string | null {
16
+ if (!value) return null
17
+
18
+ if (typeof value === 'string') {
19
+ const normalized = value.trim()
20
+ return normalized.length > 0 ? normalized : null
21
+ }
22
+
23
+ const normalized = String(value.id ?? '').trim()
24
+ return normalized.length > 0 ? normalized : null
25
+ }
26
+
27
+ async function resolveDocumentType(documentTypes: string[], id: string): Promise<string | null> {
28
+ const candidates = Array.from(new Set(documentTypes.map((entry) => String(entry ?? '').trim()).filter((entry) => entry.length > 0)))
29
+ if (candidates.length === 0) return null
30
+ if (candidates.length === 1) return candidates[0] ?? null
31
+
32
+ const config = getConfiguredNgbLookup()
33
+
34
+ try {
35
+ const items = await config.loadDocumentItemsByIds(candidates, [id])
36
+ const resolved = items.find((item) => {
37
+ const itemId = String(item.id ?? '').trim()
38
+ const documentType = String(item.documentType ?? '').trim()
39
+ return itemId === id && candidates.includes(documentType)
40
+ })
41
+
42
+ const resolvedType = String(resolved?.documentType ?? '').trim()
43
+ return resolvedType.length > 0 ? resolvedType : null
44
+ } catch {
45
+ return null
46
+ }
47
+ }
48
+
49
+ export async function buildLookupFieldTargetUrl(args: {
50
+ hint: LookupNavigationSource | null
51
+ value: LookupValueLike
52
+ route: {
53
+ fullPath: string
54
+ }
55
+ }): Promise<string | null> {
56
+ const id = lookupValueId(args.value)
57
+ const hint = args.hint
58
+
59
+ if (!id || !hint) return null
60
+
61
+ const config = getConfiguredNgbLookup()
62
+ let path: string | null = null
63
+
64
+ if (hint.kind === 'catalog') {
65
+ path = config.buildCatalogUrl(hint.catalogType, id)
66
+ } else if (hint.kind === 'coa') {
67
+ path = config.buildCoaUrl(id)
68
+ } else if (hint.kind === 'document') {
69
+ const documentType = await resolveDocumentType(hint.documentTypes, id)
70
+ if (!documentType) return null
71
+ path = config.buildDocumentUrl(documentType, id)
72
+ }
73
+
74
+ if (!path) return null
75
+ return withBackTarget(path, currentRouteBackTarget(args.route))
76
+ }
@@ -0,0 +1,40 @@
1
+ import { ensureResolvedLookupLabels } from '../metadata/filtering'
2
+ import type { LookupHint, LookupSource, LookupStoreApi, RecordFields } from '../metadata/types'
3
+ import { isNonEmptyGuid } from '../utils/guid'
4
+
5
+ type LookupPrefetchColumn = {
6
+ key: string
7
+ lookup?: LookupSource | null
8
+ }
9
+
10
+ type LookupPrefetchItem = {
11
+ payload?: {
12
+ fields?: RecordFields | null
13
+ } | null
14
+ }
15
+
16
+ export async function prefetchLookupsForPage(args: {
17
+ entityTypeCode: string
18
+ columns: readonly LookupPrefetchColumn[]
19
+ items: readonly LookupPrefetchItem[]
20
+ lookupStore: LookupStoreApi
21
+ resolveLookupHint: (entityTypeCode: string, fieldKey: string, lookup?: LookupSource | null) => LookupHint | null
22
+ }) {
23
+ const tasks: Promise<void>[] = []
24
+
25
+ for (const column of args.columns) {
26
+ const hint = args.resolveLookupHint(args.entityTypeCode, column.key, column.lookup)
27
+ if (!hint) continue
28
+
29
+ const ids = args.items
30
+ .map((item) => item.payload?.fields?.[column.key])
31
+ .filter(isNonEmptyGuid)
32
+
33
+ if (ids.length === 0) continue
34
+ tasks.push(Promise.resolve().then(() => ensureResolvedLookupLabels(args.lookupStore, hint, ids)))
35
+ }
36
+
37
+ if (tasks.length > 0) {
38
+ await Promise.allSettled(tasks)
39
+ }
40
+ }