@quazardous/qdadm 1.19.3
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 +21 -0
- package/README.md +111 -0
- package/package.json +101 -0
- package/src/assets/logo.svg +6 -0
- package/src/auth/SessionAuthAdapter.ts +459 -0
- package/src/auth/index.ts +15 -0
- package/src/chain/ActiveStack.ts +154 -0
- package/src/chain/StackHydrator.ts +292 -0
- package/src/chain/index.ts +25 -0
- package/src/chain/useActiveStack.ts +120 -0
- package/src/chain/useStackHydrator.ts +155 -0
- package/src/components/BoolCell.vue +34 -0
- package/src/components/InfoBanner.vue +73 -0
- package/src/components/QdadmRoot.vue +52 -0
- package/src/components/SeverityTag.vue +86 -0
- package/src/components/dialogs/BulkStatusDialog.vue +60 -0
- package/src/components/dialogs/MultiStepDialog.vue +331 -0
- package/src/components/dialogs/SimpleDialog.vue +122 -0
- package/src/components/dialogs/UnsavedChangesDialog.vue +80 -0
- package/src/components/display/BannerZone.vue +65 -0
- package/src/components/display/CardsGrid.vue +72 -0
- package/src/components/display/CopyableId.vue +84 -0
- package/src/components/display/EmptyState.vue +62 -0
- package/src/components/display/IntensityBar.vue +190 -0
- package/src/components/display/RichCardsGrid.vue +101 -0
- package/src/components/edit/FormActions.vue +78 -0
- package/src/components/edit/FormField.vue +82 -0
- package/src/components/edit/FormInput.vue +158 -0
- package/src/components/edit/FormPage.vue +260 -0
- package/src/components/edit/FormTab.vue +217 -0
- package/src/components/edit/FormTabs.vue +120 -0
- package/src/components/edit/LookupField.vue +406 -0
- package/src/components/edit/LookupPickerDialog.vue +230 -0
- package/src/components/editors/JsonEditorFoldable.vue +470 -0
- package/src/components/editors/JsonStructuredField.vue +264 -0
- package/src/components/editors/JsonViewer.vue +51 -0
- package/src/components/editors/KeyValueEditor.vue +314 -0
- package/src/components/editors/LanguageEditor.vue +259 -0
- package/src/components/editors/PermissionEditor.vue +558 -0
- package/src/components/editors/ScopeEditor.vue +372 -0
- package/src/components/editors/VanillaJsonEditor.vue +183 -0
- package/src/components/index.ts +83 -0
- package/src/components/item/FieldGroups.vue +392 -0
- package/src/components/layout/AppLayout.vue +1032 -0
- package/src/components/layout/BaseLayout.vue +190 -0
- package/src/components/layout/Breadcrumb.vue +55 -0
- package/src/components/layout/DashboardLayout.vue +104 -0
- package/src/components/layout/FormLayout.vue +256 -0
- package/src/components/layout/ListLayout.vue +278 -0
- package/src/components/layout/PageHeader.vue +156 -0
- package/src/components/layout/PageLayout.vue +126 -0
- package/src/components/layout/PageNav.vue +192 -0
- package/src/components/layout/SidebarBox.vue +87 -0
- package/src/components/layout/Zone.vue +182 -0
- package/src/components/layout/defaults/DefaultBreadcrumb.vue +149 -0
- package/src/components/layout/defaults/DefaultFooter.vue +89 -0
- package/src/components/layout/defaults/DefaultFormActions.vue +67 -0
- package/src/components/layout/defaults/DefaultHeader.vue +78 -0
- package/src/components/layout/defaults/DefaultMenu.vue +188 -0
- package/src/components/layout/defaults/DefaultPagination.vue +75 -0
- package/src/components/layout/defaults/DefaultTable.vue +137 -0
- package/src/components/layout/defaults/DefaultUserInfo.vue +111 -0
- package/src/components/layout/defaults/index.ts +16 -0
- package/src/components/lists/ActionButtons.vue +49 -0
- package/src/components/lists/ActionColumn.vue +40 -0
- package/src/components/lists/FilterBar.vue +54 -0
- package/src/components/lists/ListPage.vue +467 -0
- package/src/components/pages/LoginPage.vue +334 -0
- package/src/components/pages/NotFoundPage.vue +98 -0
- package/src/components/show/ShowDisplay.vue +343 -0
- package/src/components/show/ShowField.vue +67 -0
- package/src/components/show/ShowPage.vue +431 -0
- package/src/composables/index.ts +155 -0
- package/src/composables/useApp.ts +67 -0
- package/src/composables/useAuth.ts +98 -0
- package/src/composables/useBareForm.ts +319 -0
- package/src/composables/useBreadcrumb.ts +252 -0
- package/src/composables/useChildPage.ts +113 -0
- package/src/composables/useCurrentEntity.ts +60 -0
- package/src/composables/useDeferred.ts +94 -0
- package/src/composables/useDirtyState.ts +138 -0
- package/src/composables/useEntityItemFormPage.ts +942 -0
- package/src/composables/useEntityItemFormPage.types.ts +451 -0
- package/src/composables/useEntityItemPage.ts +438 -0
- package/src/composables/useEntityItemShowPage.ts +722 -0
- package/src/composables/useFieldManager.ts +692 -0
- package/src/composables/useForm.ts +544 -0
- package/src/composables/useGuardStore.ts +53 -0
- package/src/composables/useHooks.ts +56 -0
- package/src/composables/useInfoBanner.ts +217 -0
- package/src/composables/useJsonSyntax.ts +122 -0
- package/src/composables/useLayoutResolver.ts +329 -0
- package/src/composables/useListPage.ts +1535 -0
- package/src/composables/useListPage.types.ts +437 -0
- package/src/composables/useListPage.utils.ts +101 -0
- package/src/composables/useNavContext.ts +439 -0
- package/src/composables/useNavigation.ts +216 -0
- package/src/composables/useOptionsLookup.ts +323 -0
- package/src/composables/usePageTitle.ts +84 -0
- package/src/composables/useSSEBridge.ts +142 -0
- package/src/composables/useSemanticBreadcrumb.ts +222 -0
- package/src/composables/useSidebarState.ts +24 -0
- package/src/composables/useSignals.ts +52 -0
- package/src/composables/useUnsavedChangesGuard.ts +155 -0
- package/src/composables/useUserImpersonator.ts +317 -0
- package/src/composables/useZoneRegistry.ts +252 -0
- package/src/core/bundles.ts +558 -0
- package/src/core/decorator.ts +420 -0
- package/src/core/extension.ts +470 -0
- package/src/core/index.ts +59 -0
- package/src/deferred/DeferredRegistry.ts +353 -0
- package/src/deferred/index.ts +15 -0
- package/src/editors/index.ts +12 -0
- package/src/entity/EntityManager.cache.ts +530 -0
- package/src/entity/EntityManager.crud.ts +615 -0
- package/src/entity/EntityManager.interface.ts +78 -0
- package/src/entity/EntityManager.query.ts +221 -0
- package/src/entity/EntityManager.relations.ts +151 -0
- package/src/entity/EntityManager.ts +944 -0
- package/src/entity/EntityManager.types.ts +288 -0
- package/src/entity/auth/CompositeAuthAdapter.ts +194 -0
- package/src/entity/auth/EntityAuthAdapter.ts +119 -0
- package/src/entity/auth/PermissiveAdapter.ts +43 -0
- package/src/entity/auth/RoleHierarchy.ts +128 -0
- package/src/entity/auth/SecurityChecker.ts +202 -0
- package/src/entity/auth/factory.ts +203 -0
- package/src/entity/auth/index.ts +47 -0
- package/src/entity/factory.test.js +189 -0
- package/src/entity/factory.ts +146 -0
- package/src/entity/index.ts +49 -0
- package/src/entity/storage/ApiStorage.ts +228 -0
- package/src/entity/storage/IStorage.ts +100 -0
- package/src/entity/storage/LocalStorage.ts +200 -0
- package/src/entity/storage/MemoryStorage.ts +184 -0
- package/src/entity/storage/MockApiStorage.ts +318 -0
- package/src/entity/storage/SdkStorage.ts +377 -0
- package/src/entity/storage/factory.test.js +159 -0
- package/src/entity/storage/factory.ts +209 -0
- package/src/entity/storage/index.ts +75 -0
- package/src/gen/FieldMapper.ts +122 -0
- package/src/gen/StorageProfileFactory.ts +93 -0
- package/src/gen/connectors/BaseConnector.ts +150 -0
- package/src/gen/connectors/ManualConnector.test.js +499 -0
- package/src/gen/connectors/ManualConnector.ts +414 -0
- package/src/gen/connectors/OpenAPIConnector.test.js +836 -0
- package/src/gen/connectors/OpenAPIConnector.ts +693 -0
- package/src/gen/connectors/__fixtures__/sample-openapi.json +311 -0
- package/src/gen/connectors/index.ts +27 -0
- package/src/gen/createManagers.ts +245 -0
- package/src/gen/decorators.ts +134 -0
- package/src/gen/generateManagers.test.js +540 -0
- package/src/gen/generateManagers.ts +398 -0
- package/src/gen/index.ts +66 -0
- package/src/gen/schema.ts +248 -0
- package/src/gen/vite-plugin.ts +114 -0
- package/src/generated/managers/testManager.js +45 -0
- package/src/generated/managers/testManager.ts +46 -0
- package/src/hooks/HookRegistry.ts +16 -0
- package/src/hooks/index.ts +15 -0
- package/src/i18n/I18n.ts +344 -0
- package/src/i18n/IncrementalDomainProvider.ts +153 -0
- package/src/i18n/InlineTranslationProvider.ts +4 -0
- package/src/i18n/LazyTranslationProvider.ts +102 -0
- package/src/i18n/MessagesRegistry.ts +4 -0
- package/src/i18n/Resolver.ts +4 -0
- package/src/i18n/__tests__/I18n.test.ts +169 -0
- package/src/i18n/__tests__/IncrementalDomainProvider.test.ts +146 -0
- package/src/i18n/__tests__/LazyTranslationProvider.test.ts +100 -0
- package/src/i18n/__tests__/Resolver.test.ts +271 -0
- package/src/i18n/defaults/DefaultCoreProvider.ts +28 -0
- package/src/i18n/defaults/core.en.yml +55 -0
- package/src/i18n/defaults/core.fr.yml +55 -0
- package/src/i18n/index.ts +55 -0
- package/src/i18n/loaders/raw-modules.d.ts +15 -0
- package/src/i18n/loaders/yaml.ts +35 -0
- package/src/i18n/strategies.ts +4 -0
- package/src/i18n/types.ts +34 -0
- package/src/i18n/useI18n.ts +34 -0
- package/src/index.ts +453 -0
- package/src/kernel/EventRouter.ts +23 -0
- package/src/kernel/Kernel.api.ts +79 -0
- package/src/kernel/Kernel.auth.ts +120 -0
- package/src/kernel/Kernel.i18n.ts +29 -0
- package/src/kernel/Kernel.modules.ts +170 -0
- package/src/kernel/Kernel.registries.ts +237 -0
- package/src/kernel/Kernel.routing.ts +334 -0
- package/src/kernel/Kernel.ts +460 -0
- package/src/kernel/Kernel.types.ts +263 -0
- package/src/kernel/Kernel.vue.ts +304 -0
- package/src/kernel/KernelContext.entities.ts +80 -0
- package/src/kernel/KernelContext.events.ts +57 -0
- package/src/kernel/KernelContext.i18n.ts +37 -0
- package/src/kernel/KernelContext.permissions.ts +38 -0
- package/src/kernel/KernelContext.routing.ts +280 -0
- package/src/kernel/KernelContext.ts +252 -0
- package/src/kernel/KernelContext.types.ts +173 -0
- package/src/kernel/KernelContext.zones.ts +54 -0
- package/src/kernel/Module.ts +169 -0
- package/src/kernel/ModuleLoader.ts +645 -0
- package/src/kernel/SSEBridge.ts +11 -0
- package/src/kernel/SignalBus.ts +48 -0
- package/src/kernel/index.ts +56 -0
- package/src/module/index.ts +33 -0
- package/src/module/moduleRegistry.ts +492 -0
- package/src/modules/debug/AuthCollector.ts +501 -0
- package/src/modules/debug/Collector.ts +20 -0
- package/src/modules/debug/DebugBridge.ts +14 -0
- package/src/modules/debug/DebugModule.ts +220 -0
- package/src/modules/debug/EntitiesCollector.ts +839 -0
- package/src/modules/debug/ErrorCollector.ts +4 -0
- package/src/modules/debug/I18nCollector.ts +9 -0
- package/src/modules/debug/LocalStorageAdapter.ts +7 -0
- package/src/modules/debug/RouterCollector.ts +449 -0
- package/src/modules/debug/SignalCollector.ts +4 -0
- package/src/modules/debug/ToastCollector.ts +4 -0
- package/src/modules/debug/ZonesCollector.ts +447 -0
- package/src/modules/debug/components/DebugBar.vue +31 -0
- package/src/modules/debug/components/adminPanelsConfig.ts +42 -0
- package/src/modules/debug/components/index.ts +9 -0
- package/src/modules/debug/components/panels/AuthPanel.vue +267 -0
- package/src/modules/debug/components/panels/EntitiesPanel.vue +572 -0
- package/src/modules/debug/components/panels/I18nPanel.vue +738 -0
- package/src/modules/debug/components/panels/RouterPanel.vue +440 -0
- package/src/modules/debug/components/panels/ZonesPanel.vue +133 -0
- package/src/modules/debug/components/panels/index.ts +16 -0
- package/src/modules/debug/index.ts +58 -0
- package/src/modules/debug/styles.scss +2671 -0
- package/src/notifications/NotificationBadge.vue +35 -0
- package/src/notifications/NotificationListener.vue +71 -0
- package/src/notifications/NotificationModule.ts +76 -0
- package/src/notifications/NotificationPanel.vue +478 -0
- package/src/notifications/NotificationStore.ts +288 -0
- package/src/notifications/index.ts +42 -0
- package/src/notifications/styles.scss +32 -0
- package/src/orchestrator/Orchestrator.ts +427 -0
- package/src/orchestrator/index.ts +8 -0
- package/src/orchestrator/useOrchestrator.ts +80 -0
- package/src/plugin.ts +251 -0
- package/src/query/FilterQuery.ts +306 -0
- package/src/query/QueryExecutor.ts +341 -0
- package/src/query/index.ts +16 -0
- package/src/security/EntityRolesProvider.ts +385 -0
- package/src/security/PermissionMatcher.ts +152 -0
- package/src/security/PermissionRegistry.ts +273 -0
- package/src/security/PersistableRolesProvider.ts +584 -0
- package/src/security/RolesManager.ts +136 -0
- package/src/security/RolesProvider.ts +167 -0
- package/src/security/RolesProviderStorage.ts +211 -0
- package/src/security/SecurityModule.ts +94 -0
- package/src/security/StaticRolesProvider.ts +117 -0
- package/src/security/UsersManager.ts +163 -0
- package/src/security/index.ts +77 -0
- package/src/security/pages/RoleForm.vue +276 -0
- package/src/security/pages/RoleList.vue +123 -0
- package/src/styles/_alerts.scss +95 -0
- package/src/styles/_buttons.scss +391 -0
- package/src/styles/_cards.scss +241 -0
- package/src/styles/_code.scss +78 -0
- package/src/styles/_dialogs.scss +55 -0
- package/src/styles/_filter-bar.scss +70 -0
- package/src/styles/_forms.scss +434 -0
- package/src/styles/_helpers.scss +153 -0
- package/src/styles/_lists.scss +225 -0
- package/src/styles/_main.scss +356 -0
- package/src/styles/_markdown.scss +88 -0
- package/src/styles/_responsive.scss +255 -0
- package/src/styles/_show-pages.scss +186 -0
- package/src/styles/_sidebar.scss +380 -0
- package/src/styles/_states.scss +115 -0
- package/src/styles/_stats.scss +47 -0
- package/src/styles/_variables.scss +224 -0
- package/src/styles/index.scss +38 -0
- package/src/styles/theme/_components.scss +331 -0
- package/src/styles/theme/_index.scss +10 -0
- package/src/styles/theme/_tokens.scss +147 -0
- package/src/styles/theme/_utilities.scss +175 -0
- package/src/toast/ToastBridgeModule.ts +68 -0
- package/src/toast/ToastListener.vue +55 -0
- package/src/toast/index.ts +26 -0
- package/src/toast/useSignalToast.ts +154 -0
- package/src/types/index.ts +285 -0
- package/src/types/pluralize.d.ts +19 -0
- package/src/types/quarkernel.d.ts +50 -0
- package/src/utils/formatters.ts +174 -0
- package/src/utils/index.ts +35 -0
- package/src/utils/transformers.ts +131 -0
- package/src/vite/qdadmDebugPlugin.ts +401 -0
- package/src/vite-env.d.ts +50 -0
- package/src/zones/ZoneRegistry.ts +934 -0
- package/src/zones/index.ts +29 -0
- package/src/zones/zones.ts +191 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* ListLayout - Second level of 3-level layout inheritance for list pages
|
|
4
|
+
*
|
|
5
|
+
* Extends BaseLayout by providing content to the "main" zone and
|
|
6
|
+
* defining list-specific zones within:
|
|
7
|
+
* - before-table: Filter bar, search, bulk actions
|
|
8
|
+
* - table: DataTable component
|
|
9
|
+
* - after-table: Summary, additional actions
|
|
10
|
+
* - pagination: Paginator controls
|
|
11
|
+
*
|
|
12
|
+
* Inheritance pattern (Twig-style):
|
|
13
|
+
* BaseLayout (root)
|
|
14
|
+
* -> ListLayout (fills main zone, defines list zones)
|
|
15
|
+
* -> EntityListPage (fills list-specific slots)
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* <ListLayout :items="data" :loading="isLoading">
|
|
19
|
+
* <template #columns>
|
|
20
|
+
* <Column field="name" header="Name" />
|
|
21
|
+
* </template>
|
|
22
|
+
* </ListLayout>
|
|
23
|
+
*
|
|
24
|
+
* Zone customization via ZoneRegistry:
|
|
25
|
+
* ```js
|
|
26
|
+
* registry.registerBlock(LIST_ZONES.BEFORE_TABLE, {
|
|
27
|
+
* component: CustomFilterBar,
|
|
28
|
+
* id: 'custom-filter'
|
|
29
|
+
* })
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
import { computed, useSlots, provide, type ComputedRef } from 'vue'
|
|
33
|
+
import BaseLayout from './BaseLayout.vue'
|
|
34
|
+
import Zone from './Zone.vue'
|
|
35
|
+
import { LIST_ZONES } from '../../zones/zones'
|
|
36
|
+
|
|
37
|
+
// Default components for list zones
|
|
38
|
+
import DefaultTable from './defaults/DefaultTable.vue'
|
|
39
|
+
import DefaultPagination from './defaults/DefaultPagination.vue'
|
|
40
|
+
import FilterBar from '../lists/FilterBar.vue'
|
|
41
|
+
|
|
42
|
+
interface PageEvent {
|
|
43
|
+
first: number
|
|
44
|
+
rows: number
|
|
45
|
+
page: number
|
|
46
|
+
pageCount: number
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface SortEvent {
|
|
50
|
+
sortField: string | ((item: unknown) => string) | undefined
|
|
51
|
+
sortOrder: 1 | -1 | 0 | undefined | null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface TableProps {
|
|
55
|
+
items: unknown[]
|
|
56
|
+
loading: boolean
|
|
57
|
+
dataKey: string
|
|
58
|
+
selection: unknown[] | undefined
|
|
59
|
+
selectable: boolean
|
|
60
|
+
sortField?: string
|
|
61
|
+
sortOrder: 1 | -1
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface PaginationProps {
|
|
65
|
+
first: number
|
|
66
|
+
rows: number
|
|
67
|
+
totalRecords: number
|
|
68
|
+
rowsPerPageOptions: number[]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface ListContext {
|
|
72
|
+
items: ComputedRef<unknown[]>
|
|
73
|
+
loading: ComputedRef<boolean>
|
|
74
|
+
selected: ComputedRef<unknown[] | undefined>
|
|
75
|
+
selectable: ComputedRef<boolean>
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface Props {
|
|
79
|
+
// Table data
|
|
80
|
+
/** Data items to display in the table */
|
|
81
|
+
items?: unknown[]
|
|
82
|
+
/** Loading state */
|
|
83
|
+
loading?: boolean
|
|
84
|
+
/** Field to use as unique key for rows */
|
|
85
|
+
dataKey?: string
|
|
86
|
+
// Selection
|
|
87
|
+
/** Current selection (v-model compatible) */
|
|
88
|
+
selected?: unknown[]
|
|
89
|
+
/** Enable row selection */
|
|
90
|
+
selectable?: boolean
|
|
91
|
+
// Sorting
|
|
92
|
+
/** Current sort field */
|
|
93
|
+
sortField?: string
|
|
94
|
+
/** Current sort order (1 = asc, -1 = desc) */
|
|
95
|
+
sortOrder?: 1 | -1
|
|
96
|
+
// Pagination
|
|
97
|
+
/** Enable pagination (in table or separate) */
|
|
98
|
+
paginator?: boolean
|
|
99
|
+
/** Use separate pagination zone (outside table) */
|
|
100
|
+
separatePagination?: boolean
|
|
101
|
+
/** Number of rows per page */
|
|
102
|
+
rows?: number
|
|
103
|
+
/** Options for rows per page dropdown */
|
|
104
|
+
rowsPerPageOptions?: number[]
|
|
105
|
+
/** Total number of records (for lazy/server-side pagination) */
|
|
106
|
+
totalRecords?: number
|
|
107
|
+
/** First record index (for lazy pagination) */
|
|
108
|
+
first?: number
|
|
109
|
+
/** Enable lazy (server-side) loading */
|
|
110
|
+
lazy?: boolean
|
|
111
|
+
// Search/Filter
|
|
112
|
+
/** Current search query (v-model compatible) */
|
|
113
|
+
searchQuery?: string
|
|
114
|
+
/** Search input placeholder */
|
|
115
|
+
searchPlaceholder?: string
|
|
116
|
+
/** Show filter bar zone */
|
|
117
|
+
showFilterBar?: boolean
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
121
|
+
items: () => [],
|
|
122
|
+
loading: false,
|
|
123
|
+
dataKey: 'id',
|
|
124
|
+
selected: undefined,
|
|
125
|
+
selectable: false,
|
|
126
|
+
sortField: undefined,
|
|
127
|
+
sortOrder: 1,
|
|
128
|
+
paginator: true,
|
|
129
|
+
separatePagination: false,
|
|
130
|
+
rows: 10,
|
|
131
|
+
rowsPerPageOptions: () => [10, 25, 50, 100],
|
|
132
|
+
totalRecords: 0,
|
|
133
|
+
first: 0,
|
|
134
|
+
lazy: false,
|
|
135
|
+
searchQuery: '',
|
|
136
|
+
searchPlaceholder: 'Search...',
|
|
137
|
+
showFilterBar: true
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
const emit = defineEmits<{
|
|
141
|
+
'update:selected': [value: unknown[]]
|
|
142
|
+
'update:searchQuery': [value: string]
|
|
143
|
+
'page': [event: PageEvent]
|
|
144
|
+
'sort': [event: SortEvent]
|
|
145
|
+
}>()
|
|
146
|
+
|
|
147
|
+
const slots = useSlots()
|
|
148
|
+
|
|
149
|
+
// Check for specific slot content
|
|
150
|
+
const hasColumnsSlot = computed((): boolean => !!slots.columns)
|
|
151
|
+
const hasBeforeTableSlot = computed((): boolean => !!slots['before-table'])
|
|
152
|
+
const hasAfterTableSlot = computed((): boolean => !!slots['after-table'])
|
|
153
|
+
const hasPaginationSlot = computed((): boolean => !!slots.pagination)
|
|
154
|
+
|
|
155
|
+
// Provide list context to child components (for deep customization)
|
|
156
|
+
provide<ListContext>('qdadmListContext', {
|
|
157
|
+
items: computed(() => props.items),
|
|
158
|
+
loading: computed(() => props.loading),
|
|
159
|
+
selected: computed(() => props.selected),
|
|
160
|
+
selectable: computed(() => props.selectable)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
// Props bundle for table zone
|
|
164
|
+
const tableProps = computed((): TableProps => ({
|
|
165
|
+
items: props.items,
|
|
166
|
+
loading: props.loading,
|
|
167
|
+
dataKey: props.dataKey,
|
|
168
|
+
selection: props.selected,
|
|
169
|
+
selectable: props.selectable,
|
|
170
|
+
sortField: props.sortField,
|
|
171
|
+
sortOrder: props.sortOrder
|
|
172
|
+
}))
|
|
173
|
+
|
|
174
|
+
// Props bundle for pagination zone
|
|
175
|
+
const paginationProps = computed((): PaginationProps => ({
|
|
176
|
+
first: props.first,
|
|
177
|
+
rows: props.rows,
|
|
178
|
+
totalRecords: props.totalRecords,
|
|
179
|
+
rowsPerPageOptions: props.rowsPerPageOptions
|
|
180
|
+
}))
|
|
181
|
+
|
|
182
|
+
// Event handlers
|
|
183
|
+
function onSelectionChange(value: unknown[]): void {
|
|
184
|
+
emit('update:selected', value)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function onSearchChange(value: string): void {
|
|
188
|
+
emit('update:searchQuery', value)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function onPage(event: PageEvent): void {
|
|
192
|
+
emit('page', event)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function onSort(event: SortEvent): void {
|
|
196
|
+
emit('sort', event)
|
|
197
|
+
}
|
|
198
|
+
</script>
|
|
199
|
+
|
|
200
|
+
<template>
|
|
201
|
+
<BaseLayout>
|
|
202
|
+
<template #main>
|
|
203
|
+
<div class="list-layout">
|
|
204
|
+
<!-- Before-table zone: Filter bar, search, bulk actions -->
|
|
205
|
+
<Zone
|
|
206
|
+
v-if="showFilterBar"
|
|
207
|
+
:name="LIST_ZONES.BEFORE_TABLE"
|
|
208
|
+
:default-component="FilterBar"
|
|
209
|
+
:block-props="{
|
|
210
|
+
modelValue: searchQuery,
|
|
211
|
+
placeholder: searchPlaceholder,
|
|
212
|
+
'onUpdate:modelValue': onSearchChange
|
|
213
|
+
}"
|
|
214
|
+
>
|
|
215
|
+
<!-- Slot fallback if no blocks and no default -->
|
|
216
|
+
<template v-if="hasBeforeTableSlot">
|
|
217
|
+
<slot name="before-table" />
|
|
218
|
+
</template>
|
|
219
|
+
</Zone>
|
|
220
|
+
|
|
221
|
+
<!-- Table zone: DataTable -->
|
|
222
|
+
<!-- When columns slot is provided, we don't set default-component so slot content renders -->
|
|
223
|
+
<Zone
|
|
224
|
+
:name="LIST_ZONES.TABLE"
|
|
225
|
+
:default-component="hasColumnsSlot ? undefined : DefaultTable"
|
|
226
|
+
:block-props="{
|
|
227
|
+
...tableProps,
|
|
228
|
+
'onUpdate:selection': onSelectionChange,
|
|
229
|
+
onSort: onSort
|
|
230
|
+
}"
|
|
231
|
+
>
|
|
232
|
+
<!-- Render DefaultTable with columns slot when slot is provided -->
|
|
233
|
+
<DefaultTable
|
|
234
|
+
v-bind="tableProps"
|
|
235
|
+
@update:selection="onSelectionChange"
|
|
236
|
+
@sort="onSort"
|
|
237
|
+
>
|
|
238
|
+
<slot name="columns" />
|
|
239
|
+
</DefaultTable>
|
|
240
|
+
</Zone>
|
|
241
|
+
|
|
242
|
+
<!-- After-table zone: Summary, additional actions -->
|
|
243
|
+
<Zone :name="LIST_ZONES.AFTER_TABLE">
|
|
244
|
+
<template v-if="hasAfterTableSlot">
|
|
245
|
+
<slot name="after-table" />
|
|
246
|
+
</template>
|
|
247
|
+
</Zone>
|
|
248
|
+
|
|
249
|
+
<!-- Pagination zone: Paginator (when separate from table) -->
|
|
250
|
+
<Zone
|
|
251
|
+
v-if="paginator && separatePagination"
|
|
252
|
+
:name="LIST_ZONES.PAGINATION"
|
|
253
|
+
:default-component="DefaultPagination"
|
|
254
|
+
:block-props="{
|
|
255
|
+
...paginationProps,
|
|
256
|
+
onPage: onPage
|
|
257
|
+
}"
|
|
258
|
+
>
|
|
259
|
+
<template v-if="hasPaginationSlot">
|
|
260
|
+
<slot name="pagination" />
|
|
261
|
+
</template>
|
|
262
|
+
</Zone>
|
|
263
|
+
</div>
|
|
264
|
+
</template>
|
|
265
|
+
</BaseLayout>
|
|
266
|
+
</template>
|
|
267
|
+
|
|
268
|
+
<style scoped>
|
|
269
|
+
/*
|
|
270
|
+
* Only keep styles here that REQUIRE scoping (:deep, dynamic binding, component-specific overrides).
|
|
271
|
+
* Generic/reusable styles belong in src/styles/ partials (see _forms.scss, _cards.scss, etc.).
|
|
272
|
+
*/
|
|
273
|
+
.list-layout {
|
|
274
|
+
display: flex;
|
|
275
|
+
flex-direction: column;
|
|
276
|
+
gap: 1rem;
|
|
277
|
+
}
|
|
278
|
+
</style>
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* PageHeader - Reusable page header with title and actions
|
|
4
|
+
*
|
|
5
|
+
* Props:
|
|
6
|
+
* - title: Page title (simple string) OR
|
|
7
|
+
* - titleParts: { action, entityName, entityLabel } for decorated rendering
|
|
8
|
+
* - badges: Custom badges from entity manager [{ label, severity }]
|
|
9
|
+
*
|
|
10
|
+
* Title rendering:
|
|
11
|
+
* - Simple: "Edit Agent" → <h1>Edit Agent</h1>
|
|
12
|
+
* - Decorated: { action: 'Edit', entityName: 'Agent', entityLabel: 'David' }
|
|
13
|
+
* → <h1>Edit Agent: <span class="entity-label">David</span></h1>
|
|
14
|
+
*
|
|
15
|
+
* Slots:
|
|
16
|
+
* - subtitle: Optional content next to title
|
|
17
|
+
* - actions: Action buttons on the right
|
|
18
|
+
*
|
|
19
|
+
* Note: Breadcrumb is handled globally by AppLayout.
|
|
20
|
+
*/
|
|
21
|
+
import { computed, inject, type Ref } from 'vue'
|
|
22
|
+
import Tag from 'primevue/tag'
|
|
23
|
+
|
|
24
|
+
interface TitleParts {
|
|
25
|
+
simple?: string
|
|
26
|
+
action?: string
|
|
27
|
+
entityName?: string
|
|
28
|
+
entityLabel?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface BadgeConfig {
|
|
32
|
+
label: string
|
|
33
|
+
severity?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface Props {
|
|
37
|
+
title?: string | null
|
|
38
|
+
titleParts?: TitleParts | null
|
|
39
|
+
subtitle?: string | null
|
|
40
|
+
badges?: BadgeConfig[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
44
|
+
title: null,
|
|
45
|
+
titleParts: null,
|
|
46
|
+
subtitle: null,
|
|
47
|
+
badges: () => []
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// Auto-injected title from useForm (if available)
|
|
51
|
+
const injectedTitleParts = inject<Ref<TitleParts | null> | null>('qdadmPageTitleParts', null)
|
|
52
|
+
|
|
53
|
+
// Use props first, then injected, then fallback
|
|
54
|
+
const effectiveTitleParts = computed((): TitleParts | null => {
|
|
55
|
+
return props.titleParts || injectedTitleParts?.value || null
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Simple title from usePageTitle('My Title')
|
|
59
|
+
const simpleTitle = computed((): string | undefined => effectiveTitleParts.value?.simple)
|
|
60
|
+
|
|
61
|
+
// Compute title display
|
|
62
|
+
const hasDecoratedTitle = computed((): boolean => {
|
|
63
|
+
return !!effectiveTitleParts.value?.entityLabel
|
|
64
|
+
})
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<template>
|
|
68
|
+
<div class="page-header">
|
|
69
|
+
<div class="page-header-content">
|
|
70
|
+
<div class="page-header-row">
|
|
71
|
+
<div class="page-header-left">
|
|
72
|
+
<div>
|
|
73
|
+
<h1 class="page-title">
|
|
74
|
+
<template v-if="simpleTitle">{{ simpleTitle }}</template>
|
|
75
|
+
<template v-else-if="effectiveTitleParts">
|
|
76
|
+
<span v-if="hasDecoratedTitle" class="entity-label">{{ effectiveTitleParts.entityLabel }}</span>
|
|
77
|
+
<span v-else>{{ effectiveTitleParts.action }} {{ effectiveTitleParts.entityName }}</span>
|
|
78
|
+
</template>
|
|
79
|
+
<Tag
|
|
80
|
+
v-for="badge in badges"
|
|
81
|
+
:key="badge.label"
|
|
82
|
+
:value="badge.label"
|
|
83
|
+
:severity="(badge.severity as any) || 'secondary'"
|
|
84
|
+
class="entity-badge"
|
|
85
|
+
/>
|
|
86
|
+
<template v-if="!effectiveTitleParts && !simpleTitle">{{ title }}</template>
|
|
87
|
+
</h1>
|
|
88
|
+
<p v-if="subtitle" class="page-subtitle">{{ subtitle }}</p>
|
|
89
|
+
</div>
|
|
90
|
+
<slot name="subtitle" ></slot>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="header-actions">
|
|
93
|
+
<slot name="actions" ></slot>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</template>
|
|
99
|
+
|
|
100
|
+
<style scoped>
|
|
101
|
+
/*
|
|
102
|
+
* Only keep styles here that REQUIRE scoping (:deep, dynamic binding, component-specific overrides).
|
|
103
|
+
* Generic/reusable styles belong in src/styles/ partials (see _forms.scss, _cards.scss, etc.).
|
|
104
|
+
*/
|
|
105
|
+
.page-header-content {
|
|
106
|
+
display: flex;
|
|
107
|
+
flex-direction: column;
|
|
108
|
+
gap: 0.25rem;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.page-header-row {
|
|
112
|
+
display: flex;
|
|
113
|
+
justify-content: space-between;
|
|
114
|
+
align-items: center;
|
|
115
|
+
min-height: 2.5rem; /* Consistent height with or without action buttons */
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.page-header-left {
|
|
119
|
+
display: flex;
|
|
120
|
+
align-items: center;
|
|
121
|
+
gap: 1rem;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.page-subtitle {
|
|
125
|
+
margin: 0.25rem 0 0 0;
|
|
126
|
+
font-size: 0.875rem;
|
|
127
|
+
color: var(--p-text-secondary);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* Action badge (View Bot / Edit Country) - small grey badge, before title */
|
|
131
|
+
.action-badge {
|
|
132
|
+
display: inline-block;
|
|
133
|
+
font-size: 0.5em;
|
|
134
|
+
font-weight: 600;
|
|
135
|
+
background: var(--p-surface-200);
|
|
136
|
+
color: var(--p-surface-600);
|
|
137
|
+
padding: 0.25em 0.6em;
|
|
138
|
+
border-radius: 4px;
|
|
139
|
+
margin-right: 0.5em;
|
|
140
|
+
vertical-align: middle;
|
|
141
|
+
text-transform: uppercase;
|
|
142
|
+
letter-spacing: 0.03em;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Entity badges from manager callback */
|
|
146
|
+
.entity-badge {
|
|
147
|
+
vertical-align: middle;
|
|
148
|
+
margin-left: 0.4em;
|
|
149
|
+
font-size: 0.5em;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* Entity label in title - main focus */
|
|
153
|
+
.entity-label {
|
|
154
|
+
font-weight: 600;
|
|
155
|
+
}
|
|
156
|
+
</style>
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* PageLayout - Base layout for dashboard pages
|
|
4
|
+
*
|
|
5
|
+
* Provides:
|
|
6
|
+
* - PageHeader with title and actions
|
|
7
|
+
* - CardsGrid zone (optional)
|
|
8
|
+
* - Main content slot
|
|
9
|
+
*
|
|
10
|
+
* Slots:
|
|
11
|
+
* - #nav: For PageNav component (provides breadcrumb data to AppLayout)
|
|
12
|
+
* - #header-actions: Custom header action buttons
|
|
13
|
+
* - default: Main content
|
|
14
|
+
*
|
|
15
|
+
* Note: Breadcrumb is handled globally by AppLayout.
|
|
16
|
+
* Use PageNav in #nav slot to customize breadcrumb for child routes.
|
|
17
|
+
*/
|
|
18
|
+
import PageHeader from './PageHeader.vue'
|
|
19
|
+
import CardsGrid from '../display/CardsGrid.vue'
|
|
20
|
+
import Button from 'primevue/button'
|
|
21
|
+
|
|
22
|
+
interface TitleParts {
|
|
23
|
+
action?: string
|
|
24
|
+
entityName?: string
|
|
25
|
+
entityLabel?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface HeaderAction {
|
|
29
|
+
name: string
|
|
30
|
+
label: string | (() => string)
|
|
31
|
+
icon?: string
|
|
32
|
+
severity?: string
|
|
33
|
+
isLoading?: boolean
|
|
34
|
+
onClick: () => void
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type Severity = 'success' | 'danger' | 'warning' | 'info'
|
|
38
|
+
|
|
39
|
+
interface Card {
|
|
40
|
+
name: string
|
|
41
|
+
value?: string | number
|
|
42
|
+
label?: string
|
|
43
|
+
severity?: Severity
|
|
44
|
+
icon?: string
|
|
45
|
+
custom?: boolean
|
|
46
|
+
class?: string
|
|
47
|
+
onClick?: () => void
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface Props {
|
|
51
|
+
/** Page title (use title OR titleParts) */
|
|
52
|
+
title?: string
|
|
53
|
+
/** Title parts for decorated entity label */
|
|
54
|
+
titleParts?: TitleParts
|
|
55
|
+
/** Page subtitle */
|
|
56
|
+
subtitle?: string
|
|
57
|
+
/** Header action buttons */
|
|
58
|
+
headerActions?: HeaderAction[]
|
|
59
|
+
/** Cards configuration */
|
|
60
|
+
cards?: Card[]
|
|
61
|
+
/** Number of card columns or 'auto' */
|
|
62
|
+
cardsColumns?: number | 'auto'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
withDefaults(defineProps<Props>(), {
|
|
66
|
+
headerActions: () => [],
|
|
67
|
+
cards: () => [],
|
|
68
|
+
cardsColumns: 'auto'
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
function resolveLabel(label: string | (() => string)): string {
|
|
72
|
+
return typeof label === 'function' ? label() : label
|
|
73
|
+
}
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<template>
|
|
77
|
+
<div>
|
|
78
|
+
<!-- Nav slot for PageNav (provides data to AppLayout, renders nothing visible) -->
|
|
79
|
+
<slot name="nav" />
|
|
80
|
+
|
|
81
|
+
<PageHeader
|
|
82
|
+
:title="title"
|
|
83
|
+
:title-parts="titleParts"
|
|
84
|
+
>
|
|
85
|
+
<template #subtitle>
|
|
86
|
+
<slot name="subtitle">
|
|
87
|
+
<span v-if="subtitle" class="page-subtitle">{{ subtitle }}</span>
|
|
88
|
+
</slot>
|
|
89
|
+
</template>
|
|
90
|
+
<template #actions>
|
|
91
|
+
<slot name="header-actions" ></slot>
|
|
92
|
+
<Button
|
|
93
|
+
v-for="action in headerActions"
|
|
94
|
+
:key="action.name"
|
|
95
|
+
:label="resolveLabel(action.label)"
|
|
96
|
+
:icon="action.icon"
|
|
97
|
+
:severity="action.severity"
|
|
98
|
+
:loading="action.isLoading"
|
|
99
|
+
@click="action.onClick"
|
|
100
|
+
/>
|
|
101
|
+
</template>
|
|
102
|
+
</PageHeader>
|
|
103
|
+
|
|
104
|
+
<!-- Cards Zone -->
|
|
105
|
+
<CardsGrid v-if="cards.length > 0" :cards="cards" :columns="cardsColumns">
|
|
106
|
+
<template v-for="(_, slotName) in $slots" :key="slotName" #[slotName]="slotProps">
|
|
107
|
+
<slot :name="slotName" v-bind="slotProps" ></slot>
|
|
108
|
+
</template>
|
|
109
|
+
</CardsGrid>
|
|
110
|
+
|
|
111
|
+
<!-- Main Content -->
|
|
112
|
+
<slot ></slot>
|
|
113
|
+
</div>
|
|
114
|
+
</template>
|
|
115
|
+
|
|
116
|
+
<style scoped>
|
|
117
|
+
/*
|
|
118
|
+
* Only keep styles here that REQUIRE scoping (:deep, dynamic binding, component-specific overrides).
|
|
119
|
+
* Generic/reusable styles belong in src/styles/ partials (see _forms.scss, _cards.scss, etc.).
|
|
120
|
+
*/
|
|
121
|
+
.page-subtitle {
|
|
122
|
+
color: var(--p-surface-500);
|
|
123
|
+
font-size: 0.9rem;
|
|
124
|
+
margin-left: 1rem;
|
|
125
|
+
}
|
|
126
|
+
</style>
|