@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,276 @@
1
+ import type { RouteLocationRaw } from 'vue-router'
2
+
3
+ export type Awaitable<T> = T | Promise<T>
4
+
5
+ export type DataType = string
6
+ export type UiControl = number
7
+ export type ColumnAlign = number
8
+ export type EntityKind = number
9
+ export type DocumentStatusValue = number
10
+ export type ActionKind = number
11
+
12
+ export type JsonPrimitive = string | number | boolean | null
13
+ export type JsonValue = JsonPrimitive | JsonObject | JsonValue[]
14
+ export type JsonObject = { [key: string]: JsonValue }
15
+ export type RecordFields = Record<string, JsonValue>
16
+ export type RecordPartRow = RecordFields
17
+ export type RecordPart = { rows: RecordPartRow[] }
18
+ export type RecordParts = Record<string, RecordPart>
19
+ export type RecordPayload = {
20
+ fields?: RecordFields | null
21
+ parts?: RecordParts | null
22
+ }
23
+
24
+ export type LookupSource =
25
+ | {
26
+ kind: 'catalog'
27
+ catalogType: string
28
+ displayTemplate?: string | null
29
+ }
30
+ | {
31
+ kind: 'document'
32
+ documentTypes: string[]
33
+ }
34
+ | {
35
+ kind: 'coa'
36
+ }
37
+
38
+ export type LookupHint =
39
+ | {
40
+ kind: 'catalog'
41
+ catalogType: string
42
+ filters?: Record<string, string>
43
+ }
44
+ | {
45
+ kind: 'document'
46
+ documentTypes: string[]
47
+ }
48
+ | {
49
+ kind: 'coa'
50
+ }
51
+
52
+ export type LookupItem = {
53
+ id: string
54
+ label: string
55
+ meta?: string
56
+ }
57
+
58
+ export type FieldOption = {
59
+ value: string
60
+ label: string
61
+ }
62
+
63
+ export type ColumnMetadata = {
64
+ key: string
65
+ label: string
66
+ dataType: DataType
67
+ isSortable: boolean
68
+ widthPx?: number | null
69
+ align: ColumnAlign
70
+ lookup?: LookupSource | null
71
+ options?: FieldOption[] | null
72
+ }
73
+
74
+ export type ListFilterOption = FieldOption
75
+
76
+ export type ListFilterField = {
77
+ key: string
78
+ label: string
79
+ dataType: DataType
80
+ isMulti?: boolean
81
+ lookup?: LookupSource | null
82
+ options?: ListFilterOption[] | null
83
+ description?: string | null
84
+ supportsIncludeDescendants?: boolean
85
+ }
86
+
87
+ export type ListMetadata = {
88
+ columns: ColumnMetadata[]
89
+ filters?: ListFilterField[] | null
90
+ }
91
+
92
+ export type FieldValidation = {
93
+ maxLength?: number | null
94
+ min?: number | null
95
+ max?: number | null
96
+ regex?: string | null
97
+ }
98
+
99
+ export type FieldMetadata = {
100
+ key: string
101
+ label: string
102
+ dataType: DataType
103
+ uiControl: UiControl
104
+ isRequired: boolean
105
+ isReadOnly: boolean
106
+ readOnlyWhenStatusIn?: DocumentStatusValue[] | null
107
+ lookup?: LookupSource | null
108
+ validation?: FieldValidation | null
109
+ options?: FieldOption[] | null
110
+ helpText?: string | null
111
+ }
112
+
113
+ export type FormRow = {
114
+ fields: FieldMetadata[]
115
+ }
116
+
117
+ export type FormSection = {
118
+ title: string
119
+ rows: FormRow[]
120
+ }
121
+
122
+ export type FormMetadata = {
123
+ sections: FormSection[]
124
+ }
125
+
126
+ export type PartMetadata = {
127
+ partCode: string
128
+ title: string
129
+ list: ListMetadata
130
+ allowAddRemoveRows?: boolean
131
+ readOnlyWhenPosted?: boolean
132
+ }
133
+
134
+ export type ActionMetadata = {
135
+ code: string
136
+ label: string
137
+ kind?: ActionKind
138
+ requiresConfirm?: boolean
139
+ visibleWhenStatusIn?: DocumentStatusValue[] | null
140
+ }
141
+
142
+ export type CatalogTypeMetadata = {
143
+ catalogType: string
144
+ displayName: string
145
+ kind: EntityKind
146
+ icon?: string | null
147
+ list?: ListMetadata | null
148
+ form?: FormMetadata | null
149
+ parts?: PartMetadata[] | null
150
+ capabilities?: CatalogCapabilities | null
151
+ }
152
+
153
+ export type CatalogCapabilities = {
154
+ canCreate?: boolean
155
+ canEdit?: boolean
156
+ canDelete?: boolean
157
+ canMarkForDeletion?: boolean
158
+ }
159
+
160
+ export type DocumentCapabilities = {
161
+ canCreate?: boolean
162
+ canEditDraft?: boolean
163
+ canDeleteDraft?: boolean
164
+ canPost?: boolean
165
+ canUnpost?: boolean
166
+ canRepost?: boolean
167
+ canMarkForDeletion?: boolean
168
+ supportsActions?: boolean
169
+ canViewEffects?: boolean
170
+ canViewFlow?: boolean
171
+ }
172
+
173
+ export type DocumentPresentation = {
174
+ displayName?: string | null
175
+ hasNumber?: boolean
176
+ computedDisplay?: boolean
177
+ hideSystemFieldsInEditor?: boolean
178
+ }
179
+
180
+ export type DocumentTypeMetadata = {
181
+ documentType: string
182
+ displayName: string
183
+ kind: EntityKind
184
+ icon?: string | null
185
+ list?: ListMetadata | null
186
+ form?: FormMetadata | null
187
+ parts?: PartMetadata[] | null
188
+ actions?: ActionMetadata[] | null
189
+ presentation?: DocumentPresentation | null
190
+ capabilities?: DocumentCapabilities | null
191
+ }
192
+
193
+ export type EntityFormModel = Record<string, unknown>
194
+
195
+ export type ReferenceValue = {
196
+ id: string
197
+ display: string
198
+ }
199
+
200
+ export type FilterLookupItem = LookupItem
201
+
202
+ export type FilterFieldState<TItem extends FilterLookupItem = FilterLookupItem> = {
203
+ raw: string
204
+ items: TItem[]
205
+ includeDescendants?: boolean
206
+ }
207
+
208
+ export type FilterFieldOption = {
209
+ value: unknown
210
+ label: string
211
+ }
212
+
213
+ export type FilterFieldLike = {
214
+ label: string
215
+ dataType?: unknown
216
+ isMulti?: boolean
217
+ lookup?: LookupSource | null
218
+ options?: readonly FilterFieldOption[] | null
219
+ supportsIncludeDescendants?: boolean
220
+ }
221
+
222
+ export type LookupStoreApi<TItem extends LookupItem = LookupItem> = {
223
+ searchCatalog: (
224
+ catalogType: string,
225
+ query: string,
226
+ options?: { filters?: Record<string, string> },
227
+ ) => Promise<TItem[]>
228
+ searchCoa: (query: string) => Promise<TItem[]>
229
+ searchDocuments: (documentTypes: string[], query: string) => Promise<TItem[]>
230
+ ensureCatalogLabels: (catalogType: string, ids: string[]) => Promise<void>
231
+ ensureCoaLabels: (ids: string[]) => Promise<void>
232
+ ensureAnyDocumentLabels: (documentTypes: string[], ids: string[]) => Promise<void>
233
+ labelForCatalog: (catalogType: string, id: unknown) => string
234
+ labelForCoa: (id: unknown) => string
235
+ labelForAnyDocument: (documentTypes: string[], id: unknown) => string
236
+ }
237
+
238
+ export type ResolvedLookupSource = LookupHint | LookupSource
239
+
240
+ export type FieldResolverArgs = {
241
+ entityTypeCode: string
242
+ model: EntityFormModel
243
+ field: FieldMetadata
244
+ }
245
+
246
+ export type FieldReadonlyArgs = FieldResolverArgs & {
247
+ status?: DocumentStatusValue
248
+ forceReadonly?: boolean
249
+ }
250
+
251
+ export type FieldHiddenArgs = FieldResolverArgs & {
252
+ isDocumentEntity: boolean
253
+ }
254
+
255
+ export type LookupHintArgs = FieldResolverArgs
256
+
257
+ export type LookupSearchArgs = {
258
+ hint: LookupHint
259
+ query: string
260
+ }
261
+
262
+ export type LookupTargetArgs = {
263
+ hint: LookupHint
264
+ value: unknown
265
+ routeFullPath: string
266
+ }
267
+
268
+ export type MetadataFormBehavior = {
269
+ resolveFieldOptions?: (args: FieldResolverArgs) => FieldOption[] | null
270
+ resolveLookupHint?: (args: LookupHintArgs) => LookupHint | null
271
+ isFieldReadonly?: (args: FieldReadonlyArgs) => boolean
272
+ isFieldHidden?: (args: FieldHiddenArgs) => boolean
273
+ findDisplayField?: (form: FormMetadata) => FieldMetadata | null
274
+ searchLookup?: (args: LookupSearchArgs) => Awaitable<LookupItem[]>
275
+ buildLookupTargetUrl?: (args: LookupTargetArgs) => Awaitable<RouteLocationRaw | null>
276
+ }
@@ -0,0 +1,306 @@
1
+ import { computed, onBeforeUnmount, ref, watch, type ComputedRef } from 'vue';
2
+ import type { RouteLocationNormalizedLoaded, Router } from 'vue-router';
3
+
4
+ import { isGuidString } from '../utils/guid';
5
+ import { stableStringify } from '../utils/stableValue';
6
+ import { normalizeSingleQueryValue, setCleanRouteQuery } from '../router/queryParams';
7
+ import {
8
+ buildFilterOptionLabelsByKey,
9
+ hydrateResolvedLookupItems,
10
+ joinFilterValues,
11
+ labelForResolvedLookup,
12
+ optionLabelForFilter,
13
+ searchResolvedLookupItems,
14
+ splitFilterValues,
15
+ summarizeFilterValues,
16
+ } from './filtering';
17
+ import type {
18
+ FilterFieldState,
19
+ LookupHint,
20
+ LookupItem,
21
+ LookupStoreApi,
22
+ ListFilterField,
23
+ } from './types';
24
+
25
+ export type MetadataListFilterBadge = {
26
+ key: string;
27
+ text: string;
28
+ };
29
+
30
+ export type UseMetadataListFiltersArgs<
31
+ TField extends ListFilterField = ListFilterField,
32
+ TItem extends LookupItem = LookupItem,
33
+ > = {
34
+ route: RouteLocationNormalizedLoaded;
35
+ router: Router;
36
+ entityTypeCode: ComputedRef<string>;
37
+ filters: ComputedRef<readonly TField[]>;
38
+ lookupStore: LookupStoreApi<TItem>;
39
+ resolveLookupHint: (args: {
40
+ entityTypeCode: string;
41
+ field: TField;
42
+ }) => LookupHint | null;
43
+ commitDelayMs?: number;
44
+ };
45
+
46
+ function buildEmptyFilterState<TField extends Pick<ListFilterField, 'key'>, TItem extends LookupItem>(
47
+ filters: readonly TField[],
48
+ ): Record<string, FilterFieldState<TItem>> {
49
+ return Object.fromEntries(filters.map((field) => [field.key, { raw: '', items: [] }]));
50
+ }
51
+
52
+ export function useMetadataListFilters<
53
+ TField extends ListFilterField = ListFilterField,
54
+ TItem extends LookupItem = LookupItem,
55
+ >(
56
+ args: UseMetadataListFiltersArgs<TField, TItem>,
57
+ ) {
58
+ const filterDraft = ref<Record<string, FilterFieldState<TItem>>>({});
59
+ const lookupItemsByFilterKey = ref<Record<string, TItem[]>>({});
60
+ const lookupSearchSeqByFilterKey = ref<Record<string, number>>({});
61
+ const filterDraftSyncToken = ref(0);
62
+ const filterCommitTimers = new Map<string, ReturnType<typeof setTimeout>>();
63
+ const commitDelayMs = args.commitDelayMs ?? 280;
64
+
65
+ function listFilterStateFor(key: string): FilterFieldState<TItem> {
66
+ return filterDraft.value[key] ?? { raw: '', items: [] };
67
+ }
68
+
69
+ function setListFilterState(key: string, next: FilterFieldState<TItem>) {
70
+ filterDraft.value = {
71
+ ...filterDraft.value,
72
+ [key]: next,
73
+ };
74
+ }
75
+
76
+ function clearPendingFilterCommit(key: string) {
77
+ const timer = filterCommitTimers.get(key);
78
+ if (!timer) return;
79
+ clearTimeout(timer);
80
+ filterCommitTimers.delete(key);
81
+ }
82
+
83
+ function clearAllPendingFilterCommits() {
84
+ for (const timer of filterCommitTimers.values()) clearTimeout(timer);
85
+ filterCommitTimers.clear();
86
+ }
87
+
88
+ function resolveListFilterLookupHint(field: TField): LookupHint | null {
89
+ return args.resolveLookupHint({
90
+ entityTypeCode: args.entityTypeCode.value,
91
+ field,
92
+ });
93
+ }
94
+
95
+ async function searchListFilterLookupItems(field: TField, query: string): Promise<TItem[]> {
96
+ const hint = resolveListFilterLookupHint(field);
97
+ if (!hint) return [];
98
+ return await searchResolvedLookupItems(args.lookupStore, hint, query);
99
+ }
100
+
101
+ async function syncFilterDraftFromRoute() {
102
+ const filters = args.filters.value;
103
+ const token = ++filterDraftSyncToken.value;
104
+ const next = buildEmptyFilterState<TField, TItem>(filters);
105
+
106
+ for (const field of filters) {
107
+ const raw = normalizeSingleQueryValue(args.route.query[field.key]);
108
+ next[field.key] = { raw, items: [] };
109
+ }
110
+
111
+ filterDraft.value = next;
112
+
113
+ for (const field of filters) {
114
+ if (!field.lookup) continue;
115
+
116
+ const raw = normalizeSingleQueryValue(args.route.query[field.key]);
117
+ const ids = (field.isMulti ? splitFilterValues(raw) : [raw]).filter(isGuidString);
118
+ if (ids.length === 0) continue;
119
+
120
+ const hint = resolveListFilterLookupHint(field);
121
+ next[field.key] = {
122
+ raw,
123
+ items: hint ? await hydrateResolvedLookupItems(args.lookupStore, hint, ids) : [],
124
+ };
125
+
126
+ if (token !== filterDraftSyncToken.value) return;
127
+ }
128
+
129
+ if (token === filterDraftSyncToken.value) {
130
+ filterDraft.value = next;
131
+ }
132
+ }
133
+
134
+ async function replaceRouteQuery(mutator: (query: Record<string, unknown>) => void) {
135
+ const nextQuery: Record<string, unknown> = { ...args.route.query };
136
+ mutator(nextQuery);
137
+ await setCleanRouteQuery(args.route, args.router, nextQuery, 'replace');
138
+ }
139
+
140
+ async function applyFilterValue(key: string, value: string) {
141
+ clearPendingFilterCommit(key);
142
+ await replaceRouteQuery((query) => {
143
+ query[key] = value || undefined;
144
+ query.offset = 0;
145
+ });
146
+ }
147
+
148
+ function scheduleFilterCommit(key: string, value: string) {
149
+ clearPendingFilterCommit(key);
150
+ const timer = setTimeout(() => {
151
+ filterCommitTimers.delete(key);
152
+ void applyFilterValue(key, value.trim());
153
+ }, commitDelayMs);
154
+ filterCommitTimers.set(key, timer);
155
+ }
156
+
157
+ function isImmediateFilter(field: TField): boolean {
158
+ return !!field.lookup || (field.options?.length ?? 0) > 0;
159
+ }
160
+
161
+ async function handleLookupQuery(payload: { key: string; query: string }) {
162
+ const field = args.filters.value.find((entry) => entry.key === payload.key);
163
+ if (!field?.lookup) return;
164
+
165
+ const nextSeq = (lookupSearchSeqByFilterKey.value[payload.key] ?? 0) + 1;
166
+ lookupSearchSeqByFilterKey.value = {
167
+ ...lookupSearchSeqByFilterKey.value,
168
+ [payload.key]: nextSeq,
169
+ };
170
+
171
+ const items = payload.query.trim()
172
+ ? await searchListFilterLookupItems(field, payload.query)
173
+ : [];
174
+
175
+ if ((lookupSearchSeqByFilterKey.value[payload.key] ?? 0) !== nextSeq) return;
176
+
177
+ lookupItemsByFilterKey.value = {
178
+ ...lookupItemsByFilterKey.value,
179
+ [payload.key]: items,
180
+ };
181
+ }
182
+
183
+ function handleItemsUpdate(payload: { key: string; items: TItem[] }) {
184
+ const raw = joinFilterValues(payload.items.map((item) => item.id));
185
+ setListFilterState(payload.key, {
186
+ raw,
187
+ items: payload.items.map((item) => ({ ...item })),
188
+ });
189
+
190
+ void applyFilterValue(payload.key, raw);
191
+ }
192
+
193
+ function handleValueUpdate(payload: { key: string; value: string }) {
194
+ const field = args.filters.value.find((entry) => entry.key === payload.key);
195
+ if (!field) return;
196
+
197
+ setListFilterState(payload.key, {
198
+ raw: payload.value,
199
+ items: [],
200
+ });
201
+
202
+ if (isImmediateFilter(field)) {
203
+ void applyFilterValue(payload.key, payload.value.trim());
204
+ return;
205
+ }
206
+
207
+ scheduleFilterCommit(payload.key, payload.value);
208
+ }
209
+
210
+ async function undo() {
211
+ clearAllPendingFilterCommits();
212
+ filterDraft.value = buildEmptyFilterState<TField, TItem>(args.filters.value);
213
+
214
+ await replaceRouteQuery((query) => {
215
+ for (const field of args.filters.value) delete query[field.key];
216
+ query.offset = 0;
217
+ });
218
+ }
219
+
220
+ const optionLabelsByColumnKey = computed(() => buildFilterOptionLabelsByKey(args.filters.value));
221
+
222
+ const activeFilterBadges = computed<MetadataListFilterBadge[]>(() =>
223
+ args.filters.value
224
+ .map((field) => {
225
+ const appliedRaw = normalizeSingleQueryValue(args.route.query[field.key]);
226
+ if (!appliedRaw) return null;
227
+
228
+ const state = listFilterStateFor(field.key);
229
+ const selectedLabels = state.raw.trim() === appliedRaw
230
+ ? new Map(
231
+ state.items
232
+ .map((item) => [
233
+ String(item.id ?? '').trim().toLowerCase(),
234
+ String(item.label ?? item.id ?? '').trim(),
235
+ ] satisfies [string, string])
236
+ .filter((entry) => entry[0].length > 0 && entry[1].length > 0),
237
+ )
238
+ : new Map<string, string>();
239
+ const lookupHint = field.lookup ? resolveListFilterLookupHint(field) : null;
240
+ const displayValues = (field.isMulti ? splitFilterValues(appliedRaw) : [appliedRaw])
241
+ .map((value) => {
242
+ const normalized = value.trim();
243
+ if (!normalized) return '';
244
+
245
+ const fromDraft = selectedLabels.get(normalized.toLowerCase());
246
+ if (fromDraft) return fromDraft;
247
+ if (lookupHint) return labelForResolvedLookup(args.lookupStore, lookupHint, normalized);
248
+ return optionLabelForFilter(field, normalized) || normalized;
249
+ })
250
+ .filter((value) => value.length > 0);
251
+ const summary = summarizeFilterValues(displayValues);
252
+ if (!summary) return null;
253
+
254
+ return {
255
+ key: field.key,
256
+ text: `${field.label}: ${summary}`,
257
+ } satisfies MetadataListFilterBadge;
258
+ })
259
+ .filter((entry): entry is MetadataListFilterBadge => !!entry),
260
+ );
261
+
262
+ const hasActiveFilters = computed(() => activeFilterBadges.value.length > 0);
263
+ const canUndoFilters = computed(() =>
264
+ hasActiveFilters.value
265
+ || Object.values(filterDraft.value).some((state) => state.items.length > 0 || state.raw.trim().length > 0),
266
+ );
267
+
268
+ watch(
269
+ () => stableStringify(
270
+ args.filters.value
271
+ .map((field) => [field.key, normalizeSingleQueryValue(args.route.query[field.key])])
272
+ .sort(([left], [right]) => String(left).localeCompare(String(right))),
273
+ ),
274
+ () => {
275
+ void syncFilterDraftFromRoute();
276
+ },
277
+ { immediate: true },
278
+ );
279
+
280
+ watch(
281
+ () => args.entityTypeCode.value,
282
+ () => {
283
+ filterDraft.value = {};
284
+ lookupItemsByFilterKey.value = {};
285
+ lookupSearchSeqByFilterKey.value = {};
286
+ clearAllPendingFilterCommits();
287
+ },
288
+ );
289
+
290
+ onBeforeUnmount(() => {
291
+ clearAllPendingFilterCommits();
292
+ });
293
+
294
+ return {
295
+ filterDraft,
296
+ lookupItemsByFilterKey,
297
+ optionLabelsByColumnKey,
298
+ activeFilterBadges,
299
+ hasActiveFilters,
300
+ canUndoFilters,
301
+ handleLookupQuery,
302
+ handleItemsUpdate,
303
+ handleValueUpdate,
304
+ undo,
305
+ };
306
+ }