@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,143 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useSlots } from 'vue'
|
|
3
|
+
import NgbDrawer from '../components/NgbDrawer.vue'
|
|
4
|
+
import NgbRegisterGrid from '../components/register/NgbRegisterGrid.vue'
|
|
5
|
+
import type { RegisterColumn, RegisterDataRow } from '../components/register/registerTypes'
|
|
6
|
+
import NgbEntityListPageHeader from './NgbEntityListPageHeader.vue'
|
|
7
|
+
|
|
8
|
+
const props = withDefaults(defineProps<{
|
|
9
|
+
title: string
|
|
10
|
+
canBack?: boolean
|
|
11
|
+
itemsCount?: number | null
|
|
12
|
+
total?: number | null
|
|
13
|
+
loading?: boolean
|
|
14
|
+
error?: string | null
|
|
15
|
+
warning?: string | null
|
|
16
|
+
disableCreate?: boolean
|
|
17
|
+
showFilter?: boolean
|
|
18
|
+
disableFilter?: boolean
|
|
19
|
+
filterActive?: boolean
|
|
20
|
+
disablePrev?: boolean
|
|
21
|
+
disableNext?: boolean
|
|
22
|
+
columns: RegisterColumn[]
|
|
23
|
+
rows: RegisterDataRow[]
|
|
24
|
+
storageKey: string
|
|
25
|
+
drawerOpen?: boolean
|
|
26
|
+
drawerTitle?: string
|
|
27
|
+
drawerSubtitle?: string
|
|
28
|
+
drawerHideHeader?: boolean
|
|
29
|
+
drawerFlushBody?: boolean
|
|
30
|
+
beforeClose?: (() => boolean | Promise<boolean>) | null
|
|
31
|
+
}>(), {
|
|
32
|
+
canBack: true,
|
|
33
|
+
loading: false,
|
|
34
|
+
error: null,
|
|
35
|
+
warning: null,
|
|
36
|
+
showFilter: true,
|
|
37
|
+
disableCreate: false,
|
|
38
|
+
disableFilter: false,
|
|
39
|
+
filterActive: false,
|
|
40
|
+
disablePrev: false,
|
|
41
|
+
disableNext: false,
|
|
42
|
+
drawerOpen: undefined,
|
|
43
|
+
drawerTitle: '',
|
|
44
|
+
drawerSubtitle: '',
|
|
45
|
+
drawerHideHeader: false,
|
|
46
|
+
drawerFlushBody: false,
|
|
47
|
+
beforeClose: null,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const emit = defineEmits<{
|
|
51
|
+
(e: 'back'): void
|
|
52
|
+
(e: 'refresh'): void
|
|
53
|
+
(e: 'create'): void
|
|
54
|
+
(e: 'filter'): void
|
|
55
|
+
(e: 'prev'): void
|
|
56
|
+
(e: 'next'): void
|
|
57
|
+
(e: 'rowActivate', id: string): void
|
|
58
|
+
(e: 'update:drawerOpen', value: boolean): void
|
|
59
|
+
}>()
|
|
60
|
+
|
|
61
|
+
const slots = useSlots()
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<template>
|
|
65
|
+
<div class="h-full min-h-0 flex flex-col">
|
|
66
|
+
<NgbEntityListPageHeader
|
|
67
|
+
:title="title"
|
|
68
|
+
:can-back="canBack"
|
|
69
|
+
:items-count="itemsCount"
|
|
70
|
+
:total="total"
|
|
71
|
+
:loading="loading"
|
|
72
|
+
:disable-create="disableCreate"
|
|
73
|
+
:show-filter="showFilter"
|
|
74
|
+
:disable-filter="disableFilter"
|
|
75
|
+
:filter-active="filterActive"
|
|
76
|
+
:disable-prev="disablePrev"
|
|
77
|
+
:disable-next="disableNext"
|
|
78
|
+
@back="emit('back')"
|
|
79
|
+
@refresh="emit('refresh')"
|
|
80
|
+
@create="emit('create')"
|
|
81
|
+
@filter="emit('filter')"
|
|
82
|
+
@prev="emit('prev')"
|
|
83
|
+
@next="emit('next')"
|
|
84
|
+
>
|
|
85
|
+
<template #filters>
|
|
86
|
+
<slot name="filters" />
|
|
87
|
+
</template>
|
|
88
|
+
</NgbEntityListPageHeader>
|
|
89
|
+
|
|
90
|
+
<div class="p-6 flex-1 min-h-0 flex flex-col">
|
|
91
|
+
<div
|
|
92
|
+
v-if="error"
|
|
93
|
+
class="mb-4 rounded-[var(--ngb-radius)] border border-red-200 bg-red-50 p-3 text-sm text-red-900 dark:border-red-900/50 dark:bg-red-950/30 dark:text-red-100"
|
|
94
|
+
>
|
|
95
|
+
{{ error }}
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div
|
|
99
|
+
v-if="warning"
|
|
100
|
+
class="mb-4 rounded-[var(--ngb-radius)] border border-amber-200 bg-amber-50 p-3 text-sm text-amber-900 dark:border-amber-900/50 dark:bg-amber-950/20 dark:text-amber-100"
|
|
101
|
+
>
|
|
102
|
+
{{ warning }}
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<slot name="beforeGrid" />
|
|
106
|
+
|
|
107
|
+
<slot name="grid">
|
|
108
|
+
<NgbRegisterGrid
|
|
109
|
+
class="flex-1 min-h-0"
|
|
110
|
+
fill-height
|
|
111
|
+
:show-panel="false"
|
|
112
|
+
:columns="columns"
|
|
113
|
+
:rows="rows"
|
|
114
|
+
:show-totals="false"
|
|
115
|
+
:storage-key="storageKey"
|
|
116
|
+
:activate-on-row-click="true"
|
|
117
|
+
@rowActivate="emit('rowActivate', String($event))"
|
|
118
|
+
/>
|
|
119
|
+
</slot>
|
|
120
|
+
|
|
121
|
+
<div v-if="loading" class="mt-3 text-sm text-ngb-muted">Loading…</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<slot name="filterDrawer" />
|
|
125
|
+
|
|
126
|
+
<NgbDrawer
|
|
127
|
+
v-if="drawerOpen !== undefined && slots.drawerContent"
|
|
128
|
+
:open="drawerOpen"
|
|
129
|
+
:title="drawerTitle"
|
|
130
|
+
:subtitle="drawerSubtitle"
|
|
131
|
+
:hide-header="drawerHideHeader"
|
|
132
|
+
:flush-body="drawerFlushBody"
|
|
133
|
+
:before-close="beforeClose ?? undefined"
|
|
134
|
+
@update:open="emit('update:drawerOpen', $event)"
|
|
135
|
+
>
|
|
136
|
+
<template v-if="slots.drawerActions" #actions>
|
|
137
|
+
<slot name="drawerActions" />
|
|
138
|
+
</template>
|
|
139
|
+
|
|
140
|
+
<slot name="drawerContent" />
|
|
141
|
+
</NgbDrawer>
|
|
142
|
+
</div>
|
|
143
|
+
</template>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CatalogTypeMetadata, DocumentTypeMetadata, MetadataFormBehavior } from './types'
|
|
2
|
+
|
|
3
|
+
export type MetadataFrameworkConfig = {
|
|
4
|
+
loadCatalogTypeMetadata: (catalogType: string) => Promise<CatalogTypeMetadata>
|
|
5
|
+
loadDocumentTypeMetadata: (documentType: string) => Promise<DocumentTypeMetadata>
|
|
6
|
+
formBehavior?: MetadataFormBehavior
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let metadataFrameworkConfig: MetadataFrameworkConfig | null = null
|
|
10
|
+
|
|
11
|
+
export function configureNgbMetadata(config: MetadataFrameworkConfig): void {
|
|
12
|
+
metadataFrameworkConfig = config
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getConfiguredNgbMetadata(): MetadataFrameworkConfig {
|
|
16
|
+
if (!metadataFrameworkConfig) {
|
|
17
|
+
throw new Error('NGB metadata framework is not configured. Call configureNgbMetadata(...) during app bootstrap.')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return metadataFrameworkConfig
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function resolveNgbMetadataFormBehavior(override?: MetadataFormBehavior): MetadataFormBehavior {
|
|
24
|
+
return {
|
|
25
|
+
...(metadataFrameworkConfig?.formBehavior ?? {}),
|
|
26
|
+
...(override ?? {}),
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function dataTypeKind(dataType: unknown): string {
|
|
2
|
+
const normalized = String(dataType ?? '').trim()
|
|
3
|
+
return normalized || 'Unknown'
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function isGuidType(dataType: unknown): boolean {
|
|
7
|
+
return dataTypeKind(dataType) === 'Guid'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isBooleanType(dataType: unknown): boolean {
|
|
11
|
+
return dataTypeKind(dataType) === 'Boolean'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isNumberType(dataType: unknown): boolean {
|
|
15
|
+
const kind = dataTypeKind(dataType)
|
|
16
|
+
return kind === 'Int32' || kind === 'Decimal' || kind === 'Money'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isDateType(dataType: unknown): boolean {
|
|
20
|
+
const kind = dataTypeKind(dataType)
|
|
21
|
+
return kind === 'Date' || kind === 'DateOnly'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isDateTimeType(dataType: unknown): boolean {
|
|
25
|
+
const kind = dataTypeKind(dataType)
|
|
26
|
+
return kind === 'DateTime' || kind === 'DateTimeOffset'
|
|
27
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { dataTypeKind } from './dataTypes'
|
|
2
|
+
import { resolveNgbMetadataFormBehavior } from './config'
|
|
3
|
+
import { tryExtractReferenceId } from './entityModel'
|
|
4
|
+
import type { EntityFormModel, FieldHiddenArgs, FieldMetadata, FieldReadonlyArgs, FormMetadata, JsonValue, MetadataFormBehavior, RecordFields } from './types'
|
|
5
|
+
|
|
6
|
+
export function flattenFormFields(form?: FormMetadata | null): FieldMetadata[] {
|
|
7
|
+
if (!form?.sections) return []
|
|
8
|
+
|
|
9
|
+
const out: FieldMetadata[] = []
|
|
10
|
+
for (const section of form.sections) {
|
|
11
|
+
for (const row of section.rows ?? []) {
|
|
12
|
+
for (const field of row.fields ?? []) out.push(field)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return out
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function defaultFindDisplayField(form: FormMetadata): FieldMetadata | null {
|
|
20
|
+
for (const field of flattenFormFields(form)) {
|
|
21
|
+
if (field.key === 'display') return field
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function defaultIsFieldReadonly(args: FieldReadonlyArgs): boolean {
|
|
28
|
+
if (args.forceReadonly) return true
|
|
29
|
+
if (args.field.isReadOnly) return true
|
|
30
|
+
if (args.status !== undefined && args.field.readOnlyWhenStatusIn?.includes(args.status)) return true
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function defaultIsFieldHidden(_args: FieldHiddenArgs): boolean {
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function tryFlattenRef(value: unknown): unknown {
|
|
39
|
+
const referenceId = tryExtractReferenceId(value)
|
|
40
|
+
return referenceId ?? value
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function ensureModelKeys(form: FormMetadata | null | undefined, model: EntityFormModel): void {
|
|
44
|
+
for (const field of flattenFormFields(form)) {
|
|
45
|
+
if (!(field.key in model) || model[field.key] === undefined) {
|
|
46
|
+
model[field.key] = dataTypeKind(field.dataType) === 'Boolean' || field.uiControl === 5 ? false : null
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizeValue(field: FieldMetadata, value: unknown): JsonValue {
|
|
52
|
+
let normalized = value
|
|
53
|
+
if (normalized === '') normalized = null
|
|
54
|
+
if (normalized === undefined) normalized = null
|
|
55
|
+
|
|
56
|
+
normalized = tryFlattenRef(normalized)
|
|
57
|
+
|
|
58
|
+
if (normalized === null) return null
|
|
59
|
+
|
|
60
|
+
switch (field.uiControl) {
|
|
61
|
+
case 5:
|
|
62
|
+
return !!normalized
|
|
63
|
+
case 3:
|
|
64
|
+
case 4:
|
|
65
|
+
if (typeof normalized === 'number') return normalized
|
|
66
|
+
if (typeof normalized === 'string') {
|
|
67
|
+
const number = Number(normalized)
|
|
68
|
+
return Number.isFinite(number) ? number : normalized
|
|
69
|
+
}
|
|
70
|
+
return normalized as JsonValue
|
|
71
|
+
case 6:
|
|
72
|
+
case 7:
|
|
73
|
+
return normalized as JsonValue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
switch (dataTypeKind(field.dataType)) {
|
|
77
|
+
case 'Int32': {
|
|
78
|
+
if (typeof normalized === 'number') return Math.trunc(normalized)
|
|
79
|
+
if (typeof normalized === 'string') {
|
|
80
|
+
const number = Number.parseInt(normalized, 10)
|
|
81
|
+
return Number.isFinite(number) ? number : normalized
|
|
82
|
+
}
|
|
83
|
+
return normalized as JsonValue
|
|
84
|
+
}
|
|
85
|
+
case 'Decimal':
|
|
86
|
+
case 'Money': {
|
|
87
|
+
if (typeof normalized === 'number') return normalized
|
|
88
|
+
if (typeof normalized === 'string') {
|
|
89
|
+
const number = Number(normalized)
|
|
90
|
+
return Number.isFinite(number) ? number : normalized
|
|
91
|
+
}
|
|
92
|
+
return normalized as JsonValue
|
|
93
|
+
}
|
|
94
|
+
case 'Boolean':
|
|
95
|
+
return !!normalized
|
|
96
|
+
default:
|
|
97
|
+
return normalized as JsonValue
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function buildFieldsPayload(form: FormMetadata | null | undefined, model: EntityFormModel): RecordFields {
|
|
102
|
+
const fields = flattenFormFields(form)
|
|
103
|
+
const payload: RecordFields = {}
|
|
104
|
+
|
|
105
|
+
for (const field of fields) {
|
|
106
|
+
payload[field.key] = normalizeValue(field, model[field.key])
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return payload
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function resolveMetadataFormBehavior(override?: MetadataFormBehavior): MetadataFormBehavior {
|
|
113
|
+
return resolveNgbMetadataFormBehavior(override)
|
|
114
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ReferenceValue } from './types'
|
|
2
|
+
|
|
3
|
+
export type { EntityFormModel, ReferenceValue } from './types'
|
|
4
|
+
|
|
5
|
+
export function isReferenceValue(value: unknown): value is ReferenceValue {
|
|
6
|
+
return !!value
|
|
7
|
+
&& typeof value === 'object'
|
|
8
|
+
&& typeof (value as ReferenceValue).id === 'string'
|
|
9
|
+
&& typeof (value as ReferenceValue).display === 'string'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function tryExtractReferenceId(value: unknown): string | null {
|
|
13
|
+
if (typeof value === 'string') {
|
|
14
|
+
const normalized = value.trim()
|
|
15
|
+
return normalized || null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (isReferenceValue(value)) {
|
|
19
|
+
const normalized = value.id.trim()
|
|
20
|
+
return normalized || null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function tryExtractReferenceDisplay(value: unknown): string | null {
|
|
27
|
+
if (!isReferenceValue(value)) return null
|
|
28
|
+
const normalized = value.display.trim()
|
|
29
|
+
return normalized || null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function asTrimmedString(value: unknown): string {
|
|
33
|
+
return value == null ? '' : String(value).trim()
|
|
34
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { dataTypeKind } from './dataTypes'
|
|
2
|
+
import { isReferenceValue } from './entityModel'
|
|
3
|
+
|
|
4
|
+
const DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/
|
|
5
|
+
|
|
6
|
+
export function formatDateOnlyValue(value: string): string {
|
|
7
|
+
if (!DATE_ONLY_RE.test(value)) return value
|
|
8
|
+
|
|
9
|
+
const [year, month, day] = value.split('-').map((part) => Number(part))
|
|
10
|
+
const date = new Date(year, month - 1, day, 12, 0, 0)
|
|
11
|
+
if (Number.isNaN(date.getTime())) return value
|
|
12
|
+
return date.toLocaleDateString()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function formatDateTimeValue(value: string): string {
|
|
16
|
+
const date = new Date(value)
|
|
17
|
+
if (Number.isNaN(date.getTime())) return value
|
|
18
|
+
return date.toLocaleString()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function formatNumberValue(kind: string, value: unknown): string {
|
|
22
|
+
const numeric = typeof value === 'number' ? value : Number(value)
|
|
23
|
+
if (!Number.isFinite(numeric)) return String(value ?? '—')
|
|
24
|
+
|
|
25
|
+
if (kind === 'Int32') {
|
|
26
|
+
return Math.trunc(numeric).toLocaleString()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (kind === 'Money') {
|
|
30
|
+
return numeric.toLocaleString(undefined, {
|
|
31
|
+
minimumFractionDigits: 2,
|
|
32
|
+
maximumFractionDigits: 2,
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return numeric.toLocaleString(undefined, {
|
|
37
|
+
minimumFractionDigits: 0,
|
|
38
|
+
maximumFractionDigits: 4,
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function formatObjectValue(value: Record<string, unknown>): string {
|
|
43
|
+
const entries = Object.entries(value)
|
|
44
|
+
.filter(([, item]) => item !== undefined)
|
|
45
|
+
.map(([key, item]) => `${humanizeEntityKey(key)}: ${formatLooseEntityValue(item)}`)
|
|
46
|
+
.filter((item) => item.length > 0)
|
|
47
|
+
|
|
48
|
+
return entries.length > 0 ? entries.join(' · ') : '—'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function formatLooseEntityValue(value: unknown): string {
|
|
52
|
+
if (value == null) return '—'
|
|
53
|
+
if (typeof value === 'boolean') return value ? 'Yes' : 'No'
|
|
54
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value.toLocaleString() : String(value)
|
|
55
|
+
if (typeof value === 'string') return value.trim() || '—'
|
|
56
|
+
if (Array.isArray(value)) return value.map((item) => formatLooseEntityValue(item)).join(' · ')
|
|
57
|
+
if (isReferenceValue(value)) return value.display.trim() || value.id
|
|
58
|
+
if (typeof value === 'object') return formatObjectValue(value as Record<string, unknown>)
|
|
59
|
+
return String(value)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function formatTypedEntityValue(dataType: unknown, value: unknown): string {
|
|
63
|
+
if (value == null) return '—'
|
|
64
|
+
|
|
65
|
+
const kind = dataTypeKind(dataType)
|
|
66
|
+
if (kind === 'Boolean') return !!value ? 'Yes' : 'No'
|
|
67
|
+
if (kind === 'Date' && typeof value === 'string') return formatDateOnlyValue(value)
|
|
68
|
+
if (kind === 'DateTime' && typeof value === 'string') return formatDateTimeValue(value)
|
|
69
|
+
if (kind === 'Money' || kind === 'Decimal' || kind === 'Int32') return formatNumberValue(kind, value)
|
|
70
|
+
|
|
71
|
+
return formatLooseEntityValue(value)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function humanizeEntityKey(value: string): string {
|
|
75
|
+
const normalized = String(value ?? '').trim().replace(/[._]/g, ' ')
|
|
76
|
+
if (!normalized) return 'Field'
|
|
77
|
+
|
|
78
|
+
return normalized
|
|
79
|
+
.split(/\s+/)
|
|
80
|
+
.filter(Boolean)
|
|
81
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
82
|
+
.join(' ')
|
|
83
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { resolveNgbMetadataFormBehavior } from './config'
|
|
2
|
+
import { dataTypeKind } from './dataTypes'
|
|
3
|
+
import { isReferenceValue } from './entityModel'
|
|
4
|
+
import { resolveLookupHint } from './lookup'
|
|
5
|
+
import type { EntityFormModel, FieldMetadata, FieldOption, LookupHint, MetadataFormBehavior } from './types'
|
|
6
|
+
|
|
7
|
+
export type FieldRenderMode =
|
|
8
|
+
| 'select'
|
|
9
|
+
| 'lookup'
|
|
10
|
+
| 'checkbox'
|
|
11
|
+
| 'textarea'
|
|
12
|
+
| 'reference-display'
|
|
13
|
+
| 'date'
|
|
14
|
+
| 'input'
|
|
15
|
+
|
|
16
|
+
export type FieldInputType = 'text' | 'number' | 'datetime-local'
|
|
17
|
+
|
|
18
|
+
export type FieldRendererState = {
|
|
19
|
+
mode: FieldRenderMode
|
|
20
|
+
inputType: FieldInputType
|
|
21
|
+
fieldOptions: FieldOption[] | null
|
|
22
|
+
hint: LookupHint | null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function resolveFieldRendererState(args: {
|
|
26
|
+
entityTypeCode: string
|
|
27
|
+
model: EntityFormModel
|
|
28
|
+
field: FieldMetadata
|
|
29
|
+
modelValue: unknown
|
|
30
|
+
behavior?: MetadataFormBehavior
|
|
31
|
+
}): FieldRendererState {
|
|
32
|
+
const behavior = resolveNgbMetadataFormBehavior(args.behavior)
|
|
33
|
+
const fieldOptions = behavior.resolveFieldOptions?.({
|
|
34
|
+
entityTypeCode: args.entityTypeCode,
|
|
35
|
+
model: args.model,
|
|
36
|
+
field: args.field,
|
|
37
|
+
}) ?? args.field.options ?? null
|
|
38
|
+
const hint = resolveLookupHint({
|
|
39
|
+
entityTypeCode: args.entityTypeCode,
|
|
40
|
+
model: args.model,
|
|
41
|
+
field: args.field,
|
|
42
|
+
behavior,
|
|
43
|
+
})
|
|
44
|
+
const dataType = dataTypeKind(args.field.dataType)
|
|
45
|
+
|
|
46
|
+
const isLookup = !!hint || dataType === 'Lookup'
|
|
47
|
+
const isCheckbox = args.field.uiControl === 5 || dataType === 'Boolean'
|
|
48
|
+
const isTextArea = args.field.uiControl === 2
|
|
49
|
+
const isDate = args.field.uiControl === 6 || dataType === 'Date'
|
|
50
|
+
const isDateTime = args.field.uiControl === 7 || dataType === 'DateTime'
|
|
51
|
+
const isNumber = args.field.uiControl === 3 || dataType === 'Int32' || dataType === 'Decimal'
|
|
52
|
+
const isMoney = args.field.uiControl === 4 || dataType === 'Money'
|
|
53
|
+
|
|
54
|
+
if (fieldOptions) {
|
|
55
|
+
return { mode: 'select', inputType: 'text', fieldOptions, hint }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (isLookup && hint) {
|
|
59
|
+
return { mode: 'lookup', inputType: 'text', fieldOptions, hint }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (isCheckbox) {
|
|
63
|
+
return { mode: 'checkbox', inputType: 'text', fieldOptions, hint }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (isTextArea) {
|
|
67
|
+
return { mode: 'textarea', inputType: 'text', fieldOptions, hint }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (isReferenceValue(args.modelValue) && !hint) {
|
|
71
|
+
return { mode: 'reference-display', inputType: 'text', fieldOptions, hint }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (isDate) {
|
|
75
|
+
return { mode: 'date', inputType: 'text', fieldOptions, hint }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
mode: 'input',
|
|
80
|
+
inputType: isDateTime ? 'datetime-local' : (isNumber || isMoney ? 'number' : 'text'),
|
|
81
|
+
fieldOptions,
|
|
82
|
+
hint,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { isNonEmptyGuid } from '../utils/guid'
|
|
2
|
+
import type { FilterFieldLike, FilterFieldOption, FilterFieldState, FilterLookupItem, LookupStoreApi, ResolvedLookupSource } from './types'
|
|
3
|
+
|
|
4
|
+
export type {
|
|
5
|
+
FilterFieldLike,
|
|
6
|
+
FilterFieldOption,
|
|
7
|
+
FilterFieldState,
|
|
8
|
+
FilterLookupItem,
|
|
9
|
+
LookupStoreApi,
|
|
10
|
+
ResolvedLookupSource,
|
|
11
|
+
} from './types'
|
|
12
|
+
|
|
13
|
+
export function splitFilterValues(raw: string): string[] {
|
|
14
|
+
return raw
|
|
15
|
+
.split(',')
|
|
16
|
+
.map((part) => part.trim())
|
|
17
|
+
.filter((part) => part.length > 0)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function joinFilterValues(values: string[]): string {
|
|
21
|
+
return values
|
|
22
|
+
.map((value) => value.trim())
|
|
23
|
+
.filter((value) => value.length > 0)
|
|
24
|
+
.join(',')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function summarizeFilterValues(values: string[]): string | null {
|
|
28
|
+
if (values.length === 0) return null
|
|
29
|
+
const first = values[0]
|
|
30
|
+
const remaining = values.length - 1
|
|
31
|
+
return remaining > 0 ? `${first} (+${remaining})` : first
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function optionLabelForFilter(field: Pick<FilterFieldLike, 'options'>, rawValue: string): string {
|
|
35
|
+
const normalized = rawValue.trim()
|
|
36
|
+
if (!normalized) return ''
|
|
37
|
+
const option = (field.options ?? []).find((entry) => String(entry.value ?? '').trim() === normalized)
|
|
38
|
+
return String(option?.label ?? normalized).trim()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type FilterOptionLabelField = {
|
|
42
|
+
key: string
|
|
43
|
+
options?: readonly FilterFieldOption[] | null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function buildFilterOptionLabelsByKey<TField extends FilterOptionLabelField>(
|
|
47
|
+
fields: readonly TField[],
|
|
48
|
+
): Record<string, Map<string, string>> {
|
|
49
|
+
const result: Record<string, Map<string, string>> = {}
|
|
50
|
+
|
|
51
|
+
for (const field of fields) {
|
|
52
|
+
if (!field.key || !field.options?.length) continue
|
|
53
|
+
|
|
54
|
+
const options = new Map<string, string>()
|
|
55
|
+
for (const option of field.options) {
|
|
56
|
+
const value = String(option?.value ?? '').trim()
|
|
57
|
+
const label = String(option?.label ?? '').trim()
|
|
58
|
+
if (!value || !label) continue
|
|
59
|
+
options.set(value, label)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (options.size > 0) result[field.key] = options
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return result
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function filterInputType(dataType: unknown): 'number' | 'text' {
|
|
69
|
+
const normalized = String(dataType ?? '').trim().toLowerCase()
|
|
70
|
+
if (normalized === 'decimal' || normalized === 'money' || normalized === 'int32' || normalized === '2' || normalized === '3') {
|
|
71
|
+
return 'number'
|
|
72
|
+
}
|
|
73
|
+
if (normalized.includes('decimal') || normalized.includes('number') || normalized.includes('int')) return 'number'
|
|
74
|
+
return 'text'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function filterPlaceholder(field: Pick<FilterFieldLike, 'label' | 'lookup' | 'isMulti'>): string {
|
|
78
|
+
if (field.lookup) return `Type ${field.label.toLowerCase()}…`
|
|
79
|
+
if (field.isMulti) return 'Comma-separated values…'
|
|
80
|
+
return field.label
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function filterSelectOptions(
|
|
84
|
+
field: Pick<FilterFieldLike, 'options'>,
|
|
85
|
+
emptyLabel = 'Any',
|
|
86
|
+
): FilterFieldOption[] {
|
|
87
|
+
return [{ value: '', label: emptyLabel }, ...((field.options ?? []) as FilterFieldOption[])]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function extractLookupIds(value: unknown): string[] {
|
|
91
|
+
if (Array.isArray(value)) return value.filter(isNonEmptyGuid)
|
|
92
|
+
return isNonEmptyGuid(value) ? [value] : []
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function labelForResolvedLookup(
|
|
96
|
+
lookupStore: LookupStoreApi,
|
|
97
|
+
lookup: ResolvedLookupSource,
|
|
98
|
+
id: string,
|
|
99
|
+
): string {
|
|
100
|
+
if (lookup.kind === 'catalog') return lookupStore.labelForCatalog(lookup.catalogType, id)
|
|
101
|
+
if (lookup.kind === 'coa') return lookupStore.labelForCoa(id)
|
|
102
|
+
return lookupStore.labelForAnyDocument(lookup.documentTypes, id)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function ensureResolvedLookupLabels(
|
|
106
|
+
lookupStore: LookupStoreApi,
|
|
107
|
+
lookup: ResolvedLookupSource,
|
|
108
|
+
ids: string[],
|
|
109
|
+
): Promise<void> {
|
|
110
|
+
if (lookup.kind === 'catalog') {
|
|
111
|
+
await lookupStore.ensureCatalogLabels(lookup.catalogType, ids)
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (lookup.kind === 'coa') {
|
|
116
|
+
await lookupStore.ensureCoaLabels(ids)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await lookupStore.ensureAnyDocumentLabels(lookup.documentTypes, ids)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function searchResolvedLookupItems<TItem extends FilterLookupItem>(
|
|
124
|
+
lookupStore: LookupStoreApi<TItem>,
|
|
125
|
+
lookup: ResolvedLookupSource,
|
|
126
|
+
query: string,
|
|
127
|
+
): Promise<TItem[]> {
|
|
128
|
+
if (lookup.kind === 'catalog') {
|
|
129
|
+
return await lookupStore.searchCatalog(
|
|
130
|
+
lookup.catalogType,
|
|
131
|
+
query,
|
|
132
|
+
'filters' in lookup && lookup.filters ? { filters: lookup.filters } : undefined,
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (lookup.kind === 'coa') return await lookupStore.searchCoa(query)
|
|
137
|
+
return await lookupStore.searchDocuments(lookup.documentTypes, query)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function hydrateResolvedLookupItems<TItem extends FilterLookupItem>(
|
|
141
|
+
lookupStore: LookupStoreApi<TItem>,
|
|
142
|
+
lookup: ResolvedLookupSource,
|
|
143
|
+
ids: string[],
|
|
144
|
+
): Promise<TItem[]> {
|
|
145
|
+
if (ids.length === 0) return []
|
|
146
|
+
|
|
147
|
+
await ensureResolvedLookupLabels(lookupStore, lookup, ids)
|
|
148
|
+
|
|
149
|
+
return ids.map((id) => ({
|
|
150
|
+
id,
|
|
151
|
+
label: labelForResolvedLookup(lookupStore, lookup, id),
|
|
152
|
+
}) as TItem)
|
|
153
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { resolveNgbMetadataFormBehavior } from './config'
|
|
2
|
+
import type { EntityFormModel, FieldMetadata, LookupHint, LookupSource, MetadataFormBehavior } from './types'
|
|
3
|
+
|
|
4
|
+
export function lookupHintFromSource(source?: LookupSource | null): LookupHint | null {
|
|
5
|
+
if (!source) return null
|
|
6
|
+
|
|
7
|
+
if (source.kind === 'catalog') return { kind: 'catalog', catalogType: source.catalogType }
|
|
8
|
+
if (source.kind === 'document') return { kind: 'document', documentTypes: [...source.documentTypes] }
|
|
9
|
+
return { kind: 'coa' }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function resolveLookupHint(args: {
|
|
13
|
+
entityTypeCode: string
|
|
14
|
+
model: EntityFormModel
|
|
15
|
+
field: FieldMetadata
|
|
16
|
+
behavior?: MetadataFormBehavior
|
|
17
|
+
}): LookupHint | null {
|
|
18
|
+
const behavior = resolveNgbMetadataFormBehavior(args.behavior)
|
|
19
|
+
|
|
20
|
+
return behavior.resolveLookupHint?.({
|
|
21
|
+
entityTypeCode: args.entityTypeCode,
|
|
22
|
+
model: args.model,
|
|
23
|
+
field: args.field,
|
|
24
|
+
}) ?? lookupHintFromSource(args.field.lookup)
|
|
25
|
+
}
|