@ngbplatform/ui 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +44 -0
- package/package.json +61 -0
- package/public/favicon.svg +6 -0
- package/public/silent-check-sso.html +8 -0
- package/src/assets/ngb_logo.svg +17 -0
- package/src/assets/ngb_logo_color.svg +17 -0
- package/src/index.ts +1023 -0
- package/src/ngb/README.md +27 -0
- package/src/ngb/accounting/NgbAccountingPeriodClosingPage.vue +1157 -0
- package/src/ngb/accounting/NgbChartOfAccountEditor.vue +590 -0
- package/src/ngb/accounting/NgbChartOfAccountsPage.vue +420 -0
- package/src/ngb/accounting/NgbGeneralJournalEntryEditPage.vue +796 -0
- package/src/ngb/accounting/NgbGeneralJournalEntryLinesEditor.vue +454 -0
- package/src/ngb/accounting/NgbGeneralJournalEntryListPage.vue +186 -0
- package/src/ngb/accounting/api.ts +79 -0
- package/src/ngb/accounting/generalJournalEntry.ts +127 -0
- package/src/ngb/accounting/generalJournalEntryApi.ts +112 -0
- package/src/ngb/accounting/generalJournalEntryTypes.ts +155 -0
- package/src/ngb/accounting/navigation.ts +83 -0
- package/src/ngb/accounting/periodClosing.ts +75 -0
- package/src/ngb/accounting/periodClosingApi.ts +60 -0
- package/src/ngb/accounting/periodClosingTypes.ts +73 -0
- package/src/ngb/accounting/types.ts +57 -0
- package/src/ngb/api/audit.ts +23 -0
- package/src/ngb/api/catalogs.ts +50 -0
- package/src/ngb/api/contracts.ts +285 -0
- package/src/ngb/api/documents.ts +170 -0
- package/src/ngb/api/http.ts +446 -0
- package/src/ngb/api/lookups.ts +14 -0
- package/src/ngb/api/types.ts +6 -0
- package/src/ngb/auth/index.ts +13 -0
- package/src/ngb/auth/keycloak.ts +374 -0
- package/src/ngb/auth/router.ts +35 -0
- package/src/ngb/auth/types.ts +12 -0
- package/src/ngb/auth/useAuthStore.ts +209 -0
- package/src/ngb/command-palette/NgbCommandPaletteDialog.vue +342 -0
- package/src/ngb/command-palette/NgbCommandPaletteEmptyState.vue +55 -0
- package/src/ngb/command-palette/NgbCommandPaletteFooterHints.vue +29 -0
- package/src/ngb/command-palette/api.ts +10 -0
- package/src/ngb/command-palette/config.ts +16 -0
- package/src/ngb/command-palette/presentation.ts +85 -0
- package/src/ngb/command-palette/search.ts +130 -0
- package/src/ngb/command-palette/staticItems.ts +243 -0
- package/src/ngb/command-palette/storage.ts +45 -0
- package/src/ngb/command-palette/store.ts +716 -0
- package/src/ngb/command-palette/types.ts +120 -0
- package/src/ngb/command-palette/useCommandPaletteHotkeys.ts +27 -0
- package/src/ngb/command-palette/useCommandPalettePageContext.ts +18 -0
- package/src/ngb/components/NgbConfirmDialog.vue +63 -0
- package/src/ngb/components/NgbDialog.vue +44 -0
- package/src/ngb/components/NgbDrawer.vue +156 -0
- package/src/ngb/components/NgbHeaderActionCluster.vue +140 -0
- package/src/ngb/components/NgbModalShell.vue +99 -0
- package/src/ngb/components/NgbToolbar.vue +14 -0
- package/src/ngb/components/forms/NgbFormLayout.vue +13 -0
- package/src/ngb/components/forms/NgbFormRow.vue +21 -0
- package/src/ngb/components/forms/NgbFormSection.vue +16 -0
- package/src/ngb/components/forms/NgbValidationSummary.vue +18 -0
- package/src/ngb/components/navigation/NgbNavigationTree.vue +68 -0
- package/src/ngb/components/navigation/NgbTreeNode.vue +94 -0
- package/src/ngb/components/register/NgbColumnChooser.vue +101 -0
- package/src/ngb/components/register/NgbRegisterGrid.vue +473 -0
- package/src/ngb/components/register/registerTypes.ts +51 -0
- package/src/ngb/components/register/useRegisterColumnResize.ts +99 -0
- package/src/ngb/components/register/useRegisterColumnState.ts +220 -0
- package/src/ngb/components/register/useRegisterRows.ts +209 -0
- package/src/ngb/components/register/useRegisterViewport.ts +56 -0
- package/src/ngb/editor/NgbDocumentEffectsPage.vue +512 -0
- package/src/ngb/editor/NgbDocumentFlowPage.vue +409 -0
- package/src/ngb/editor/NgbDocumentPrintPage.vue +712 -0
- package/src/ngb/editor/NgbEditorDiscardDialog.vue +40 -0
- package/src/ngb/editor/NgbEntityAuditSidebar.vue +288 -0
- package/src/ngb/editor/NgbEntityEditor.vue +250 -0
- package/src/ngb/editor/NgbEntityEditorDrawerActions.vue +88 -0
- package/src/ngb/editor/NgbEntityEditorHeader.vue +114 -0
- package/src/ngb/editor/catalogNavigation.ts +13 -0
- package/src/ngb/editor/config.ts +252 -0
- package/src/ngb/editor/documentCopyDraft.ts +245 -0
- package/src/ngb/editor/documentEffects.ts +43 -0
- package/src/ngb/editor/documentEffectsDateFormatting.ts +18 -0
- package/src/ngb/editor/documentNavigation.ts +115 -0
- package/src/ngb/editor/documentStatus.ts +45 -0
- package/src/ngb/editor/entityEditorErrors.ts +162 -0
- package/src/ngb/editor/entityEditorPersistence.ts +241 -0
- package/src/ngb/editor/extensions.ts +40 -0
- package/src/ngb/editor/types.ts +295 -0
- package/src/ngb/editor/useConfiguredEntityEditorDocumentActions.ts +183 -0
- package/src/ngb/editor/useDocumentEditorDrawerState.ts +152 -0
- package/src/ngb/editor/useEditorDrawerState.ts +88 -0
- package/src/ngb/editor/useEntityEditorBusinessContext.ts +97 -0
- package/src/ngb/editor/useEntityEditorCapabilities.ts +190 -0
- package/src/ngb/editor/useEntityEditorCommandPalette.ts +154 -0
- package/src/ngb/editor/useEntityEditorCommitHandlers.ts +102 -0
- package/src/ngb/editor/useEntityEditorHeaderActions.ts +253 -0
- package/src/ngb/editor/useEntityEditorLeaveGuard.ts +66 -0
- package/src/ngb/editor/useEntityEditorNavigationActions.ts +235 -0
- package/src/ngb/editor/useEntityEditorOutputs.ts +59 -0
- package/src/ngb/editor/useEntityEditorPageActions.ts +57 -0
- package/src/ngb/editor/useRouteQueryEditorDrawer.ts +173 -0
- package/src/ngb/env/runtimeConfig.ts +25 -0
- package/src/ngb/lookup/config.ts +42 -0
- package/src/ngb/lookup/defaultConfig.ts +136 -0
- package/src/ngb/lookup/navigation.ts +76 -0
- package/src/ngb/lookup/prefetch.ts +40 -0
- package/src/ngb/lookup/store.ts +261 -0
- package/src/ngb/metadata/NgbDocumentListFiltersDrawer.vue +78 -0
- package/src/ngb/metadata/NgbDocumentPeriodFilter.vue +53 -0
- package/src/ngb/metadata/NgbEntityForm.vue +84 -0
- package/src/ngb/metadata/NgbEntityFormFieldsBlock.vue +110 -0
- package/src/ngb/metadata/NgbEntityListPageHeader.vue +80 -0
- package/src/ngb/metadata/NgbFilterFieldControl.vue +104 -0
- package/src/ngb/metadata/NgbMetadataCatalogEditPage.vue +42 -0
- package/src/ngb/metadata/NgbMetadataCatalogListPage.vue +269 -0
- package/src/ngb/metadata/NgbMetadataDocumentEditPage.vue +68 -0
- package/src/ngb/metadata/NgbMetadataDocumentListPage.vue +353 -0
- package/src/ngb/metadata/NgbMetadataFieldRenderer.vue +124 -0
- package/src/ngb/metadata/NgbMetadataLookupControl.vue +92 -0
- package/src/ngb/metadata/NgbRecycleBinFilter.vue +53 -0
- package/src/ngb/metadata/NgbRegisterPageLayout.vue +143 -0
- package/src/ngb/metadata/config.ts +28 -0
- package/src/ngb/metadata/dataTypes.ts +27 -0
- package/src/ngb/metadata/entityForm.ts +114 -0
- package/src/ngb/metadata/entityModel.ts +34 -0
- package/src/ngb/metadata/entityValueFormatting.ts +83 -0
- package/src/ngb/metadata/fieldRendererState.ts +84 -0
- package/src/ngb/metadata/filtering.ts +153 -0
- package/src/ngb/metadata/lookup.ts +25 -0
- package/src/ngb/metadata/monthPagedListQuery.ts +108 -0
- package/src/ngb/metadata/normalization.ts +100 -0
- package/src/ngb/metadata/referenceHydration.ts +99 -0
- package/src/ngb/metadata/register.ts +135 -0
- package/src/ngb/metadata/routePages.ts +86 -0
- package/src/ngb/metadata/store.ts +35 -0
- package/src/ngb/metadata/types.ts +276 -0
- package/src/ngb/metadata/useMetadataListFilters.ts +306 -0
- package/src/ngb/metadata/useMetadataRegisterPageData.ts +246 -0
- package/src/ngb/metadata/useValidationFocus.ts +49 -0
- package/src/ngb/primitives/NgbBadge.vue +31 -0
- package/src/ngb/primitives/NgbButton.vue +53 -0
- package/src/ngb/primitives/NgbCheckbox.vue +33 -0
- package/src/ngb/primitives/NgbDatePicker.vue +141 -0
- package/src/ngb/primitives/NgbIcon.vue +545 -0
- package/src/ngb/primitives/NgbInput.vue +57 -0
- package/src/ngb/primitives/NgbLookup.vue +264 -0
- package/src/ngb/primitives/NgbMonthPicker.vue +132 -0
- package/src/ngb/primitives/NgbMultiSelect.vue +179 -0
- package/src/ngb/primitives/NgbPeriodPicker.vue +125 -0
- package/src/ngb/primitives/NgbPickerNavButton.vue +31 -0
- package/src/ngb/primitives/NgbPickerPopover.vue +79 -0
- package/src/ngb/primitives/NgbRadioGroup.vue +53 -0
- package/src/ngb/primitives/NgbSelect.vue +108 -0
- package/src/ngb/primitives/NgbStatusIcon.vue +63 -0
- package/src/ngb/primitives/NgbSwitch.vue +42 -0
- package/src/ngb/primitives/NgbTabs.vue +116 -0
- package/src/ngb/primitives/NgbToastHost.vue +49 -0
- package/src/ngb/primitives/NgbTooltip.vue +27 -0
- package/src/ngb/primitives/iconNames.ts +83 -0
- package/src/ngb/primitives/toast.ts +66 -0
- package/src/ngb/primitives/useAsyncComboboxQuery.ts +85 -0
- package/src/ngb/primitives/useFloatingDropdownPosition.ts +129 -0
- package/src/ngb/reporting/NgbReportComposerCollectionSection.vue +106 -0
- package/src/ngb/reporting/NgbReportComposerPanel.vue +1187 -0
- package/src/ngb/reporting/NgbReportDateRangeFilter.vue +54 -0
- package/src/ngb/reporting/NgbReportPage.vue +1124 -0
- package/src/ngb/reporting/NgbReportSheet.vue +467 -0
- package/src/ngb/reporting/api.ts +35 -0
- package/src/ngb/reporting/composer.ts +904 -0
- package/src/ngb/reporting/config.ts +120 -0
- package/src/ngb/reporting/defaultConfig.ts +15 -0
- package/src/ngb/reporting/lookupFilters.ts +43 -0
- package/src/ngb/reporting/navigation.ts +181 -0
- package/src/ngb/reporting/pageHelpers.ts +73 -0
- package/src/ngb/reporting/pageSession.ts +92 -0
- package/src/ngb/reporting/paging.ts +66 -0
- package/src/ngb/reporting/types.ts +333 -0
- package/src/ngb/router/backNavigation.ts +122 -0
- package/src/ngb/router/queryParams.ts +129 -0
- package/src/ngb/router/queryState.ts +121 -0
- package/src/ngb/router/routeAliases.ts +66 -0
- package/src/ngb/router/routeParams.ts +15 -0
- package/src/ngb/router/shareLink.ts +39 -0
- package/src/ngb/security/NgbAccessAuditPanel.vue +32 -0
- package/src/ngb/security/NgbAccessDeniedState.vue +27 -0
- package/src/ngb/security/NgbEffectiveAccessPanel.vue +60 -0
- package/src/ngb/security/NgbPermissionMatrix.vue +160 -0
- package/src/ngb/security/NgbRoleEditorPage.vue +319 -0
- package/src/ngb/security/NgbRolesPage.vue +125 -0
- package/src/ngb/security/NgbUserEditorPage.vue +615 -0
- package/src/ngb/security/NgbUsersPage.vue +125 -0
- package/src/ngb/security/api.ts +91 -0
- package/src/ngb/security/audit.ts +21 -0
- package/src/ngb/security/permissions.ts +65 -0
- package/src/ngb/security/types.ts +157 -0
- package/src/ngb/security/useAccessStore.ts +69 -0
- package/src/ngb/site/NgbDashboardAsOfToolbar.vue +100 -0
- package/src/ngb/site/NgbDashboardStatusBanner.vue +91 -0
- package/src/ngb/site/NgbLogo.vue +44 -0
- package/src/ngb/site/NgbPageHeader.vue +49 -0
- package/src/ngb/site/NgbSiteShell.vue +286 -0
- package/src/ngb/site/NgbSiteSidebar.vue +270 -0
- package/src/ngb/site/NgbTopBar.vue +425 -0
- package/src/ngb/site/NgbTrendChart.vue +224 -0
- package/src/ngb/site/dashboardData.ts +324 -0
- package/src/ngb/site/mainMenuStore.ts +49 -0
- package/src/ngb/site/types.ts +31 -0
- package/src/ngb/site/useDashboardPageState.ts +98 -0
- package/src/ngb/site/useTheme.ts +95 -0
- package/src/ngb/utils/clone.ts +43 -0
- package/src/ngb/utils/dateValues.ts +107 -0
- package/src/ngb/utils/errorMessage.ts +14 -0
- package/src/ngb/utils/guid.ts +21 -0
- package/src/ngb/utils/stableValue.ts +21 -0
- package/src/ngb/utils/storage.ts +135 -0
- package/src/styles/tailwind.css +165 -0
- package/src/vite/publicAssets.ts +104 -0
- package/vite-public-assets.js +68 -0
|
@@ -0,0 +1,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
|
+
}
|