@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,353 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref, watch } from 'vue'
3
+ import { useRoute, useRouter } from 'vue-router'
4
+
5
+ import NgbBadge from '../primitives/NgbBadge.vue'
6
+ import NgbEditorDiscardDialog from '../editor/NgbEditorDiscardDialog.vue'
7
+ import { buildDocumentFullPageUrl, shouldOpenDocumentInFullPageByDefault } from '../editor/documentNavigation'
8
+ import { isDocumentEditorDrawerQueryKey, useDocumentEditorDrawerState } from '../editor/useDocumentEditorDrawerState'
9
+ import { useEditorDrawerState } from '../editor/useEditorDrawerState'
10
+ import { useEntityEditorCommitHandlers } from '../editor/useEntityEditorCommitHandlers'
11
+ import type { EntityEditorHandle } from '../editor/types'
12
+ import { useLookupStore } from '../lookup/store'
13
+ import NgbDocumentListFiltersDrawer from './NgbDocumentListFiltersDrawer.vue'
14
+ import NgbDocumentPeriodFilter from './NgbDocumentPeriodFilter.vue'
15
+ import NgbRecycleBinFilter from './NgbRecycleBinFilter.vue'
16
+ import NgbRegisterPageLayout from './NgbRegisterPageLayout.vue'
17
+ import type { MetadataDocumentListPageProps } from './routePages'
18
+ import {
19
+ monthValueToDateOnlyEnd,
20
+ monthValueToDateOnlyStart,
21
+ useMonthPagedListQuery,
22
+ } from './monthPagedListQuery'
23
+ import { useMetadataStore } from './store'
24
+ import { useMetadataListFilters } from './useMetadataListFilters'
25
+ import {
26
+ useMetadataPageReloadKey,
27
+ useMetadataRegisterPageData,
28
+ } from './useMetadataRegisterPageData'
29
+ import { currentRouteBackTarget, navigateBack, withBackTarget } from '../router/backNavigation'
30
+ import { normalizeSingleQueryValue } from '../router/queryParams'
31
+
32
+ const props = withDefaults(defineProps<MetadataDocumentListPageProps>(), {
33
+ resolveLookupHint: null,
34
+ resolveTitle: null,
35
+ resolveStorageKey: null,
36
+ resolveWarning: null,
37
+ isCreateDisabled: null,
38
+ handleCreateOverride: null,
39
+ backTarget: '/',
40
+ })
41
+
42
+ const route = useRoute()
43
+ const router = useRouter()
44
+ const metaStore = useMetadataStore()
45
+ const lookupStore = useLookupStore()
46
+
47
+ const resolvedBackTarget = computed(() => String(props.backTarget ?? '').trim() || '/')
48
+ const canBack = computed(() => route.path !== resolvedBackTarget.value)
49
+ const documentType = computed(() => String(route.params.documentType ?? ''))
50
+
51
+ const {
52
+ offset,
53
+ limit,
54
+ trashMode,
55
+ periodFromMonth,
56
+ periodToMonth,
57
+ nextPage,
58
+ prevPage,
59
+ } = useMonthPagedListQuery({
60
+ route,
61
+ router,
62
+ })
63
+
64
+ const filterDrawerOpen = ref(false)
65
+
66
+ const routeReloadKey = useMetadataPageReloadKey({
67
+ route,
68
+ entityTypeCode: documentType,
69
+ ignoreQueryKey: isDocumentEditorDrawerQueryKey,
70
+ })
71
+
72
+ const {
73
+ loading,
74
+ error,
75
+ metadata,
76
+ page,
77
+ listFilters,
78
+ hasListFilters,
79
+ columns,
80
+ rows,
81
+ load,
82
+ } = useMetadataRegisterPageData({
83
+ route,
84
+ entityTypeCode: documentType,
85
+ reloadKey: routeReloadKey,
86
+ loadMetadata: (entityTypeCode) => metaStore.ensureDocumentType(entityTypeCode),
87
+ loadPage: async ({ entityTypeCode, metadata }) => {
88
+ const listFilterParams = Object.fromEntries(
89
+ (metadata.list?.filters ?? [])
90
+ .map((field) => [field.key, normalizeSingleQueryValue(route.query[field.key])] as const)
91
+ .filter((entry) => entry[1].length > 0),
92
+ )
93
+
94
+ return await props.loadPage({
95
+ documentType: entityTypeCode,
96
+ metadata,
97
+ offset: offset.value,
98
+ limit: limit.value,
99
+ search: String(route.query.search ?? '') || undefined,
100
+ trashMode: trashMode.value,
101
+ periodFrom: monthValueToDateOnlyStart(periodFromMonth.value) ?? null,
102
+ periodTo: monthValueToDateOnlyEnd(periodToMonth.value) ?? null,
103
+ listFilters: listFilterParams,
104
+ })
105
+ },
106
+ lookupStore,
107
+ resolveLookupHint: props.resolveLookupHint
108
+ ? ({ entityTypeCode, fieldKey, lookup }) => props.resolveLookupHint?.({ entityTypeCode, fieldKey, lookup }) ?? null
109
+ : undefined,
110
+ })
111
+
112
+ const preferFullPage = computed(() => shouldOpenDocumentInFullPageByDefault(metadata.value))
113
+ const resolvedTitle = computed(() => {
114
+ if (!metadata.value) return `Documents: ${documentType.value}`
115
+ return props.resolveTitle?.(documentType.value, metadata.value.displayName) ?? metadata.value.displayName
116
+ })
117
+ const resolvedStorageKey = computed(() =>
118
+ String(props.resolveStorageKey?.(documentType.value) ?? '').trim() || `ngb:document:${documentType.value}`,
119
+ )
120
+ const resolvedWarning = computed(() => props.resolveWarning?.(documentType.value, metadata.value) ?? null)
121
+ const createDisabled = computed(() => metadata.value?.capabilities?.canCreate === false
122
+ || (props.isCreateDisabled?.(documentType.value, metadata.value) ?? false),
123
+ )
124
+
125
+ const {
126
+ filterDraft,
127
+ lookupItemsByFilterKey,
128
+ activeFilterBadges,
129
+ hasActiveFilters,
130
+ canUndoFilters,
131
+ handleLookupQuery,
132
+ handleItemsUpdate,
133
+ handleValueUpdate,
134
+ undo,
135
+ } = useMetadataListFilters({
136
+ route,
137
+ router,
138
+ entityTypeCode: documentType,
139
+ filters: listFilters,
140
+ lookupStore,
141
+ resolveLookupHint: ({ entityTypeCode, field }) =>
142
+ props.resolveLookupHint?.({
143
+ entityTypeCode,
144
+ fieldKey: field.key,
145
+ lookup: field.lookup,
146
+ }) ?? null,
147
+ })
148
+
149
+ const {
150
+ isPanelOpen,
151
+ currentEditorId,
152
+ initialFields,
153
+ initialParts,
154
+ expandTo,
155
+ openCreateDrawer,
156
+ openEditDrawer,
157
+ reopenCreatedDocument,
158
+ closeDrawer,
159
+ } = useDocumentEditorDrawerState({
160
+ route,
161
+ router,
162
+ documentType,
163
+ })
164
+
165
+ const editorRef = ref<EntityEditorHandle | null>(null)
166
+ const {
167
+ editorFlags,
168
+ discardOpen,
169
+ requestDiscard,
170
+ handleEditorFlags,
171
+ discardConfirm,
172
+ discardCancel,
173
+ beforeCloseDrawer,
174
+ } = useEditorDrawerState()
175
+
176
+ async function requestDrawerReplacement(): Promise<boolean> {
177
+ if (!isPanelOpen.value || !editorFlags.value.isDirty) return true
178
+ return await requestDiscard()
179
+ }
180
+
181
+ async function openCreateDefault() {
182
+ if (preferFullPage.value) {
183
+ await router.push(
184
+ withBackTarget(
185
+ buildDocumentFullPageUrl(documentType.value),
186
+ currentRouteBackTarget(route),
187
+ ),
188
+ )
189
+ return
190
+ }
191
+
192
+ if (isPanelOpen.value && !currentEditorId.value) return
193
+ if (!await requestDrawerReplacement()) return
194
+ openCreateDrawer()
195
+ }
196
+
197
+ async function openEdit(id: string) {
198
+ if (preferFullPage.value) {
199
+ void router.push(
200
+ withBackTarget(
201
+ buildDocumentFullPageUrl(documentType.value, id),
202
+ currentRouteBackTarget(route),
203
+ ),
204
+ )
205
+ return
206
+ }
207
+
208
+ if (currentEditorId.value === id) return
209
+ if (!await requestDrawerReplacement()) return
210
+ openEditDrawer(id)
211
+ }
212
+
213
+ const {
214
+ handleCreated,
215
+ handleSaved,
216
+ handleChanged,
217
+ handleDeleted,
218
+ } = useEntityEditorCommitHandlers({
219
+ reload: load,
220
+ closeDrawer,
221
+ closeOnCreated: false,
222
+ closeOnSaved: false,
223
+ closeOnChanged: ({ reason }) => reason === 'markForDeletion' || reason === 'unmarkForDeletion',
224
+ onCreated: async ({ id }) => {
225
+ reopenCreatedDocument(id)
226
+ },
227
+ })
228
+
229
+ watch(
230
+ () => documentType.value,
231
+ () => {
232
+ filterDrawerOpen.value = false
233
+ },
234
+ )
235
+
236
+ async function createNew() {
237
+ const handled = await props.handleCreateOverride?.({
238
+ documentType: documentType.value,
239
+ metadata: metadata.value,
240
+ preferFullPage: preferFullPage.value,
241
+ route,
242
+ router,
243
+ openCreateDrawer,
244
+ openFullPage: async () => {
245
+ await router.push(
246
+ withBackTarget(
247
+ buildDocumentFullPageUrl(documentType.value),
248
+ currentRouteBackTarget(route),
249
+ ),
250
+ )
251
+ },
252
+ })
253
+
254
+ if (handled) return
255
+ await openCreateDefault()
256
+ }
257
+ </script>
258
+
259
+ <template>
260
+ <NgbRegisterPageLayout
261
+ :title="resolvedTitle"
262
+ :can-back="canBack"
263
+ :items-count="page ? page.items.length : null"
264
+ :total="page?.total ?? null"
265
+ :loading="loading"
266
+ :error="error"
267
+ :warning="resolvedWarning"
268
+ :disable-create="createDisabled"
269
+ :disable-filter="!hasListFilters"
270
+ :filter-active="hasActiveFilters"
271
+ :disable-prev="offset === 0"
272
+ :disable-next="(page?.items?.length ?? 0) < limit"
273
+ :columns="columns"
274
+ :rows="rows"
275
+ :storage-key="resolvedStorageKey"
276
+ :drawer-open="isPanelOpen"
277
+ drawer-title=""
278
+ drawer-hide-header
279
+ drawer-flush-body
280
+ :before-close="beforeCloseDrawer"
281
+ @back="navigateBack(router, route, resolvedBackTarget)"
282
+ @refresh="load"
283
+ @create="() => void createNew()"
284
+ @filter="filterDrawerOpen = true"
285
+ @prev="prevPage"
286
+ @next="nextPage"
287
+ @rowActivate="openEdit"
288
+ @update:drawerOpen="(value) => (!value ? closeDrawer() : null)"
289
+ >
290
+ <template #filters>
291
+ <NgbDocumentPeriodFilter
292
+ :from-month="periodFromMonth"
293
+ :to-month="periodToMonth"
294
+ :disabled="loading"
295
+ @update:from-month="periodFromMonth = $event"
296
+ @update:to-month="periodToMonth = $event"
297
+ />
298
+ <NgbRecycleBinFilter v-model="trashMode" :disabled="loading" />
299
+ </template>
300
+
301
+ <template #beforeGrid>
302
+ <div v-if="activeFilterBadges.length > 0" class="mb-4 flex flex-wrap items-center gap-2">
303
+ <NgbBadge v-for="badge in activeFilterBadges" :key="badge.key" tone="neutral">
304
+ {{ badge.text }}
305
+ </NgbBadge>
306
+ </div>
307
+ </template>
308
+
309
+ <template #filterDrawer>
310
+ <NgbDocumentListFiltersDrawer
311
+ :open="filterDrawerOpen"
312
+ :filters="listFilters"
313
+ :values="filterDraft"
314
+ :lookup-items-by-key="lookupItemsByFilterKey"
315
+ :can-undo="canUndoFilters"
316
+ :disabled="loading"
317
+ @update:open="filterDrawerOpen = $event"
318
+ @lookup-query="handleLookupQuery"
319
+ @update:items="handleItemsUpdate"
320
+ @update:value="handleValueUpdate"
321
+ @undo="undo"
322
+ />
323
+ </template>
324
+
325
+ <template #drawerContent>
326
+ <component
327
+ :is="editorComponent"
328
+ ref="editorRef"
329
+ v-if="isPanelOpen"
330
+ kind="document"
331
+ :type-code="documentType"
332
+ :id="currentEditorId"
333
+ mode="drawer"
334
+ :initial-fields="initialFields"
335
+ :initial-parts="initialParts"
336
+ :expand-to="expandTo"
337
+ :navigate-on-create="false"
338
+ @flags="handleEditorFlags"
339
+ @created="handleCreated"
340
+ @saved="handleSaved"
341
+ @changed="handleChanged"
342
+ @deleted="handleDeleted"
343
+ @close="closeDrawer"
344
+ />
345
+ </template>
346
+ </NgbRegisterPageLayout>
347
+
348
+ <NgbEditorDiscardDialog
349
+ :open="discardOpen"
350
+ @cancel="discardCancel"
351
+ @confirm="discardConfirm"
352
+ />
353
+ </template>
@@ -0,0 +1,124 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import NgbCheckbox from '../primitives/NgbCheckbox.vue'
4
+ import NgbDatePicker from '../primitives/NgbDatePicker.vue'
5
+ import NgbInput from '../primitives/NgbInput.vue'
6
+ import NgbSelect from '../primitives/NgbSelect.vue'
7
+ import { dataTypeKind } from './dataTypes'
8
+ import { isReferenceValue } from './entityModel'
9
+ import { resolveFieldRendererState } from './fieldRendererState'
10
+ import type { EntityFormModel, FieldMetadata, MetadataFormBehavior } from './types'
11
+ import NgbMetadataLookupControl from './NgbMetadataLookupControl.vue'
12
+
13
+ const props = defineProps<{
14
+ field: FieldMetadata
15
+ model: EntityFormModel
16
+ modelValue: unknown
17
+ entityTypeCode: string
18
+ readonly?: boolean
19
+ disabled?: boolean
20
+ behavior?: MetadataFormBehavior
21
+ }>()
22
+
23
+ const emit = defineEmits<{
24
+ (e: 'update:modelValue', value: unknown): void
25
+ }>()
26
+
27
+ const rendererState = computed(() => resolveFieldRendererState({
28
+ entityTypeCode: props.entityTypeCode,
29
+ model: props.model,
30
+ field: props.field,
31
+ modelValue: props.modelValue,
32
+ behavior: props.behavior,
33
+ }))
34
+
35
+ function update(value: unknown) {
36
+ emit('update:modelValue', value)
37
+ }
38
+
39
+ const hasRef = computed(() => isReferenceValue(props.modelValue))
40
+ const refDisplay = computed(() => (isReferenceValue(props.modelValue) ? props.modelValue.display : null))
41
+
42
+ function normalizeSelectValue(value: unknown): unknown {
43
+ if (dataTypeKind(props.field.dataType) !== 'Int32') return value
44
+ if (typeof value === 'number') return Math.trunc(value)
45
+ if (typeof value !== 'string') return value
46
+
47
+ const parsed = Number.parseInt(value, 10)
48
+ return Number.isFinite(parsed) ? parsed : value
49
+ }
50
+
51
+ const selectOptions = computed(() =>
52
+ (rendererState.value.fieldOptions ?? []).map((option) => ({
53
+ ...option,
54
+ value: normalizeSelectValue(option.value),
55
+ })),
56
+ )
57
+
58
+ const selectValue = computed(() => normalizeSelectValue(props.modelValue))
59
+ </script>
60
+
61
+ <template>
62
+ <div class="w-full">
63
+ <NgbSelect
64
+ v-if="rendererState.mode === 'select' && rendererState.fieldOptions"
65
+ :model-value="selectValue ?? null"
66
+ :options="selectOptions"
67
+ :disabled="disabled || readonly"
68
+ @update:modelValue="update"
69
+ />
70
+
71
+ <NgbMetadataLookupControl
72
+ v-else-if="rendererState.mode === 'lookup' && rendererState.hint"
73
+ :hint="rendererState.hint"
74
+ :model-value="modelValue"
75
+ :disabled="disabled"
76
+ :readonly="readonly"
77
+ :behavior="behavior"
78
+ @update:modelValue="update"
79
+ />
80
+
81
+ <NgbCheckbox
82
+ v-else-if="rendererState.mode === 'checkbox'"
83
+ :model-value="!!modelValue"
84
+ :disabled="disabled || readonly"
85
+ @update:modelValue="update"
86
+ />
87
+
88
+ <textarea
89
+ v-else-if="rendererState.mode === 'textarea'"
90
+ class="min-h-[96px] w-full rounded-[var(--ngb-radius)] border border-ngb-border bg-ngb-card px-3 py-2 text-sm text-ngb-text placeholder:text-ngb-muted/70 ngb-focus"
91
+ :value="modelValue ?? ''"
92
+ :readonly="readonly"
93
+ :disabled="disabled"
94
+ @input="update(($event.target as HTMLTextAreaElement).value)"
95
+ />
96
+
97
+ <NgbInput
98
+ v-else-if="rendererState.mode === 'reference-display' && hasRef"
99
+ type="text"
100
+ :model-value="refDisplay ?? ''"
101
+ :title="refDisplay ?? ''"
102
+ :disabled="true"
103
+ :readonly="true"
104
+ />
105
+
106
+ <NgbDatePicker
107
+ v-else-if="rendererState.mode === 'date'"
108
+ :model-value="(modelValue as string | null | undefined) ?? null"
109
+ :disabled="disabled || readonly"
110
+ @update:modelValue="update"
111
+ />
112
+
113
+ <NgbInput
114
+ v-else
115
+ :type="rendererState.inputType"
116
+ :model-value="modelValue ?? ''"
117
+ :disabled="disabled"
118
+ :readonly="readonly"
119
+ @update:modelValue="update"
120
+ />
121
+
122
+ <div v-if="field.helpText" class="mt-1 text-xs text-ngb-muted">{{ field.helpText }}</div>
123
+ </div>
124
+ </template>
@@ -0,0 +1,92 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref } from 'vue'
3
+ import { useRoute, useRouter } from 'vue-router'
4
+ import NgbLookup from '../primitives/NgbLookup.vue'
5
+ import { resolveNgbMetadataFormBehavior } from './config'
6
+ import { isReferenceValue, tryExtractReferenceId } from './entityModel'
7
+ import type { LookupHint, LookupItem, MetadataFormBehavior } from './types'
8
+
9
+ const props = defineProps<{
10
+ hint: LookupHint
11
+ modelValue: unknown
12
+ readonly?: boolean
13
+ disabled?: boolean
14
+ behavior?: MetadataFormBehavior
15
+ }>()
16
+
17
+ const emit = defineEmits<{
18
+ (e: 'update:modelValue', value: unknown): void
19
+ }>()
20
+
21
+ const router = useRouter()
22
+ const route = useRoute()
23
+
24
+ const behavior = computed(() => resolveNgbMetadataFormBehavior(props.behavior))
25
+ const lookupItems = ref<LookupItem[]>([])
26
+ const lookupValueId = computed(() => tryExtractReferenceId(props.modelValue))
27
+ const canOpenLookup = computed(() => !!lookupValueId.value && !!behavior.value.buildLookupTargetUrl)
28
+
29
+ const selectedItem = computed((): LookupItem | null => {
30
+ const value = props.modelValue
31
+ if (!value) return null
32
+
33
+ if (isReferenceValue(value)) {
34
+ return { id: value.id, label: value.display }
35
+ }
36
+
37
+ const id = tryExtractReferenceId(value)
38
+ return id ? { id, label: id } : null
39
+ })
40
+
41
+ const canClearLookup = computed(() => !props.readonly && !props.disabled && !!selectedItem.value)
42
+
43
+ async function onLookupQuery(queryText: string) {
44
+ const query = queryText.trim()
45
+ if (!query || !behavior.value.searchLookup) {
46
+ lookupItems.value = []
47
+ return
48
+ }
49
+
50
+ lookupItems.value = await behavior.value.searchLookup({
51
+ hint: props.hint,
52
+ query,
53
+ })
54
+ }
55
+
56
+ function onLookupSelect(value: LookupItem | null) {
57
+ if (!value) {
58
+ emit('update:modelValue', null)
59
+ return
60
+ }
61
+
62
+ emit('update:modelValue', { id: value.id, display: value.label })
63
+ }
64
+
65
+ async function openLookupValue() {
66
+ if (!behavior.value.buildLookupTargetUrl) return
67
+
68
+ const target = await behavior.value.buildLookupTargetUrl({
69
+ hint: props.hint,
70
+ value: lookupValueId.value,
71
+ routeFullPath: route.fullPath,
72
+ })
73
+
74
+ if (!target) return
75
+ await router.push(target)
76
+ }
77
+ </script>
78
+
79
+ <template>
80
+ <NgbLookup
81
+ :model-value="selectedItem"
82
+ :items="lookupItems"
83
+ :disabled="disabled"
84
+ :readonly="readonly"
85
+ :show-open="canOpenLookup"
86
+ :show-clear="canClearLookup"
87
+ placeholder="Type to search…"
88
+ @update:modelValue="onLookupSelect"
89
+ @query="onLookupQuery"
90
+ @open="void openLookupValue()"
91
+ />
92
+ </template>
@@ -0,0 +1,53 @@
1
+ <script setup lang="ts">
2
+ import type { QueryTrashMode } from '../router/queryParams'
3
+
4
+ type RecycleBinMode = QueryTrashMode
5
+
6
+ const props = defineProps<{
7
+ modelValue: RecycleBinMode
8
+ disabled?: boolean
9
+ }>()
10
+
11
+ const emit = defineEmits<{
12
+ (e: 'update:modelValue', v: RecycleBinMode): void
13
+ }>()
14
+
15
+ type Option = { key: RecycleBinMode; label: string }
16
+ const options: Option[] = [
17
+ { key: 'active', label: 'Active' },
18
+ { key: 'deleted', label: 'Deleted' },
19
+ { key: 'all', label: 'All' },
20
+ ]
21
+
22
+ function pick(v: RecycleBinMode) {
23
+ if (props.disabled) return
24
+ if (v === props.modelValue) return
25
+ emit('update:modelValue', v)
26
+ }
27
+
28
+ function btnClass(v: RecycleBinMode) {
29
+ const isActive = v === props.modelValue
30
+ return [
31
+ 'px-2.5 py-1 text-xs font-medium ngb-focus',
32
+ isActive
33
+ ? 'bg-[var(--ngb-row-selected)] text-ngb-text'
34
+ : 'text-ngb-muted hover:bg-[var(--ngb-row-hover)]',
35
+ props.disabled ? 'opacity-60 cursor-not-allowed' : 'cursor-pointer',
36
+ ].join(' ')
37
+ }
38
+ </script>
39
+
40
+ <template>
41
+ <div class="inline-flex items-center overflow-hidden rounded-[var(--ngb-radius)] border border-ngb-border bg-ngb-card">
42
+ <button
43
+ v-for="opt in options"
44
+ :key="opt.key"
45
+ type="button"
46
+ :class="btnClass(opt.key)"
47
+ :disabled="!!disabled"
48
+ @click="pick(opt.key)"
49
+ >
50
+ {{ opt.label }}
51
+ </button>
52
+ </div>
53
+ </template>