@ngbplatform/ui 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +44 -0
  3. package/package.json +61 -0
  4. package/public/favicon.svg +6 -0
  5. package/public/silent-check-sso.html +8 -0
  6. package/src/assets/ngb_logo.svg +17 -0
  7. package/src/assets/ngb_logo_color.svg +17 -0
  8. package/src/index.ts +1023 -0
  9. package/src/ngb/README.md +27 -0
  10. package/src/ngb/accounting/NgbAccountingPeriodClosingPage.vue +1157 -0
  11. package/src/ngb/accounting/NgbChartOfAccountEditor.vue +590 -0
  12. package/src/ngb/accounting/NgbChartOfAccountsPage.vue +420 -0
  13. package/src/ngb/accounting/NgbGeneralJournalEntryEditPage.vue +796 -0
  14. package/src/ngb/accounting/NgbGeneralJournalEntryLinesEditor.vue +454 -0
  15. package/src/ngb/accounting/NgbGeneralJournalEntryListPage.vue +186 -0
  16. package/src/ngb/accounting/api.ts +79 -0
  17. package/src/ngb/accounting/generalJournalEntry.ts +127 -0
  18. package/src/ngb/accounting/generalJournalEntryApi.ts +112 -0
  19. package/src/ngb/accounting/generalJournalEntryTypes.ts +155 -0
  20. package/src/ngb/accounting/navigation.ts +83 -0
  21. package/src/ngb/accounting/periodClosing.ts +75 -0
  22. package/src/ngb/accounting/periodClosingApi.ts +60 -0
  23. package/src/ngb/accounting/periodClosingTypes.ts +73 -0
  24. package/src/ngb/accounting/types.ts +57 -0
  25. package/src/ngb/api/audit.ts +23 -0
  26. package/src/ngb/api/catalogs.ts +50 -0
  27. package/src/ngb/api/contracts.ts +285 -0
  28. package/src/ngb/api/documents.ts +170 -0
  29. package/src/ngb/api/http.ts +446 -0
  30. package/src/ngb/api/lookups.ts +14 -0
  31. package/src/ngb/api/types.ts +6 -0
  32. package/src/ngb/auth/index.ts +13 -0
  33. package/src/ngb/auth/keycloak.ts +374 -0
  34. package/src/ngb/auth/router.ts +35 -0
  35. package/src/ngb/auth/types.ts +12 -0
  36. package/src/ngb/auth/useAuthStore.ts +209 -0
  37. package/src/ngb/command-palette/NgbCommandPaletteDialog.vue +342 -0
  38. package/src/ngb/command-palette/NgbCommandPaletteEmptyState.vue +55 -0
  39. package/src/ngb/command-palette/NgbCommandPaletteFooterHints.vue +29 -0
  40. package/src/ngb/command-palette/api.ts +10 -0
  41. package/src/ngb/command-palette/config.ts +16 -0
  42. package/src/ngb/command-palette/presentation.ts +85 -0
  43. package/src/ngb/command-palette/search.ts +130 -0
  44. package/src/ngb/command-palette/staticItems.ts +243 -0
  45. package/src/ngb/command-palette/storage.ts +45 -0
  46. package/src/ngb/command-palette/store.ts +716 -0
  47. package/src/ngb/command-palette/types.ts +120 -0
  48. package/src/ngb/command-palette/useCommandPaletteHotkeys.ts +27 -0
  49. package/src/ngb/command-palette/useCommandPalettePageContext.ts +18 -0
  50. package/src/ngb/components/NgbConfirmDialog.vue +63 -0
  51. package/src/ngb/components/NgbDialog.vue +44 -0
  52. package/src/ngb/components/NgbDrawer.vue +156 -0
  53. package/src/ngb/components/NgbHeaderActionCluster.vue +140 -0
  54. package/src/ngb/components/NgbModalShell.vue +99 -0
  55. package/src/ngb/components/NgbToolbar.vue +14 -0
  56. package/src/ngb/components/forms/NgbFormLayout.vue +13 -0
  57. package/src/ngb/components/forms/NgbFormRow.vue +21 -0
  58. package/src/ngb/components/forms/NgbFormSection.vue +16 -0
  59. package/src/ngb/components/forms/NgbValidationSummary.vue +18 -0
  60. package/src/ngb/components/navigation/NgbNavigationTree.vue +68 -0
  61. package/src/ngb/components/navigation/NgbTreeNode.vue +94 -0
  62. package/src/ngb/components/register/NgbColumnChooser.vue +101 -0
  63. package/src/ngb/components/register/NgbRegisterGrid.vue +473 -0
  64. package/src/ngb/components/register/registerTypes.ts +51 -0
  65. package/src/ngb/components/register/useRegisterColumnResize.ts +99 -0
  66. package/src/ngb/components/register/useRegisterColumnState.ts +220 -0
  67. package/src/ngb/components/register/useRegisterRows.ts +209 -0
  68. package/src/ngb/components/register/useRegisterViewport.ts +56 -0
  69. package/src/ngb/editor/NgbDocumentEffectsPage.vue +512 -0
  70. package/src/ngb/editor/NgbDocumentFlowPage.vue +409 -0
  71. package/src/ngb/editor/NgbDocumentPrintPage.vue +712 -0
  72. package/src/ngb/editor/NgbEditorDiscardDialog.vue +40 -0
  73. package/src/ngb/editor/NgbEntityAuditSidebar.vue +288 -0
  74. package/src/ngb/editor/NgbEntityEditor.vue +250 -0
  75. package/src/ngb/editor/NgbEntityEditorDrawerActions.vue +88 -0
  76. package/src/ngb/editor/NgbEntityEditorHeader.vue +114 -0
  77. package/src/ngb/editor/catalogNavigation.ts +13 -0
  78. package/src/ngb/editor/config.ts +252 -0
  79. package/src/ngb/editor/documentCopyDraft.ts +245 -0
  80. package/src/ngb/editor/documentEffects.ts +43 -0
  81. package/src/ngb/editor/documentEffectsDateFormatting.ts +18 -0
  82. package/src/ngb/editor/documentNavigation.ts +115 -0
  83. package/src/ngb/editor/documentStatus.ts +45 -0
  84. package/src/ngb/editor/entityEditorErrors.ts +162 -0
  85. package/src/ngb/editor/entityEditorPersistence.ts +241 -0
  86. package/src/ngb/editor/extensions.ts +40 -0
  87. package/src/ngb/editor/types.ts +295 -0
  88. package/src/ngb/editor/useConfiguredEntityEditorDocumentActions.ts +183 -0
  89. package/src/ngb/editor/useDocumentEditorDrawerState.ts +152 -0
  90. package/src/ngb/editor/useEditorDrawerState.ts +88 -0
  91. package/src/ngb/editor/useEntityEditorBusinessContext.ts +97 -0
  92. package/src/ngb/editor/useEntityEditorCapabilities.ts +190 -0
  93. package/src/ngb/editor/useEntityEditorCommandPalette.ts +154 -0
  94. package/src/ngb/editor/useEntityEditorCommitHandlers.ts +102 -0
  95. package/src/ngb/editor/useEntityEditorHeaderActions.ts +253 -0
  96. package/src/ngb/editor/useEntityEditorLeaveGuard.ts +66 -0
  97. package/src/ngb/editor/useEntityEditorNavigationActions.ts +235 -0
  98. package/src/ngb/editor/useEntityEditorOutputs.ts +59 -0
  99. package/src/ngb/editor/useEntityEditorPageActions.ts +57 -0
  100. package/src/ngb/editor/useRouteQueryEditorDrawer.ts +173 -0
  101. package/src/ngb/env/runtimeConfig.ts +25 -0
  102. package/src/ngb/lookup/config.ts +42 -0
  103. package/src/ngb/lookup/defaultConfig.ts +136 -0
  104. package/src/ngb/lookup/navigation.ts +76 -0
  105. package/src/ngb/lookup/prefetch.ts +40 -0
  106. package/src/ngb/lookup/store.ts +261 -0
  107. package/src/ngb/metadata/NgbDocumentListFiltersDrawer.vue +78 -0
  108. package/src/ngb/metadata/NgbDocumentPeriodFilter.vue +53 -0
  109. package/src/ngb/metadata/NgbEntityForm.vue +84 -0
  110. package/src/ngb/metadata/NgbEntityFormFieldsBlock.vue +110 -0
  111. package/src/ngb/metadata/NgbEntityListPageHeader.vue +80 -0
  112. package/src/ngb/metadata/NgbFilterFieldControl.vue +104 -0
  113. package/src/ngb/metadata/NgbMetadataCatalogEditPage.vue +42 -0
  114. package/src/ngb/metadata/NgbMetadataCatalogListPage.vue +269 -0
  115. package/src/ngb/metadata/NgbMetadataDocumentEditPage.vue +68 -0
  116. package/src/ngb/metadata/NgbMetadataDocumentListPage.vue +353 -0
  117. package/src/ngb/metadata/NgbMetadataFieldRenderer.vue +124 -0
  118. package/src/ngb/metadata/NgbMetadataLookupControl.vue +92 -0
  119. package/src/ngb/metadata/NgbRecycleBinFilter.vue +53 -0
  120. package/src/ngb/metadata/NgbRegisterPageLayout.vue +143 -0
  121. package/src/ngb/metadata/config.ts +28 -0
  122. package/src/ngb/metadata/dataTypes.ts +27 -0
  123. package/src/ngb/metadata/entityForm.ts +114 -0
  124. package/src/ngb/metadata/entityModel.ts +34 -0
  125. package/src/ngb/metadata/entityValueFormatting.ts +83 -0
  126. package/src/ngb/metadata/fieldRendererState.ts +84 -0
  127. package/src/ngb/metadata/filtering.ts +153 -0
  128. package/src/ngb/metadata/lookup.ts +25 -0
  129. package/src/ngb/metadata/monthPagedListQuery.ts +108 -0
  130. package/src/ngb/metadata/normalization.ts +100 -0
  131. package/src/ngb/metadata/referenceHydration.ts +99 -0
  132. package/src/ngb/metadata/register.ts +135 -0
  133. package/src/ngb/metadata/routePages.ts +86 -0
  134. package/src/ngb/metadata/store.ts +35 -0
  135. package/src/ngb/metadata/types.ts +276 -0
  136. package/src/ngb/metadata/useMetadataListFilters.ts +306 -0
  137. package/src/ngb/metadata/useMetadataRegisterPageData.ts +246 -0
  138. package/src/ngb/metadata/useValidationFocus.ts +49 -0
  139. package/src/ngb/primitives/NgbBadge.vue +31 -0
  140. package/src/ngb/primitives/NgbButton.vue +53 -0
  141. package/src/ngb/primitives/NgbCheckbox.vue +33 -0
  142. package/src/ngb/primitives/NgbDatePicker.vue +141 -0
  143. package/src/ngb/primitives/NgbIcon.vue +545 -0
  144. package/src/ngb/primitives/NgbInput.vue +57 -0
  145. package/src/ngb/primitives/NgbLookup.vue +264 -0
  146. package/src/ngb/primitives/NgbMonthPicker.vue +132 -0
  147. package/src/ngb/primitives/NgbMultiSelect.vue +179 -0
  148. package/src/ngb/primitives/NgbPeriodPicker.vue +125 -0
  149. package/src/ngb/primitives/NgbPickerNavButton.vue +31 -0
  150. package/src/ngb/primitives/NgbPickerPopover.vue +79 -0
  151. package/src/ngb/primitives/NgbRadioGroup.vue +53 -0
  152. package/src/ngb/primitives/NgbSelect.vue +108 -0
  153. package/src/ngb/primitives/NgbStatusIcon.vue +63 -0
  154. package/src/ngb/primitives/NgbSwitch.vue +42 -0
  155. package/src/ngb/primitives/NgbTabs.vue +116 -0
  156. package/src/ngb/primitives/NgbToastHost.vue +49 -0
  157. package/src/ngb/primitives/NgbTooltip.vue +27 -0
  158. package/src/ngb/primitives/iconNames.ts +83 -0
  159. package/src/ngb/primitives/toast.ts +66 -0
  160. package/src/ngb/primitives/useAsyncComboboxQuery.ts +85 -0
  161. package/src/ngb/primitives/useFloatingDropdownPosition.ts +129 -0
  162. package/src/ngb/reporting/NgbReportComposerCollectionSection.vue +106 -0
  163. package/src/ngb/reporting/NgbReportComposerPanel.vue +1187 -0
  164. package/src/ngb/reporting/NgbReportDateRangeFilter.vue +54 -0
  165. package/src/ngb/reporting/NgbReportPage.vue +1124 -0
  166. package/src/ngb/reporting/NgbReportSheet.vue +467 -0
  167. package/src/ngb/reporting/api.ts +35 -0
  168. package/src/ngb/reporting/composer.ts +904 -0
  169. package/src/ngb/reporting/config.ts +120 -0
  170. package/src/ngb/reporting/defaultConfig.ts +15 -0
  171. package/src/ngb/reporting/lookupFilters.ts +43 -0
  172. package/src/ngb/reporting/navigation.ts +181 -0
  173. package/src/ngb/reporting/pageHelpers.ts +73 -0
  174. package/src/ngb/reporting/pageSession.ts +92 -0
  175. package/src/ngb/reporting/paging.ts +66 -0
  176. package/src/ngb/reporting/types.ts +333 -0
  177. package/src/ngb/router/backNavigation.ts +122 -0
  178. package/src/ngb/router/queryParams.ts +129 -0
  179. package/src/ngb/router/queryState.ts +121 -0
  180. package/src/ngb/router/routeAliases.ts +66 -0
  181. package/src/ngb/router/routeParams.ts +15 -0
  182. package/src/ngb/router/shareLink.ts +39 -0
  183. package/src/ngb/security/NgbAccessAuditPanel.vue +32 -0
  184. package/src/ngb/security/NgbAccessDeniedState.vue +27 -0
  185. package/src/ngb/security/NgbEffectiveAccessPanel.vue +60 -0
  186. package/src/ngb/security/NgbPermissionMatrix.vue +160 -0
  187. package/src/ngb/security/NgbRoleEditorPage.vue +319 -0
  188. package/src/ngb/security/NgbRolesPage.vue +125 -0
  189. package/src/ngb/security/NgbUserEditorPage.vue +615 -0
  190. package/src/ngb/security/NgbUsersPage.vue +125 -0
  191. package/src/ngb/security/api.ts +91 -0
  192. package/src/ngb/security/audit.ts +21 -0
  193. package/src/ngb/security/permissions.ts +65 -0
  194. package/src/ngb/security/types.ts +157 -0
  195. package/src/ngb/security/useAccessStore.ts +69 -0
  196. package/src/ngb/site/NgbDashboardAsOfToolbar.vue +100 -0
  197. package/src/ngb/site/NgbDashboardStatusBanner.vue +91 -0
  198. package/src/ngb/site/NgbLogo.vue +44 -0
  199. package/src/ngb/site/NgbPageHeader.vue +49 -0
  200. package/src/ngb/site/NgbSiteShell.vue +286 -0
  201. package/src/ngb/site/NgbSiteSidebar.vue +270 -0
  202. package/src/ngb/site/NgbTopBar.vue +425 -0
  203. package/src/ngb/site/NgbTrendChart.vue +224 -0
  204. package/src/ngb/site/dashboardData.ts +324 -0
  205. package/src/ngb/site/mainMenuStore.ts +49 -0
  206. package/src/ngb/site/types.ts +31 -0
  207. package/src/ngb/site/useDashboardPageState.ts +98 -0
  208. package/src/ngb/site/useTheme.ts +95 -0
  209. package/src/ngb/utils/clone.ts +43 -0
  210. package/src/ngb/utils/dateValues.ts +107 -0
  211. package/src/ngb/utils/errorMessage.ts +14 -0
  212. package/src/ngb/utils/guid.ts +21 -0
  213. package/src/ngb/utils/stableValue.ts +21 -0
  214. package/src/ngb/utils/storage.ts +135 -0
  215. package/src/styles/tailwind.css +165 -0
  216. package/src/vite/publicAssets.ts +104 -0
  217. package/vite-public-assets.js +68 -0
@@ -0,0 +1,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
+ })