@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,261 @@
1
+ import { defineStore } from 'pinia'
2
+ import { ref } from 'vue'
3
+ import type { LookupItem } from '../metadata/types'
4
+ import { isNonEmptyGuid, shortGuid } from '../utils/guid'
5
+ import {
6
+ getConfiguredNgbLookup,
7
+ type LookupSearchOptions,
8
+ type LookupFrameworkConfig,
9
+ type ResolvedDocumentLookupItem,
10
+ } from './config'
11
+
12
+ export type UiLookupItem = LookupItem
13
+
14
+ function normalizeDocumentTypes(documentTypes: string[]): string[] {
15
+ return Array.from(new Set(documentTypes.map((entry) => String(entry ?? '').trim()).filter((entry) => entry.length > 0)))
16
+ }
17
+
18
+ async function loadCoaItems(config: LookupFrameworkConfig, ids: string[]): Promise<LookupItem[]> {
19
+ if (ids.length === 0) return []
20
+ return await config.loadCoaItemsByIds(ids)
21
+ }
22
+
23
+ async function loadResolvedDocumentItems(
24
+ config: LookupFrameworkConfig,
25
+ documentTypes: string[],
26
+ ids: string[],
27
+ ): Promise<ResolvedDocumentLookupItem[]> {
28
+ if (documentTypes.length === 0 || ids.length === 0) return []
29
+ return await config.loadDocumentItemsByIds(documentTypes, ids)
30
+ }
31
+
32
+ async function searchResolvedDocumentItems(
33
+ config: LookupFrameworkConfig,
34
+ documentTypes: string[],
35
+ query: string,
36
+ ): Promise<ResolvedDocumentLookupItem[]> {
37
+ if (documentTypes.length === 0) return []
38
+ return await config.searchDocumentsAcrossTypes(documentTypes, query)
39
+ }
40
+
41
+ export const useLookupStore = defineStore('lookup', () => {
42
+ const catalogLabels = ref<Record<string, Record<string, string>>>({})
43
+ const coaLabels = ref<Record<string, string>>({})
44
+ const documentLabels = ref<Record<string, Record<string, string>>>({})
45
+
46
+ function mergeCatalogItems(catalogType: string, items: readonly LookupItem[]) {
47
+ const existing = catalogLabels.value[catalogType] ?? {}
48
+ const next = { ...existing }
49
+
50
+ for (const item of items) {
51
+ const id = String(item.id ?? '').trim()
52
+ const label = String(item.label ?? '').trim()
53
+ if (!id || !label) continue
54
+ next[id] = label
55
+ }
56
+
57
+ catalogLabels.value = { ...catalogLabels.value, [catalogType]: next }
58
+ }
59
+
60
+ function mergeDocumentItems(documentType: string, items: readonly LookupItem[]) {
61
+ const existing = documentLabels.value[documentType] ?? {}
62
+ const next = { ...existing }
63
+
64
+ for (const item of items) {
65
+ const id = String(item.id ?? '').trim()
66
+ const label = String(item.label ?? '').trim()
67
+ if (!id || !label) continue
68
+ next[id] = label
69
+ }
70
+
71
+ documentLabels.value = { ...documentLabels.value, [documentType]: next }
72
+ }
73
+
74
+ function mergeResolvedDocumentItems(items: readonly ResolvedDocumentLookupItem[]) {
75
+ for (const item of items) {
76
+ mergeDocumentItems(item.documentType, [item])
77
+ }
78
+ }
79
+
80
+ async function ensureCatalogLabels(catalogType: string, ids: string[]) {
81
+ const uniq = Array.from(new Set(ids.filter(isNonEmptyGuid)))
82
+ if (uniq.length === 0) return
83
+
84
+ const existing = catalogLabels.value[catalogType] ?? {}
85
+ const missing = uniq.filter((id) => !existing[id])
86
+ if (missing.length === 0) return
87
+
88
+ const config = getConfiguredNgbLookup()
89
+ const items = await config.loadCatalogItemsByIds(catalogType, missing)
90
+ mergeCatalogItems(catalogType, items)
91
+ }
92
+
93
+ function labelForCatalog(catalogType: string, id: unknown): string {
94
+ if (!isNonEmptyGuid(id)) return String(id ?? '—')
95
+ return catalogLabels.value[catalogType]?.[id] ?? shortGuid(id)
96
+ }
97
+
98
+ async function searchCatalog(
99
+ catalogType: string,
100
+ query: string,
101
+ options?: LookupSearchOptions,
102
+ ): Promise<UiLookupItem[]> {
103
+ const config = getConfiguredNgbLookup()
104
+ const items = await config.searchCatalog(catalogType, query, options)
105
+ mergeCatalogItems(catalogType, items)
106
+ return items.map((item) => ({ ...item }))
107
+ }
108
+
109
+ async function ensureCoaLabels(ids: string[]) {
110
+ const uniq = Array.from(new Set(ids.filter(isNonEmptyGuid)))
111
+ if (uniq.length === 0) return
112
+
113
+ const missing = uniq.filter((id) => !coaLabels.value[id])
114
+ if (missing.length === 0) return
115
+
116
+ const config = getConfiguredNgbLookup()
117
+ const items = await loadCoaItems(config, missing).catch(() => [])
118
+ const next = { ...coaLabels.value }
119
+
120
+ for (const item of items) {
121
+ const id = String(item.id ?? '').trim()
122
+ const label = String(item.label ?? '').trim()
123
+ if (!id || !label) continue
124
+ next[id] = label
125
+ }
126
+
127
+ for (const id of missing) {
128
+ if (!next[id]) {
129
+ next[id] = shortGuid(id)
130
+ }
131
+ }
132
+
133
+ coaLabels.value = next
134
+ }
135
+
136
+ function labelForCoa(id: unknown): string {
137
+ if (!isNonEmptyGuid(id)) return String(id ?? '—')
138
+ return coaLabels.value[id] ?? shortGuid(id)
139
+ }
140
+
141
+ async function searchCoa(query: string): Promise<UiLookupItem[]> {
142
+ const config = getConfiguredNgbLookup()
143
+ const items = await config.searchCoa(query)
144
+ const next = { ...coaLabels.value }
145
+
146
+ for (const item of items) {
147
+ const id = String(item.id ?? '').trim()
148
+ const label = String(item.label ?? '').trim()
149
+ if (!id || !label) continue
150
+ next[id] = label
151
+ }
152
+
153
+ coaLabels.value = next
154
+ return items.map((item) => ({ ...item }))
155
+ }
156
+
157
+ async function ensureAnyDocumentLabels(documentTypes: string[], ids: string[]) {
158
+ const types = normalizeDocumentTypes(documentTypes)
159
+ const uniq = Array.from(new Set(ids.filter(isNonEmptyGuid)))
160
+ if (types.length === 0 || uniq.length === 0) return
161
+
162
+ const missing = uniq.filter((id) => !types.some((documentType) => !!documentLabels.value[documentType]?.[id]))
163
+ if (missing.length === 0) return
164
+
165
+ const config = getConfiguredNgbLookup()
166
+ const items = await loadResolvedDocumentItems(config, types, missing).catch(() => [])
167
+ mergeResolvedDocumentItems(items)
168
+
169
+ const resolvedIds = new Set(items.map((item) => item.id))
170
+ const fallbackType = types[0]!
171
+
172
+ for (const id of missing) {
173
+ if (!resolvedIds.has(id)) {
174
+ mergeDocumentItems(fallbackType, [{ id, label: shortGuid(id) }])
175
+ }
176
+ }
177
+ }
178
+
179
+ async function ensureDocumentLabels(documentType: string, ids: string[]) {
180
+ const uniq = Array.from(new Set(ids.filter(isNonEmptyGuid)))
181
+ if (uniq.length === 0) return
182
+
183
+ const existing = documentLabels.value[documentType] ?? {}
184
+ const missing = uniq.filter((id) => !existing[id])
185
+ if (missing.length === 0) return
186
+
187
+ const config = getConfiguredNgbLookup()
188
+ const items = await loadResolvedDocumentItems(config, [documentType], missing).catch(() => [])
189
+ mergeResolvedDocumentItems(items)
190
+
191
+ const resolvedIds = new Set(items.map((item) => item.id))
192
+ for (const id of missing) {
193
+ if (!resolvedIds.has(id)) {
194
+ mergeDocumentItems(documentType, [{ id, label: shortGuid(id) }])
195
+ }
196
+ }
197
+ }
198
+
199
+ function labelForAnyDocument(documentTypes: string[], id: unknown): string {
200
+ if (!isNonEmptyGuid(id)) return String(id ?? '—')
201
+
202
+ for (const documentType of documentTypes) {
203
+ const label = documentLabels.value[documentType]?.[id]
204
+ if (label) return label
205
+ }
206
+
207
+ return shortGuid(id)
208
+ }
209
+
210
+ function labelForDocument(documentType: string, id: unknown): string {
211
+ if (!isNonEmptyGuid(id)) return String(id ?? '—')
212
+ return documentLabels.value[documentType]?.[id] ?? shortGuid(id)
213
+ }
214
+
215
+ async function searchDocuments(documentTypes: string[], query: string): Promise<UiLookupItem[]> {
216
+ const types = normalizeDocumentTypes(documentTypes)
217
+ if (types.length === 0) return []
218
+
219
+ const config = getConfiguredNgbLookup()
220
+ const items = await searchResolvedDocumentItems(config, types, query)
221
+ mergeResolvedDocumentItems(items)
222
+
223
+ const seen = new Set<string>()
224
+ const merged: UiLookupItem[] = []
225
+
226
+ for (const item of items) {
227
+ const id = String(item.id ?? '').trim()
228
+ if (!id || seen.has(id)) continue
229
+ seen.add(id)
230
+ merged.push({
231
+ id: item.id,
232
+ label: item.label,
233
+ meta: item.meta,
234
+ })
235
+ }
236
+
237
+ return merged
238
+ }
239
+
240
+ async function searchDocument(documentType: string, query: string): Promise<UiLookupItem[]> {
241
+ const config = getConfiguredNgbLookup()
242
+ const items = await config.searchDocument(documentType, query)
243
+ mergeDocumentItems(documentType, items)
244
+ return items.map((item) => ({ ...item }))
245
+ }
246
+
247
+ return {
248
+ ensureCatalogLabels,
249
+ searchCatalog,
250
+ labelForCatalog,
251
+ ensureCoaLabels,
252
+ searchCoa,
253
+ labelForCoa,
254
+ ensureAnyDocumentLabels,
255
+ ensureDocumentLabels,
256
+ searchDocuments,
257
+ searchDocument,
258
+ labelForAnyDocument,
259
+ labelForDocument,
260
+ }
261
+ })
@@ -0,0 +1,78 @@
1
+ <script setup lang="ts">
2
+ import NgbDrawer from '../components/NgbDrawer.vue'
3
+ import NgbFormLayout from '../components/forms/NgbFormLayout.vue'
4
+ import NgbFormRow from '../components/forms/NgbFormRow.vue'
5
+ import NgbIcon from '../primitives/NgbIcon.vue'
6
+ import type { FilterFieldState, FilterLookupItem, ListFilterField } from './types'
7
+ import NgbFilterFieldControl from './NgbFilterFieldControl.vue'
8
+
9
+ const props = defineProps<{
10
+ open: boolean
11
+ filters: ListFilterField[]
12
+ values: Record<string, FilterFieldState>
13
+ lookupItemsByKey: Record<string, FilterLookupItem[]>
14
+ canUndo: boolean
15
+ disabled?: boolean
16
+ }>()
17
+
18
+ const emit = defineEmits<{
19
+ (e: 'update:open', value: boolean): void
20
+ (e: 'lookup-query', payload: { key: string; query: string }): void
21
+ (e: 'update:items', payload: { key: string; items: FilterLookupItem[] }): void
22
+ (e: 'update:value', payload: { key: string; value: string }): void
23
+ (e: 'undo'): void
24
+ }>()
25
+
26
+ function stateFor(key: string): FilterFieldState {
27
+ return props.values[key] ?? { raw: '', items: [] }
28
+ }
29
+ </script>
30
+
31
+ <template>
32
+ <NgbDrawer
33
+ :open="open"
34
+ title="Filter"
35
+ subtitle="Define criteria to refine results"
36
+ @update:open="emit('update:open', $event)"
37
+ >
38
+ <template #actions>
39
+ <button
40
+ type="button"
41
+ class="ngb-iconbtn"
42
+ title="Undo"
43
+ :disabled="disabled || !canUndo"
44
+ @click="emit('undo')"
45
+ >
46
+ <NgbIcon name="undo" />
47
+ </button>
48
+ </template>
49
+
50
+ <NgbFormLayout v-if="filters.length > 0">
51
+ <NgbFormRow
52
+ v-for="field in filters"
53
+ :key="field.key"
54
+ :label="field.label"
55
+ :hint="field.description ?? undefined"
56
+ dense
57
+ >
58
+ <NgbFilterFieldControl
59
+ :field="field"
60
+ :state="stateFor(field.key)"
61
+ :lookup-items="lookupItemsByKey[field.key] ?? []"
62
+ :disabled="disabled"
63
+ select-empty-label="Any"
64
+ @lookup-query="emit('lookup-query', { key: field.key, query: $event })"
65
+ @update:items="emit('update:items', { key: field.key, items: $event as FilterLookupItem[] })"
66
+ @update:raw="emit('update:value', { key: field.key, value: $event })"
67
+ />
68
+ </NgbFormRow>
69
+ </NgbFormLayout>
70
+
71
+ <div
72
+ v-else
73
+ class="rounded-[var(--ngb-radius)] border border-ngb-border bg-ngb-bg px-3 py-2 text-sm text-ngb-muted"
74
+ >
75
+ No filters available.
76
+ </div>
77
+ </NgbDrawer>
78
+ </template>
@@ -0,0 +1,53 @@
1
+ <script setup lang="ts">
2
+ import NgbMonthPicker from '../primitives/NgbMonthPicker.vue'
3
+ import { normalizeMonthValue } from '../utils/dateValues'
4
+
5
+ const props = defineProps<{
6
+ fromMonth: string
7
+ toMonth: string
8
+ disabled?: boolean
9
+ }>()
10
+
11
+ const emit = defineEmits<{
12
+ (e: 'update:fromMonth', value: string): void
13
+ (e: 'update:toMonth', value: string): void
14
+ }>()
15
+
16
+ function updateFrom(value: string | null) {
17
+ emit('update:fromMonth', value ?? '')
18
+ }
19
+
20
+ function updateTo(value: string | null) {
21
+ emit('update:toMonth', value ?? '')
22
+ }
23
+ </script>
24
+
25
+ <template>
26
+ <div class="relative inline-flex h-[26px] items-stretch rounded-[var(--ngb-radius)] border border-ngb-border bg-ngb-card text-xs shadow-card">
27
+ <div class="w-[9rem]">
28
+ <NgbMonthPicker
29
+ :model-value="normalizeMonthValue(props.fromMonth)"
30
+ placeholder="Start month"
31
+ grouped
32
+ display-format="short"
33
+ :disabled="!!props.disabled"
34
+ @update:model-value="updateFrom"
35
+ />
36
+ </div>
37
+
38
+ <div class="w-px self-stretch bg-ngb-border" />
39
+ <div class="flex items-center px-2 text-[11px] text-ngb-muted select-none">-</div>
40
+ <div class="w-px self-stretch bg-ngb-border" />
41
+
42
+ <div class="w-[9rem]">
43
+ <NgbMonthPicker
44
+ :model-value="normalizeMonthValue(props.toMonth)"
45
+ placeholder="End month"
46
+ grouped
47
+ display-format="short"
48
+ :disabled="!!props.disabled"
49
+ @update:model-value="updateTo"
50
+ />
51
+ </div>
52
+ </div>
53
+ </template>
@@ -0,0 +1,84 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref } from 'vue'
3
+ import NgbFormLayout from '../components/forms/NgbFormLayout.vue'
4
+ import NgbFormSection from '../components/forms/NgbFormSection.vue'
5
+ import { defaultFindDisplayField } from './entityForm'
6
+ import { resolveNgbMetadataFormBehavior } from './config'
7
+ import { useValidationFocus } from './useValidationFocus'
8
+ import type { EntityFormModel, FieldMetadata, FormMetadata, MetadataFormBehavior } from './types'
9
+ import NgbEntityFormFieldsBlock from './NgbEntityFormFieldsBlock.vue'
10
+
11
+ const props = defineProps<{
12
+ form: FormMetadata
13
+ model: EntityFormModel
14
+ entityTypeCode: string
15
+ status?: number
16
+ forceReadonly?: boolean
17
+ presentation?: 'sections' | 'flat'
18
+ errors?: Record<string, string | string[] | null> | null
19
+ behavior?: MetadataFormBehavior
20
+ }>()
21
+
22
+ const rootRef = ref<HTMLElement | null>(null)
23
+ const behavior = computed(() => resolveNgbMetadataFormBehavior(props.behavior))
24
+ const displayField = computed<FieldMetadata | null>(() =>
25
+ behavior.value.findDisplayField?.(props.form) ?? defaultFindDisplayField(props.form),
26
+ )
27
+ const presentation = computed(() => props.presentation ?? 'sections')
28
+ const sections = computed(() => props.form?.sections ?? [])
29
+ const flatRows = computed(() => sections.value.flatMap((section) => section.rows ?? []))
30
+ const validationFocus = useValidationFocus(rootRef, { attribute: 'data-validation-key' })
31
+
32
+ function scrollToField(key: string): boolean {
33
+ return validationFocus.scrollTo(key)
34
+ }
35
+
36
+ function focusField(key: string): boolean {
37
+ return validationFocus.focus(key)
38
+ }
39
+
40
+ function focusFirstError(keys: string[]): boolean {
41
+ for (const key of keys) {
42
+ if (focusField(key)) return true
43
+ }
44
+ return false
45
+ }
46
+
47
+ defineExpose({
48
+ scrollToField,
49
+ focusField,
50
+ focusFirstError,
51
+ })
52
+ </script>
53
+
54
+ <template>
55
+ <div ref="rootRef">
56
+ <NgbFormLayout v-if="presentation === 'sections'">
57
+ <NgbFormSection v-for="(section, sectionIndex) in sections" :key="sectionIndex" :title="section.title">
58
+ <NgbEntityFormFieldsBlock
59
+ :rows="section.rows ?? []"
60
+ :display-field="sectionIndex === 0 ? displayField : null"
61
+ :model="model"
62
+ :entity-type-code="entityTypeCode"
63
+ :status="status"
64
+ :force-readonly="forceReadonly"
65
+ :errors="errors"
66
+ :behavior="behavior.value"
67
+ />
68
+ </NgbFormSection>
69
+ </NgbFormLayout>
70
+
71
+ <div v-else class="space-y-3">
72
+ <NgbEntityFormFieldsBlock
73
+ :rows="flatRows"
74
+ :display-field="displayField"
75
+ :model="model"
76
+ :entity-type-code="entityTypeCode"
77
+ :status="status"
78
+ :force-readonly="forceReadonly"
79
+ :errors="errors"
80
+ :behavior="behavior.value"
81
+ />
82
+ </div>
83
+ </div>
84
+ </template>
@@ -0,0 +1,110 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import NgbFormRow from '../components/forms/NgbFormRow.vue'
4
+ import { defaultIsFieldHidden, defaultIsFieldReadonly } from './entityForm'
5
+ import { resolveNgbMetadataFormBehavior } from './config'
6
+ import type { EntityFormModel, FieldMetadata, MetadataFormBehavior } from './types'
7
+ import NgbMetadataFieldRenderer from './NgbMetadataFieldRenderer.vue'
8
+
9
+ type FormRowDescriptor = {
10
+ fields?: FieldMetadata[] | null
11
+ }
12
+
13
+ const props = defineProps<{
14
+ rows: FormRowDescriptor[]
15
+ model: EntityFormModel
16
+ entityTypeCode: string
17
+ status?: number
18
+ forceReadonly?: boolean
19
+ displayField?: FieldMetadata | null
20
+ errors?: Record<string, string | string[] | null> | null
21
+ behavior?: MetadataFormBehavior
22
+ }>()
23
+
24
+ const behavior = computed(() => resolveNgbMetadataFormBehavior(props.behavior))
25
+ const isDocumentEntity = computed(() => props.status !== undefined)
26
+
27
+ function isReadonlyField(field: FieldMetadata): boolean {
28
+ const args = {
29
+ entityTypeCode: props.entityTypeCode,
30
+ model: props.model,
31
+ field,
32
+ status: props.status,
33
+ forceReadonly: props.forceReadonly,
34
+ }
35
+
36
+ return behavior.value.isFieldReadonly?.(args) ?? defaultIsFieldReadonly(args)
37
+ }
38
+
39
+ function isHiddenField(field: FieldMetadata): boolean {
40
+ const args = {
41
+ entityTypeCode: props.entityTypeCode,
42
+ model: props.model,
43
+ field,
44
+ isDocumentEntity: isDocumentEntity.value,
45
+ }
46
+
47
+ return behavior.value.isFieldHidden?.(args) ?? defaultIsFieldHidden(args)
48
+ }
49
+
50
+ function isDisplayField(field: FieldMetadata): boolean {
51
+ return field.key === 'display'
52
+ }
53
+
54
+ function firstFieldError(key: string): string | undefined {
55
+ const value = props.errors?.[key]
56
+ if (Array.isArray(value)) return value.find((entry) => typeof entry === 'string' && entry.trim().length > 0)
57
+ if (typeof value === 'string' && value.trim().length > 0) return value
58
+ return undefined
59
+ }
60
+ </script>
61
+
62
+ <template>
63
+ <div class="space-y-3">
64
+ <div
65
+ v-if="displayField && !isHiddenField(displayField)"
66
+ :data-validation-key="displayField.key"
67
+ >
68
+ <NgbFormRow
69
+ :label="displayField.label"
70
+ :hint="displayField.helpText || (displayField.isRequired ? 'Required' : undefined)"
71
+ :error="firstFieldError(displayField.key)"
72
+ >
73
+ <NgbMetadataFieldRenderer
74
+ :field="displayField"
75
+ :model="model"
76
+ :entity-type-code="entityTypeCode"
77
+ :model-value="model[displayField.key]"
78
+ :readonly="isReadonlyField(displayField)"
79
+ :behavior="behavior.value"
80
+ @update:modelValue="model[displayField.key] = $event"
81
+ />
82
+ </NgbFormRow>
83
+ </div>
84
+
85
+ <template v-for="(row, rowIndex) in rows" :key="rowIndex">
86
+ <template v-for="(field, fieldIndex) in row.fields ?? []" :key="`${field.key}:${fieldIndex}`">
87
+ <div
88
+ v-if="!isDisplayField(field) && !isHiddenField(field)"
89
+ :data-validation-key="field.key"
90
+ >
91
+ <NgbFormRow
92
+ :label="field.label"
93
+ :hint="field.helpText || (field.isRequired ? 'Required' : undefined)"
94
+ :error="firstFieldError(field.key)"
95
+ >
96
+ <NgbMetadataFieldRenderer
97
+ :field="field"
98
+ :model="model"
99
+ :entity-type-code="entityTypeCode"
100
+ :model-value="model[field.key]"
101
+ :readonly="isReadonlyField(field)"
102
+ :behavior="behavior.value"
103
+ @update:modelValue="model[field.key] = $event"
104
+ />
105
+ </NgbFormRow>
106
+ </div>
107
+ </template>
108
+ </template>
109
+ </div>
110
+ </template>
@@ -0,0 +1,80 @@
1
+ <script setup lang="ts">
2
+ import { computed, useSlots } from 'vue'
3
+ import NgbIcon from '../primitives/NgbIcon.vue'
4
+ import NgbPageHeader from '../site/NgbPageHeader.vue'
5
+
6
+ const props = withDefaults(defineProps<{
7
+ title: string
8
+ canBack?: boolean
9
+ itemsCount?: number | null
10
+ total?: number | null
11
+ loading?: boolean
12
+ disableCreate?: boolean
13
+ showFilter?: boolean
14
+ disableFilter?: boolean
15
+ filterActive?: boolean
16
+ disablePrev?: boolean
17
+ disableNext?: boolean
18
+ }>(), {
19
+ showFilter: true,
20
+ })
21
+
22
+ const emit = defineEmits<{
23
+ (e: 'back'): void
24
+ (e: 'refresh'): void
25
+ (e: 'create'): void
26
+ (e: 'filter'): void
27
+ (e: 'prev'): void
28
+ (e: 'next'): void
29
+ }>()
30
+
31
+ const slots = useSlots()
32
+ const hasFilters = computed(() => !!slots.filters)
33
+ const showCount = computed(() => props.itemsCount !== null && props.itemsCount !== undefined)
34
+ const subtitle = computed(() => {
35
+ if (!showCount.value) return undefined
36
+ const left = String(props.itemsCount)
37
+ const right = props.total != null ? ` / ${props.total}` : ''
38
+ return `${left}${right}`
39
+ })
40
+ </script>
41
+
42
+ <template>
43
+ <NgbPageHeader :title="title" :can-back="!!canBack" @back="emit('back')">
44
+ <template #secondary>
45
+ <div v-if="subtitle" class="text-xs text-ngb-muted truncate">{{ subtitle }}</div>
46
+ </template>
47
+ <template #actions>
48
+ <div class="flex items-center gap-1.5">
49
+ <slot name="filters" />
50
+
51
+ <div v-if="hasFilters" class="w-px h-5 bg-ngb-border mx-1" />
52
+
53
+ <button class="ngb-iconbtn" title="Create" :disabled="!!disableCreate" @click="emit('create')">
54
+ <NgbIcon name="plus" />
55
+ </button>
56
+
57
+ <button
58
+ v-if="props.showFilter !== false"
59
+ :class="['ngb-iconbtn', props.filterActive ? 'bg-ngb-bg text-ngb-text' : '']"
60
+ title="Filter"
61
+ :disabled="!!disableFilter"
62
+ :aria-pressed="props.filterActive ? 'true' : 'false'"
63
+ @click="emit('filter')"
64
+ >
65
+ <NgbIcon name="filter" />
66
+ </button>
67
+
68
+ <button class="ngb-iconbtn" title="Refresh" :disabled="!!loading" @click="emit('refresh')">
69
+ <NgbIcon name="refresh" />
70
+ </button>
71
+ <button class="ngb-iconbtn" title="Previous" :disabled="!!disablePrev" @click="emit('prev')">
72
+ <NgbIcon name="arrow-left" />
73
+ </button>
74
+ <button class="ngb-iconbtn" title="Next" :disabled="!!disableNext" @click="emit('next')">
75
+ <NgbIcon name="arrow-right" />
76
+ </button>
77
+ </div>
78
+ </template>
79
+ </NgbPageHeader>
80
+ </template>