@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.
- package/LICENSE +201 -0
- package/README.md +44 -0
- package/package.json +61 -0
- package/public/favicon.svg +6 -0
- package/public/silent-check-sso.html +8 -0
- package/src/assets/ngb_logo.svg +17 -0
- package/src/assets/ngb_logo_color.svg +17 -0
- package/src/index.ts +1023 -0
- package/src/ngb/README.md +27 -0
- package/src/ngb/accounting/NgbAccountingPeriodClosingPage.vue +1157 -0
- package/src/ngb/accounting/NgbChartOfAccountEditor.vue +590 -0
- package/src/ngb/accounting/NgbChartOfAccountsPage.vue +420 -0
- package/src/ngb/accounting/NgbGeneralJournalEntryEditPage.vue +796 -0
- package/src/ngb/accounting/NgbGeneralJournalEntryLinesEditor.vue +454 -0
- package/src/ngb/accounting/NgbGeneralJournalEntryListPage.vue +186 -0
- package/src/ngb/accounting/api.ts +79 -0
- package/src/ngb/accounting/generalJournalEntry.ts +127 -0
- package/src/ngb/accounting/generalJournalEntryApi.ts +112 -0
- package/src/ngb/accounting/generalJournalEntryTypes.ts +155 -0
- package/src/ngb/accounting/navigation.ts +83 -0
- package/src/ngb/accounting/periodClosing.ts +75 -0
- package/src/ngb/accounting/periodClosingApi.ts +60 -0
- package/src/ngb/accounting/periodClosingTypes.ts +73 -0
- package/src/ngb/accounting/types.ts +57 -0
- package/src/ngb/api/audit.ts +23 -0
- package/src/ngb/api/catalogs.ts +50 -0
- package/src/ngb/api/contracts.ts +285 -0
- package/src/ngb/api/documents.ts +170 -0
- package/src/ngb/api/http.ts +446 -0
- package/src/ngb/api/lookups.ts +14 -0
- package/src/ngb/api/types.ts +6 -0
- package/src/ngb/auth/index.ts +13 -0
- package/src/ngb/auth/keycloak.ts +374 -0
- package/src/ngb/auth/router.ts +35 -0
- package/src/ngb/auth/types.ts +12 -0
- package/src/ngb/auth/useAuthStore.ts +209 -0
- package/src/ngb/command-palette/NgbCommandPaletteDialog.vue +342 -0
- package/src/ngb/command-palette/NgbCommandPaletteEmptyState.vue +55 -0
- package/src/ngb/command-palette/NgbCommandPaletteFooterHints.vue +29 -0
- package/src/ngb/command-palette/api.ts +10 -0
- package/src/ngb/command-palette/config.ts +16 -0
- package/src/ngb/command-palette/presentation.ts +85 -0
- package/src/ngb/command-palette/search.ts +130 -0
- package/src/ngb/command-palette/staticItems.ts +243 -0
- package/src/ngb/command-palette/storage.ts +45 -0
- package/src/ngb/command-palette/store.ts +716 -0
- package/src/ngb/command-palette/types.ts +120 -0
- package/src/ngb/command-palette/useCommandPaletteHotkeys.ts +27 -0
- package/src/ngb/command-palette/useCommandPalettePageContext.ts +18 -0
- package/src/ngb/components/NgbConfirmDialog.vue +63 -0
- package/src/ngb/components/NgbDialog.vue +44 -0
- package/src/ngb/components/NgbDrawer.vue +156 -0
- package/src/ngb/components/NgbHeaderActionCluster.vue +140 -0
- package/src/ngb/components/NgbModalShell.vue +99 -0
- package/src/ngb/components/NgbToolbar.vue +14 -0
- package/src/ngb/components/forms/NgbFormLayout.vue +13 -0
- package/src/ngb/components/forms/NgbFormRow.vue +21 -0
- package/src/ngb/components/forms/NgbFormSection.vue +16 -0
- package/src/ngb/components/forms/NgbValidationSummary.vue +18 -0
- package/src/ngb/components/navigation/NgbNavigationTree.vue +68 -0
- package/src/ngb/components/navigation/NgbTreeNode.vue +94 -0
- package/src/ngb/components/register/NgbColumnChooser.vue +101 -0
- package/src/ngb/components/register/NgbRegisterGrid.vue +473 -0
- package/src/ngb/components/register/registerTypes.ts +51 -0
- package/src/ngb/components/register/useRegisterColumnResize.ts +99 -0
- package/src/ngb/components/register/useRegisterColumnState.ts +220 -0
- package/src/ngb/components/register/useRegisterRows.ts +209 -0
- package/src/ngb/components/register/useRegisterViewport.ts +56 -0
- package/src/ngb/editor/NgbDocumentEffectsPage.vue +512 -0
- package/src/ngb/editor/NgbDocumentFlowPage.vue +409 -0
- package/src/ngb/editor/NgbDocumentPrintPage.vue +712 -0
- package/src/ngb/editor/NgbEditorDiscardDialog.vue +40 -0
- package/src/ngb/editor/NgbEntityAuditSidebar.vue +288 -0
- package/src/ngb/editor/NgbEntityEditor.vue +250 -0
- package/src/ngb/editor/NgbEntityEditorDrawerActions.vue +88 -0
- package/src/ngb/editor/NgbEntityEditorHeader.vue +114 -0
- package/src/ngb/editor/catalogNavigation.ts +13 -0
- package/src/ngb/editor/config.ts +252 -0
- package/src/ngb/editor/documentCopyDraft.ts +245 -0
- package/src/ngb/editor/documentEffects.ts +43 -0
- package/src/ngb/editor/documentEffectsDateFormatting.ts +18 -0
- package/src/ngb/editor/documentNavigation.ts +115 -0
- package/src/ngb/editor/documentStatus.ts +45 -0
- package/src/ngb/editor/entityEditorErrors.ts +162 -0
- package/src/ngb/editor/entityEditorPersistence.ts +241 -0
- package/src/ngb/editor/extensions.ts +40 -0
- package/src/ngb/editor/types.ts +295 -0
- package/src/ngb/editor/useConfiguredEntityEditorDocumentActions.ts +183 -0
- package/src/ngb/editor/useDocumentEditorDrawerState.ts +152 -0
- package/src/ngb/editor/useEditorDrawerState.ts +88 -0
- package/src/ngb/editor/useEntityEditorBusinessContext.ts +97 -0
- package/src/ngb/editor/useEntityEditorCapabilities.ts +190 -0
- package/src/ngb/editor/useEntityEditorCommandPalette.ts +154 -0
- package/src/ngb/editor/useEntityEditorCommitHandlers.ts +102 -0
- package/src/ngb/editor/useEntityEditorHeaderActions.ts +253 -0
- package/src/ngb/editor/useEntityEditorLeaveGuard.ts +66 -0
- package/src/ngb/editor/useEntityEditorNavigationActions.ts +235 -0
- package/src/ngb/editor/useEntityEditorOutputs.ts +59 -0
- package/src/ngb/editor/useEntityEditorPageActions.ts +57 -0
- package/src/ngb/editor/useRouteQueryEditorDrawer.ts +173 -0
- package/src/ngb/env/runtimeConfig.ts +25 -0
- package/src/ngb/lookup/config.ts +42 -0
- package/src/ngb/lookup/defaultConfig.ts +136 -0
- package/src/ngb/lookup/navigation.ts +76 -0
- package/src/ngb/lookup/prefetch.ts +40 -0
- package/src/ngb/lookup/store.ts +261 -0
- package/src/ngb/metadata/NgbDocumentListFiltersDrawer.vue +78 -0
- package/src/ngb/metadata/NgbDocumentPeriodFilter.vue +53 -0
- package/src/ngb/metadata/NgbEntityForm.vue +84 -0
- package/src/ngb/metadata/NgbEntityFormFieldsBlock.vue +110 -0
- package/src/ngb/metadata/NgbEntityListPageHeader.vue +80 -0
- package/src/ngb/metadata/NgbFilterFieldControl.vue +104 -0
- package/src/ngb/metadata/NgbMetadataCatalogEditPage.vue +42 -0
- package/src/ngb/metadata/NgbMetadataCatalogListPage.vue +269 -0
- package/src/ngb/metadata/NgbMetadataDocumentEditPage.vue +68 -0
- package/src/ngb/metadata/NgbMetadataDocumentListPage.vue +353 -0
- package/src/ngb/metadata/NgbMetadataFieldRenderer.vue +124 -0
- package/src/ngb/metadata/NgbMetadataLookupControl.vue +92 -0
- package/src/ngb/metadata/NgbRecycleBinFilter.vue +53 -0
- package/src/ngb/metadata/NgbRegisterPageLayout.vue +143 -0
- package/src/ngb/metadata/config.ts +28 -0
- package/src/ngb/metadata/dataTypes.ts +27 -0
- package/src/ngb/metadata/entityForm.ts +114 -0
- package/src/ngb/metadata/entityModel.ts +34 -0
- package/src/ngb/metadata/entityValueFormatting.ts +83 -0
- package/src/ngb/metadata/fieldRendererState.ts +84 -0
- package/src/ngb/metadata/filtering.ts +153 -0
- package/src/ngb/metadata/lookup.ts +25 -0
- package/src/ngb/metadata/monthPagedListQuery.ts +108 -0
- package/src/ngb/metadata/normalization.ts +100 -0
- package/src/ngb/metadata/referenceHydration.ts +99 -0
- package/src/ngb/metadata/register.ts +135 -0
- package/src/ngb/metadata/routePages.ts +86 -0
- package/src/ngb/metadata/store.ts +35 -0
- package/src/ngb/metadata/types.ts +276 -0
- package/src/ngb/metadata/useMetadataListFilters.ts +306 -0
- package/src/ngb/metadata/useMetadataRegisterPageData.ts +246 -0
- package/src/ngb/metadata/useValidationFocus.ts +49 -0
- package/src/ngb/primitives/NgbBadge.vue +31 -0
- package/src/ngb/primitives/NgbButton.vue +53 -0
- package/src/ngb/primitives/NgbCheckbox.vue +33 -0
- package/src/ngb/primitives/NgbDatePicker.vue +141 -0
- package/src/ngb/primitives/NgbIcon.vue +545 -0
- package/src/ngb/primitives/NgbInput.vue +57 -0
- package/src/ngb/primitives/NgbLookup.vue +264 -0
- package/src/ngb/primitives/NgbMonthPicker.vue +132 -0
- package/src/ngb/primitives/NgbMultiSelect.vue +179 -0
- package/src/ngb/primitives/NgbPeriodPicker.vue +125 -0
- package/src/ngb/primitives/NgbPickerNavButton.vue +31 -0
- package/src/ngb/primitives/NgbPickerPopover.vue +79 -0
- package/src/ngb/primitives/NgbRadioGroup.vue +53 -0
- package/src/ngb/primitives/NgbSelect.vue +108 -0
- package/src/ngb/primitives/NgbStatusIcon.vue +63 -0
- package/src/ngb/primitives/NgbSwitch.vue +42 -0
- package/src/ngb/primitives/NgbTabs.vue +116 -0
- package/src/ngb/primitives/NgbToastHost.vue +49 -0
- package/src/ngb/primitives/NgbTooltip.vue +27 -0
- package/src/ngb/primitives/iconNames.ts +83 -0
- package/src/ngb/primitives/toast.ts +66 -0
- package/src/ngb/primitives/useAsyncComboboxQuery.ts +85 -0
- package/src/ngb/primitives/useFloatingDropdownPosition.ts +129 -0
- package/src/ngb/reporting/NgbReportComposerCollectionSection.vue +106 -0
- package/src/ngb/reporting/NgbReportComposerPanel.vue +1187 -0
- package/src/ngb/reporting/NgbReportDateRangeFilter.vue +54 -0
- package/src/ngb/reporting/NgbReportPage.vue +1124 -0
- package/src/ngb/reporting/NgbReportSheet.vue +467 -0
- package/src/ngb/reporting/api.ts +35 -0
- package/src/ngb/reporting/composer.ts +904 -0
- package/src/ngb/reporting/config.ts +120 -0
- package/src/ngb/reporting/defaultConfig.ts +15 -0
- package/src/ngb/reporting/lookupFilters.ts +43 -0
- package/src/ngb/reporting/navigation.ts +181 -0
- package/src/ngb/reporting/pageHelpers.ts +73 -0
- package/src/ngb/reporting/pageSession.ts +92 -0
- package/src/ngb/reporting/paging.ts +66 -0
- package/src/ngb/reporting/types.ts +333 -0
- package/src/ngb/router/backNavigation.ts +122 -0
- package/src/ngb/router/queryParams.ts +129 -0
- package/src/ngb/router/queryState.ts +121 -0
- package/src/ngb/router/routeAliases.ts +66 -0
- package/src/ngb/router/routeParams.ts +15 -0
- package/src/ngb/router/shareLink.ts +39 -0
- package/src/ngb/security/NgbAccessAuditPanel.vue +32 -0
- package/src/ngb/security/NgbAccessDeniedState.vue +27 -0
- package/src/ngb/security/NgbEffectiveAccessPanel.vue +60 -0
- package/src/ngb/security/NgbPermissionMatrix.vue +160 -0
- package/src/ngb/security/NgbRoleEditorPage.vue +319 -0
- package/src/ngb/security/NgbRolesPage.vue +125 -0
- package/src/ngb/security/NgbUserEditorPage.vue +615 -0
- package/src/ngb/security/NgbUsersPage.vue +125 -0
- package/src/ngb/security/api.ts +91 -0
- package/src/ngb/security/audit.ts +21 -0
- package/src/ngb/security/permissions.ts +65 -0
- package/src/ngb/security/types.ts +157 -0
- package/src/ngb/security/useAccessStore.ts +69 -0
- package/src/ngb/site/NgbDashboardAsOfToolbar.vue +100 -0
- package/src/ngb/site/NgbDashboardStatusBanner.vue +91 -0
- package/src/ngb/site/NgbLogo.vue +44 -0
- package/src/ngb/site/NgbPageHeader.vue +49 -0
- package/src/ngb/site/NgbSiteShell.vue +286 -0
- package/src/ngb/site/NgbSiteSidebar.vue +270 -0
- package/src/ngb/site/NgbTopBar.vue +425 -0
- package/src/ngb/site/NgbTrendChart.vue +224 -0
- package/src/ngb/site/dashboardData.ts +324 -0
- package/src/ngb/site/mainMenuStore.ts +49 -0
- package/src/ngb/site/types.ts +31 -0
- package/src/ngb/site/useDashboardPageState.ts +98 -0
- package/src/ngb/site/useTheme.ts +95 -0
- package/src/ngb/utils/clone.ts +43 -0
- package/src/ngb/utils/dateValues.ts +107 -0
- package/src/ngb/utils/errorMessage.ts +14 -0
- package/src/ngb/utils/guid.ts +21 -0
- package/src/ngb/utils/stableValue.ts +21 -0
- package/src/ngb/utils/storage.ts +135 -0
- package/src/styles/tailwind.css +165 -0
- package/src/vite/publicAssets.ts +104 -0
- 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
|
+
}
|