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