@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,108 @@
|
|
|
1
|
+
import { computed } from 'vue'
|
|
2
|
+
import type { ComputedRef } from 'vue'
|
|
3
|
+
import type { RouteLocationNormalizedLoaded, Router } from 'vue-router'
|
|
4
|
+
import { monthValueToDateOnly, normalizeMonthValue } from '../utils/dateValues'
|
|
5
|
+
import { normalizeTrashMode, pushCleanRouteQuery, type QueryTrashMode } from '../router/queryParams'
|
|
6
|
+
|
|
7
|
+
type UseMonthPagedListQueryArgs = {
|
|
8
|
+
route: RouteLocationNormalizedLoaded
|
|
9
|
+
router: Router
|
|
10
|
+
defaultLimit?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function parseNonNegativeInteger(value: unknown, fallback: number): number {
|
|
14
|
+
const parsed = Number(value)
|
|
15
|
+
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : fallback
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parsePositiveInteger(value: unknown, fallback: number): number {
|
|
19
|
+
const parsed = Number(value)
|
|
20
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function monthValueToDateOnlyStart(monthValue: string): string | undefined {
|
|
24
|
+
return monthValueToDateOnly(monthValue) ?? undefined
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function monthValueToDateOnlyEnd(monthValue: string): string | undefined {
|
|
28
|
+
if (!/^\d{4}-\d{2}$/.test(monthValue)) return undefined
|
|
29
|
+
|
|
30
|
+
const [year, month] = monthValue.split('-').map((part) => Number(part))
|
|
31
|
+
if (!Number.isFinite(year) || !Number.isFinite(month)) return undefined
|
|
32
|
+
|
|
33
|
+
const lastDay = new Date(year, month, 0).getDate()
|
|
34
|
+
return `${monthValue}-${String(lastDay).padStart(2, '0')}`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function useMonthPagedListQuery(args: UseMonthPagedListQueryArgs) {
|
|
38
|
+
const defaultLimit = args.defaultLimit ?? 50
|
|
39
|
+
|
|
40
|
+
const offset = computed(() => parseNonNegativeInteger(args.route.query.offset, 0))
|
|
41
|
+
const limit = computed(() => parsePositiveInteger(args.route.query.limit, defaultLimit))
|
|
42
|
+
|
|
43
|
+
function pushQuery(partial: Record<string, unknown>, options?: { preserveOffset?: boolean }) {
|
|
44
|
+
void pushCleanRouteQuery(args.route, args.router, {
|
|
45
|
+
...partial,
|
|
46
|
+
...(options?.preserveOffset ? {} : { offset: 0 }),
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const trashMode = computed<QueryTrashMode>({
|
|
51
|
+
get() {
|
|
52
|
+
return normalizeTrashMode(args.route.query.trash)
|
|
53
|
+
},
|
|
54
|
+
set(value) {
|
|
55
|
+
pushQuery({ trash: value })
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const periodFromMonth = computed<string>({
|
|
60
|
+
get() {
|
|
61
|
+
return normalizeMonthValue(args.route.query.periodFrom) ?? ''
|
|
62
|
+
},
|
|
63
|
+
set(value) {
|
|
64
|
+
pushQuery({ periodFrom: value || undefined })
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const periodToMonth = computed<string>({
|
|
69
|
+
get() {
|
|
70
|
+
return normalizeMonthValue(args.route.query.periodTo) ?? ''
|
|
71
|
+
},
|
|
72
|
+
set(value) {
|
|
73
|
+
pushQuery({ periodTo: value || undefined })
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
function updateListQuery(partial: Record<string, unknown>, options?: { preserveOffset?: boolean }) {
|
|
78
|
+
pushQuery(partial, options)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function nextPage() {
|
|
82
|
+
pushQuery({ offset: offset.value + limit.value }, { preserveOffset: true })
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function prevPage() {
|
|
86
|
+
pushQuery({ offset: Math.max(0, offset.value - limit.value) }, { preserveOffset: true })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
offset,
|
|
91
|
+
limit,
|
|
92
|
+
trashMode,
|
|
93
|
+
periodFromMonth,
|
|
94
|
+
periodToMonth,
|
|
95
|
+
updateListQuery,
|
|
96
|
+
nextPage,
|
|
97
|
+
prevPage,
|
|
98
|
+
} satisfies {
|
|
99
|
+
offset: ComputedRef<number>
|
|
100
|
+
limit: ComputedRef<number>
|
|
101
|
+
trashMode: ComputedRef<QueryTrashMode>
|
|
102
|
+
periodFromMonth: ComputedRef<string>
|
|
103
|
+
periodToMonth: ComputedRef<string>
|
|
104
|
+
updateListQuery: (partial: Record<string, unknown>, options?: { preserveOffset?: boolean }) => void
|
|
105
|
+
nextPage: () => void
|
|
106
|
+
prevPage: () => void
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CatalogTypeMetadata,
|
|
3
|
+
DocumentTypeMetadata,
|
|
4
|
+
FieldMetadata,
|
|
5
|
+
FormMetadata,
|
|
6
|
+
ListFilterField,
|
|
7
|
+
ListMetadata,
|
|
8
|
+
PartMetadata,
|
|
9
|
+
} from './types'
|
|
10
|
+
|
|
11
|
+
const DATA_TYPE_BY_LEGACY_CODE: Record<number, string> = {
|
|
12
|
+
1: 'String',
|
|
13
|
+
2: 'Int32',
|
|
14
|
+
3: 'Decimal',
|
|
15
|
+
4: 'Boolean',
|
|
16
|
+
5: 'Date',
|
|
17
|
+
6: 'DateTime',
|
|
18
|
+
7: 'Money',
|
|
19
|
+
8: 'Guid',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalizeDataType(value: unknown): string {
|
|
23
|
+
if (typeof value === 'string') {
|
|
24
|
+
const normalized = value.trim()
|
|
25
|
+
return normalized || 'Unknown'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
29
|
+
return DATA_TYPE_BY_LEGACY_CODE[Math.trunc(value)] ?? 'Unknown'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return 'Unknown'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function normalizeListFilterField(field: ListFilterField): ListFilterField {
|
|
36
|
+
return {
|
|
37
|
+
...field,
|
|
38
|
+
dataType: normalizeDataType(field.dataType),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function normalizeListMetadata(list: ListMetadata | null | undefined): ListMetadata | null | undefined {
|
|
43
|
+
if (!list) return list
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
...list,
|
|
47
|
+
columns: (list.columns ?? []).map((column) => ({
|
|
48
|
+
...column,
|
|
49
|
+
dataType: normalizeDataType(column.dataType),
|
|
50
|
+
})),
|
|
51
|
+
filters: (list.filters ?? []).map(normalizeListFilterField),
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizeField(field: FieldMetadata): FieldMetadata {
|
|
56
|
+
return {
|
|
57
|
+
...field,
|
|
58
|
+
dataType: normalizeDataType(field.dataType),
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function normalizeFormMetadata(form: FormMetadata | null | undefined): FormMetadata | null | undefined {
|
|
63
|
+
if (!form) return form
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
...form,
|
|
67
|
+
sections: (form.sections ?? []).map((section) => ({
|
|
68
|
+
...section,
|
|
69
|
+
rows: (section.rows ?? []).map((row) => ({
|
|
70
|
+
...row,
|
|
71
|
+
fields: (row.fields ?? []).map(normalizeField),
|
|
72
|
+
})),
|
|
73
|
+
})),
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizePartMetadata(part: PartMetadata): PartMetadata {
|
|
78
|
+
return {
|
|
79
|
+
...part,
|
|
80
|
+
list: normalizeListMetadata(part.list) ?? part.list,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function normalizeCatalogTypeMetadata(metadata: CatalogTypeMetadata): CatalogTypeMetadata {
|
|
85
|
+
return {
|
|
86
|
+
...metadata,
|
|
87
|
+
list: normalizeListMetadata(metadata.list),
|
|
88
|
+
form: normalizeFormMetadata(metadata.form),
|
|
89
|
+
parts: (metadata.parts ?? []).map(normalizePartMetadata),
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function normalizeDocumentTypeMetadata(metadata: DocumentTypeMetadata): DocumentTypeMetadata {
|
|
94
|
+
return {
|
|
95
|
+
...metadata,
|
|
96
|
+
list: normalizeListMetadata(metadata.list),
|
|
97
|
+
form: normalizeFormMetadata(metadata.form),
|
|
98
|
+
parts: (metadata.parts ?? []).map(normalizePartMetadata),
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { resolveNgbMetadataFormBehavior } from './config'
|
|
2
|
+
import { isNonEmptyGuid } from '../utils/guid'
|
|
3
|
+
import { flattenFormFields } from './entityForm'
|
|
4
|
+
import { isReferenceValue } from './entityModel'
|
|
5
|
+
import { resolveLookupHint } from './lookup'
|
|
6
|
+
import type { EntityFormModel, FormMetadata, LookupHint, LookupStoreApi, MetadataFormBehavior, ReferenceValue } from './types'
|
|
7
|
+
|
|
8
|
+
type PendingReferenceHydration = {
|
|
9
|
+
fieldKey: string
|
|
10
|
+
id: string
|
|
11
|
+
hint: LookupHint
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function buildReferenceValue(id: string, display: string): ReferenceValue {
|
|
15
|
+
return {
|
|
16
|
+
id,
|
|
17
|
+
display: display.trim() || id,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function resolveReferenceLabel(lookupStore: LookupStoreApi, hint: LookupHint, id: string): string {
|
|
22
|
+
if (hint.kind === 'catalog') return lookupStore.labelForCatalog(hint.catalogType, id)
|
|
23
|
+
if (hint.kind === 'coa') return lookupStore.labelForCoa(id)
|
|
24
|
+
return lookupStore.labelForAnyDocument(hint.documentTypes, id)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function hydrateEntityReferenceFieldsForEditing(args: {
|
|
28
|
+
entityTypeCode: string
|
|
29
|
+
form: FormMetadata | null | undefined
|
|
30
|
+
model: EntityFormModel
|
|
31
|
+
lookupStore: LookupStoreApi
|
|
32
|
+
behavior?: MetadataFormBehavior
|
|
33
|
+
}): Promise<void> {
|
|
34
|
+
const pending: PendingReferenceHydration[] = []
|
|
35
|
+
const behavior = resolveNgbMetadataFormBehavior(args.behavior)
|
|
36
|
+
|
|
37
|
+
for (const field of flattenFormFields(args.form)) {
|
|
38
|
+
const value = args.model[field.key]
|
|
39
|
+
if (isReferenceValue(value) || !isNonEmptyGuid(value)) continue
|
|
40
|
+
|
|
41
|
+
const hint = resolveLookupHint({
|
|
42
|
+
entityTypeCode: args.entityTypeCode,
|
|
43
|
+
model: args.model,
|
|
44
|
+
field,
|
|
45
|
+
behavior,
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
if (!hint) continue
|
|
49
|
+
|
|
50
|
+
pending.push({
|
|
51
|
+
fieldKey: field.key,
|
|
52
|
+
id: value,
|
|
53
|
+
hint,
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (pending.length === 0) return
|
|
58
|
+
|
|
59
|
+
const catalogIdsByType = new Map<string, Set<string>>()
|
|
60
|
+
const documentIdsByTypesKey = new Map<string, { documentTypes: string[]; ids: Set<string> }>()
|
|
61
|
+
const coaIds = new Set<string>()
|
|
62
|
+
|
|
63
|
+
for (const entry of pending) {
|
|
64
|
+
if (entry.hint.kind === 'catalog') {
|
|
65
|
+
const ids = catalogIdsByType.get(entry.hint.catalogType) ?? new Set<string>()
|
|
66
|
+
ids.add(entry.id)
|
|
67
|
+
catalogIdsByType.set(entry.hint.catalogType, ids)
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (entry.hint.kind === 'coa') {
|
|
72
|
+
coaIds.add(entry.id)
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const key = entry.hint.documentTypes.join('|')
|
|
77
|
+
const group = documentIdsByTypesKey.get(key) ?? {
|
|
78
|
+
documentTypes: entry.hint.documentTypes,
|
|
79
|
+
ids: new Set<string>(),
|
|
80
|
+
}
|
|
81
|
+
group.ids.add(entry.id)
|
|
82
|
+
documentIdsByTypesKey.set(key, group)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const tasks: Promise<void>[] = []
|
|
86
|
+
for (const [catalogType, ids] of catalogIdsByType) {
|
|
87
|
+
tasks.push(args.lookupStore.ensureCatalogLabels(catalogType, [...ids]))
|
|
88
|
+
}
|
|
89
|
+
if (coaIds.size > 0) tasks.push(args.lookupStore.ensureCoaLabels([...coaIds]))
|
|
90
|
+
for (const group of documentIdsByTypesKey.values()) {
|
|
91
|
+
tasks.push(args.lookupStore.ensureAnyDocumentLabels(group.documentTypes, [...group.ids]))
|
|
92
|
+
}
|
|
93
|
+
await Promise.all(tasks)
|
|
94
|
+
|
|
95
|
+
for (const entry of pending) {
|
|
96
|
+
const display = resolveReferenceLabel(args.lookupStore, entry.hint, entry.id)
|
|
97
|
+
args.model[entry.fieldKey] = buildReferenceValue(entry.id, display)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { ColumnMetadata, JsonValue, RecordFields } from './types'
|
|
2
|
+
import type { RegisterColumn, RegisterDataRow } from '../components/register/registerTypes'
|
|
3
|
+
import { dataTypeKind } from './dataTypes'
|
|
4
|
+
import { isReferenceValue } from './entityModel'
|
|
5
|
+
|
|
6
|
+
export type RegisterAlign = NonNullable<RegisterColumn['align']>
|
|
7
|
+
export type RegisterRow = RegisterDataRow
|
|
8
|
+
|
|
9
|
+
type RegisterRecordItem = {
|
|
10
|
+
id: string
|
|
11
|
+
payload?: {
|
|
12
|
+
fields?: RecordFields | null
|
|
13
|
+
} | null
|
|
14
|
+
isDeleted?: boolean
|
|
15
|
+
isMarkedForDeletion?: boolean
|
|
16
|
+
status?: unknown
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type BuildColumnsArgs = {
|
|
20
|
+
columns: readonly ColumnMetadata[]
|
|
21
|
+
optionLabelsByColumnKey?: Record<string, Map<string, string>>
|
|
22
|
+
formatOverride?: (column: ColumnMetadata, value: unknown) => string | null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type BuildRowsArgs<TItem extends RegisterRecordItem> = {
|
|
26
|
+
items: readonly TItem[]
|
|
27
|
+
columns: readonly ColumnMetadata[]
|
|
28
|
+
mapFieldValue?: (column: ColumnMetadata, rawValue: JsonValue | undefined, item: TItem) => unknown
|
|
29
|
+
extendRow?: (row: RegisterRow, item: TItem) => void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const dateOnlyRe = /^\d{4}-\d{2}-\d{2}$/
|
|
33
|
+
|
|
34
|
+
export function alignFromDto(value: unknown): RegisterAlign {
|
|
35
|
+
if (value === 2) return 'center'
|
|
36
|
+
if (value === 3) return 'right'
|
|
37
|
+
return 'left'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function prettifyRegisterTitle(label: string, key: string): string {
|
|
41
|
+
const normalized = key.toLowerCase()
|
|
42
|
+
if (normalized.endsWith('_id') || normalized.endsWith('_account_id')) return label.replace(/\s+Id$/i, '')
|
|
43
|
+
return label
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function tryFormatDateOnly(value: unknown): string | null {
|
|
47
|
+
if (typeof value !== 'string') return null
|
|
48
|
+
if (!dateOnlyRe.test(value)) return null
|
|
49
|
+
|
|
50
|
+
const [year, month, day] = value.split('-').map((part) => Number(part))
|
|
51
|
+
if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) return null
|
|
52
|
+
|
|
53
|
+
const date = new Date(year, month - 1, day)
|
|
54
|
+
if (Number.isNaN(date.getTime())) return null
|
|
55
|
+
return date.toLocaleDateString()
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function formatRegisterValue(dataType: unknown, value: unknown): string {
|
|
59
|
+
if (value == null) return '—'
|
|
60
|
+
if (isReferenceValue(value)) return value.display
|
|
61
|
+
|
|
62
|
+
const kind = dataTypeKind(dataType)
|
|
63
|
+
|
|
64
|
+
if (kind === 'Boolean') return value ? 'Yes' : 'No'
|
|
65
|
+
|
|
66
|
+
if (kind === 'Int32') {
|
|
67
|
+
const parsed = Number(value)
|
|
68
|
+
if (Number.isFinite(parsed)) return parsed.toLocaleString(undefined, { maximumFractionDigits: 0 })
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (kind === 'Decimal' || kind === 'Money') {
|
|
72
|
+
const parsed = Number(value)
|
|
73
|
+
if (Number.isFinite(parsed)) {
|
|
74
|
+
return parsed.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (kind === 'Date') {
|
|
79
|
+
const raw = typeof value === 'string' ? value : String(value)
|
|
80
|
+
const dateOnly = tryFormatDateOnly(raw)
|
|
81
|
+
if (dateOnly) return dateOnly
|
|
82
|
+
|
|
83
|
+
const date = new Date(value as string | number | Date)
|
|
84
|
+
if (!Number.isNaN(date.getTime())) return date.toLocaleDateString()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (kind === 'DateTime') {
|
|
88
|
+
const date = new Date(value as string | number | Date)
|
|
89
|
+
if (!Number.isNaN(date.getTime())) return date.toLocaleString()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (typeof value === 'object') return JSON.stringify(value)
|
|
93
|
+
return String(value)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function buildMetadataRegisterColumns(args: BuildColumnsArgs): RegisterColumn[] {
|
|
97
|
+
return args.columns.map((column) => ({
|
|
98
|
+
key: column.key,
|
|
99
|
+
title: prettifyRegisterTitle(column.label, column.key),
|
|
100
|
+
width: column.widthPx ?? undefined,
|
|
101
|
+
align: alignFromDto(column.align),
|
|
102
|
+
sortable: column.isSortable,
|
|
103
|
+
format: (value: unknown) => {
|
|
104
|
+
const override = args.formatOverride?.(column, value)
|
|
105
|
+
if (override != null) return override
|
|
106
|
+
|
|
107
|
+
const normalizedValue = String(value ?? '').trim()
|
|
108
|
+
const optionLabel = args.optionLabelsByColumnKey?.[column.key]?.get(normalizedValue)
|
|
109
|
+
?? column.options?.find((entry) => String(entry.value ?? '').trim() === normalizedValue)?.label
|
|
110
|
+
if (optionLabel) return optionLabel
|
|
111
|
+
|
|
112
|
+
return formatRegisterValue(column.dataType, value)
|
|
113
|
+
},
|
|
114
|
+
}))
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function buildMetadataRegisterRows<TItem extends RegisterRecordItem>(args: BuildRowsArgs<TItem>): RegisterRow[] {
|
|
118
|
+
return args.items.map((item) => {
|
|
119
|
+
const fields = (item.payload?.fields ?? {}) as RecordFields
|
|
120
|
+
const row: RegisterRow = {
|
|
121
|
+
key: item.id,
|
|
122
|
+
isDeleted: item.isDeleted,
|
|
123
|
+
isMarkedForDeletion: item.isMarkedForDeletion,
|
|
124
|
+
status: item.status,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const column of args.columns) {
|
|
128
|
+
const rawValue = fields[column.key]
|
|
129
|
+
row[column.key] = args.mapFieldValue?.(column, rawValue, item) ?? rawValue
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
args.extendRow?.(row, item)
|
|
133
|
+
return row
|
|
134
|
+
})
|
|
135
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Component } from 'vue'
|
|
2
|
+
import type { RouteLocationNormalizedLoaded, Router } from 'vue-router'
|
|
3
|
+
|
|
4
|
+
import type { EntityEditorFlags, EntityHeaderIconAction } from '../editor/types'
|
|
5
|
+
import type { QueryTrashMode } from '../router/queryParams'
|
|
6
|
+
import type { Awaitable, LookupHint, LookupSource } from './types'
|
|
7
|
+
import type { MetadataRegisterPageMetadata, MetadataRegisterPageResponse } from './useMetadataRegisterPageData'
|
|
8
|
+
|
|
9
|
+
export type MetadataCatalogListPageLoadArgs = {
|
|
10
|
+
catalogType: string
|
|
11
|
+
offset: number
|
|
12
|
+
limit: number
|
|
13
|
+
search?: string
|
|
14
|
+
trashMode: QueryTrashMode
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type MetadataCatalogDrawerActionArgs = {
|
|
18
|
+
action: string
|
|
19
|
+
editor: unknown | null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface MetadataCatalogListPageProps {
|
|
23
|
+
editorComponent: Component
|
|
24
|
+
loadPage: (args: MetadataCatalogListPageLoadArgs) => Promise<MetadataRegisterPageResponse>
|
|
25
|
+
resolveTitle?: ((catalogType: string, displayName: string) => string) | null
|
|
26
|
+
resolveStorageKey?: ((catalogType: string) => string | null | undefined) | null
|
|
27
|
+
resolveDrawerExtraActions?: ((args: { editorFlags: EntityEditorFlags }) => EntityHeaderIconAction[]) | null
|
|
28
|
+
handleDrawerExtraAction?: ((args: MetadataCatalogDrawerActionArgs) => Awaitable<boolean | void>) | null
|
|
29
|
+
backTarget?: string | null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface MetadataCatalogEditPageProps {
|
|
33
|
+
editorComponent: Component
|
|
34
|
+
canBack?: boolean
|
|
35
|
+
resolveCompactTo?: ((catalogType: string, id?: string | null) => string | null | undefined) | null
|
|
36
|
+
resolveCloseTo?: ((catalogType: string) => string | null | undefined) | null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type MetadataDocumentListPageLoadArgs<TMeta extends MetadataRegisterPageMetadata = MetadataRegisterPageMetadata> = {
|
|
40
|
+
documentType: string
|
|
41
|
+
metadata: TMeta
|
|
42
|
+
offset: number
|
|
43
|
+
limit: number
|
|
44
|
+
search?: string
|
|
45
|
+
trashMode: QueryTrashMode
|
|
46
|
+
periodFrom?: string | null
|
|
47
|
+
periodTo?: string | null
|
|
48
|
+
listFilters: Record<string, string>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type MetadataRouteLocationLike = Pick<RouteLocationNormalizedLoaded, 'fullPath' | 'params' | 'query'>
|
|
52
|
+
|
|
53
|
+
export type MetadataRouterLike = Pick<Router, 'push'>
|
|
54
|
+
|
|
55
|
+
export type MetadataDocumentCreateOverrideArgs<TMeta extends MetadataRegisterPageMetadata = MetadataRegisterPageMetadata> = {
|
|
56
|
+
documentType: string
|
|
57
|
+
metadata: TMeta | null
|
|
58
|
+
preferFullPage: boolean
|
|
59
|
+
route: MetadataRouteLocationLike
|
|
60
|
+
router: MetadataRouterLike
|
|
61
|
+
openCreateDrawer: (copyDraftToken?: string | null) => void
|
|
62
|
+
openFullPage: () => Promise<void>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface MetadataDocumentListPageProps<TMeta extends MetadataRegisterPageMetadata = MetadataRegisterPageMetadata> {
|
|
66
|
+
editorComponent: Component
|
|
67
|
+
loadPage: (args: MetadataDocumentListPageLoadArgs<TMeta>) => Promise<MetadataRegisterPageResponse>
|
|
68
|
+
resolveLookupHint?: ((args: {
|
|
69
|
+
entityTypeCode: string
|
|
70
|
+
fieldKey: string
|
|
71
|
+
lookup?: LookupSource | null
|
|
72
|
+
}) => LookupHint | null) | null
|
|
73
|
+
resolveTitle?: ((documentType: string, displayName: string) => string) | null
|
|
74
|
+
resolveStorageKey?: ((documentType: string) => string | null | undefined) | null
|
|
75
|
+
resolveWarning?: ((documentType: string, metadata: TMeta | null) => string | null | undefined) | null
|
|
76
|
+
isCreateDisabled?: ((documentType: string, metadata: TMeta | null) => boolean) | null
|
|
77
|
+
handleCreateOverride?: ((args: MetadataDocumentCreateOverrideArgs<TMeta>) => Awaitable<boolean | void>) | null
|
|
78
|
+
backTarget?: string | null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface MetadataDocumentEditPageProps {
|
|
82
|
+
editorComponent: Component
|
|
83
|
+
canBack?: boolean
|
|
84
|
+
resolveCompactTo?: ((documentType: string, id?: string | null) => string | null | undefined) | null
|
|
85
|
+
resolveCloseTo?: ((documentType: string) => string | null | undefined) | null
|
|
86
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { defineStore } from 'pinia'
|
|
2
|
+
import { getConfiguredNgbMetadata } from './config'
|
|
3
|
+
import { normalizeCatalogTypeMetadata, normalizeDocumentTypeMetadata } from './normalization'
|
|
4
|
+
import type { CatalogTypeMetadata, DocumentTypeMetadata } from './types'
|
|
5
|
+
|
|
6
|
+
export const useMetadataStore = defineStore('metadata', {
|
|
7
|
+
state: () => ({
|
|
8
|
+
catalogs: {} as Record<string, CatalogTypeMetadata>,
|
|
9
|
+
documents: {} as Record<string, DocumentTypeMetadata>,
|
|
10
|
+
}),
|
|
11
|
+
actions: {
|
|
12
|
+
async ensureCatalogType(catalogType: string): Promise<CatalogTypeMetadata> {
|
|
13
|
+
if (!this.catalogs[catalogType]) {
|
|
14
|
+
const config = getConfiguredNgbMetadata()
|
|
15
|
+
this.catalogs[catalogType] = normalizeCatalogTypeMetadata(await config.loadCatalogTypeMetadata(catalogType))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return this.catalogs[catalogType]
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
async ensureDocumentType(documentType: string): Promise<DocumentTypeMetadata> {
|
|
22
|
+
if (!this.documents[documentType]) {
|
|
23
|
+
const config = getConfiguredNgbMetadata()
|
|
24
|
+
this.documents[documentType] = normalizeDocumentTypeMetadata(await config.loadDocumentTypeMetadata(documentType))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return this.documents[documentType]
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
clear() {
|
|
31
|
+
this.catalogs = {}
|
|
32
|
+
this.documents = {}
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
})
|