@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,67 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* DefaultFormActions - Default component for the form actions zone
|
|
4
|
+
*
|
|
5
|
+
* Renders save/cancel buttons with loading states, using the form state
|
|
6
|
+
* provided by FormLayout via Vue injection.
|
|
7
|
+
*
|
|
8
|
+
* This component is rendered by Zone when no blocks are registered
|
|
9
|
+
* in the 'actions' zone and no slot override is provided.
|
|
10
|
+
*
|
|
11
|
+
* Uses FormActions component for consistent button rendering.
|
|
12
|
+
*
|
|
13
|
+
* Inject:
|
|
14
|
+
* - qdadmFormState: { loading, saving, dirty, isEdit }
|
|
15
|
+
* - qdadmFormEmit: { save, saveAndClose, cancel, delete }
|
|
16
|
+
*/
|
|
17
|
+
import { inject, computed, type Ref } from 'vue'
|
|
18
|
+
import FormActions from '../../edit/FormActions.vue'
|
|
19
|
+
|
|
20
|
+
interface FormState {
|
|
21
|
+
isEdit?: boolean
|
|
22
|
+
saving?: boolean
|
|
23
|
+
dirty?: boolean
|
|
24
|
+
loading?: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface FormEmit {
|
|
28
|
+
save: () => void
|
|
29
|
+
saveAndClose: () => void
|
|
30
|
+
cancel: () => void
|
|
31
|
+
delete?: () => void
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Inject form state from FormLayout
|
|
35
|
+
const formState = inject<Ref<FormState> | null>('qdadmFormState', null)
|
|
36
|
+
const formEmit = inject<FormEmit | null>('qdadmFormEmit', null)
|
|
37
|
+
|
|
38
|
+
// Fallback values if not injected (for standalone testing)
|
|
39
|
+
const isEdit = computed(() => formState?.value?.isEdit ?? false)
|
|
40
|
+
const saving = computed(() => formState?.value?.saving ?? false)
|
|
41
|
+
const dirty = computed(() => formState?.value?.dirty ?? true)
|
|
42
|
+
|
|
43
|
+
// Event handlers
|
|
44
|
+
function onSave(): void {
|
|
45
|
+
formEmit?.save()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function onSaveAndClose(): void {
|
|
49
|
+
formEmit?.saveAndClose()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function onCancel(): void {
|
|
53
|
+
formEmit?.cancel()
|
|
54
|
+
}
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<template>
|
|
58
|
+
<FormActions
|
|
59
|
+
:isEdit="isEdit"
|
|
60
|
+
:saving="saving"
|
|
61
|
+
:dirty="dirty"
|
|
62
|
+
:showSaveAndClose="true"
|
|
63
|
+
@save="onSave"
|
|
64
|
+
@saveAndClose="onSaveAndClose"
|
|
65
|
+
@cancel="onCancel"
|
|
66
|
+
/>
|
|
67
|
+
</template>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* DefaultHeader - Default header component for BaseLayout
|
|
4
|
+
*
|
|
5
|
+
* Displays app branding (logo or name) and version badge.
|
|
6
|
+
* Extracted from AppLayout for zone-based architecture.
|
|
7
|
+
*
|
|
8
|
+
* This is the default component rendered in the "header" zone
|
|
9
|
+
* when no blocks are registered.
|
|
10
|
+
*/
|
|
11
|
+
import { inject } from 'vue'
|
|
12
|
+
|
|
13
|
+
interface AppConfig {
|
|
14
|
+
name: string
|
|
15
|
+
shortName: string
|
|
16
|
+
version: string | null
|
|
17
|
+
logo: string | null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// App config from useApp (injected by createQdadm plugin)
|
|
21
|
+
const app = inject<AppConfig>('qdadmApp', {
|
|
22
|
+
name: 'Admin',
|
|
23
|
+
shortName: 'adm',
|
|
24
|
+
version: null,
|
|
25
|
+
logo: null
|
|
26
|
+
})
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<div class="default-header">
|
|
31
|
+
<div class="header-branding">
|
|
32
|
+
<img v-if="app.logo" :src="app.logo" :alt="app.name" class="header-logo" />
|
|
33
|
+
<h1 v-else class="header-title">{{ app.name }}</h1>
|
|
34
|
+
</div>
|
|
35
|
+
<span v-if="app.version" class="header-version">v{{ app.version }}</span>
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<style scoped>
|
|
40
|
+
/*
|
|
41
|
+
* Only keep styles here that REQUIRE scoping (:deep, dynamic binding, component-specific overrides).
|
|
42
|
+
* Generic/reusable styles belong in src/styles/ partials (see _forms.scss, _cards.scss, etc.).
|
|
43
|
+
*/
|
|
44
|
+
.default-header {
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
align-items: flex-start;
|
|
48
|
+
gap: 0.5rem;
|
|
49
|
+
padding: 1.5rem;
|
|
50
|
+
border-bottom: 1px solid var(--p-surface-700, #334155);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.header-branding {
|
|
54
|
+
display: flex;
|
|
55
|
+
align-items: center;
|
|
56
|
+
gap: 0.5rem;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.header-title {
|
|
60
|
+
font-size: 1.25rem;
|
|
61
|
+
font-weight: 600;
|
|
62
|
+
margin: 0;
|
|
63
|
+
color: var(--p-surface-0, white);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.header-logo {
|
|
67
|
+
max-height: 32px;
|
|
68
|
+
max-width: 160px;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.header-version {
|
|
72
|
+
font-size: 0.625rem;
|
|
73
|
+
color: var(--p-surface-400, #94a3b8);
|
|
74
|
+
background: var(--p-surface-700, #334155);
|
|
75
|
+
padding: 0.125rem 0.375rem;
|
|
76
|
+
border-radius: 0.25rem;
|
|
77
|
+
}
|
|
78
|
+
</style>
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* DefaultMenu - Default navigation menu for BaseLayout
|
|
4
|
+
*
|
|
5
|
+
* Displays sidebar navigation with collapsible sections.
|
|
6
|
+
* Navigation items are auto-built from moduleRegistry.
|
|
7
|
+
* Extracted from AppLayout for zone-based architecture.
|
|
8
|
+
*
|
|
9
|
+
* This is the default component rendered in the "menu" zone
|
|
10
|
+
* when no blocks are registered.
|
|
11
|
+
*/
|
|
12
|
+
import { ref, watch, onMounted, computed, inject } from 'vue'
|
|
13
|
+
import { RouterLink, useRoute } from 'vue-router'
|
|
14
|
+
import { useNavigation, type NavSection } from '../../../composables/useNavigation'
|
|
15
|
+
|
|
16
|
+
interface AppConfig {
|
|
17
|
+
shortName: string
|
|
18
|
+
[key: string]: unknown
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const route = useRoute()
|
|
22
|
+
const { navSections, isNavActive, sectionHasActiveItem, handleNavClick } = useNavigation()
|
|
23
|
+
|
|
24
|
+
// App config for storage key namespacing
|
|
25
|
+
const app = inject<AppConfig>('qdadmApp', { shortName: 'qdadm' })
|
|
26
|
+
|
|
27
|
+
// LocalStorage key for collapsed sections state
|
|
28
|
+
const STORAGE_KEY = computed<string>(() => `${app.shortName.toLowerCase()}_nav_collapsed`)
|
|
29
|
+
|
|
30
|
+
// Collapsed sections state (section title -> boolean)
|
|
31
|
+
const collapsedSections = ref<Record<string, boolean>>({})
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Load collapsed state from localStorage
|
|
35
|
+
*/
|
|
36
|
+
function loadCollapsedState(): void {
|
|
37
|
+
try {
|
|
38
|
+
const stored = localStorage.getItem(STORAGE_KEY.value)
|
|
39
|
+
if (stored) {
|
|
40
|
+
collapsedSections.value = JSON.parse(stored) as Record<string, boolean>
|
|
41
|
+
}
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.warn('Failed to load nav state:', e)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Save collapsed state to localStorage
|
|
49
|
+
*/
|
|
50
|
+
function saveCollapsedState(): void {
|
|
51
|
+
try {
|
|
52
|
+
localStorage.setItem(STORAGE_KEY.value, JSON.stringify(collapsedSections.value))
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.warn('Failed to save nav state:', e)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Toggle section collapsed state
|
|
60
|
+
*/
|
|
61
|
+
function toggleSection(sectionTitle: string): void {
|
|
62
|
+
collapsedSections.value[sectionTitle] = !collapsedSections.value[sectionTitle]
|
|
63
|
+
saveCollapsedState()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if section should be shown expanded
|
|
68
|
+
* - Never collapsed if it contains active item
|
|
69
|
+
* - Otherwise respect user preference
|
|
70
|
+
*/
|
|
71
|
+
function isSectionExpanded(section: NavSection): boolean {
|
|
72
|
+
if (sectionHasActiveItem(section)) {
|
|
73
|
+
return true
|
|
74
|
+
}
|
|
75
|
+
const title = section.title || section.label || ''
|
|
76
|
+
return !collapsedSections.value[title]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Load state on mount
|
|
80
|
+
onMounted(() => {
|
|
81
|
+
loadCollapsedState()
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
// Auto-expand section when navigating to an item in it
|
|
85
|
+
watch(() => route?.path, () => {
|
|
86
|
+
if (!route) return
|
|
87
|
+
for (const section of navSections.value) {
|
|
88
|
+
const title = section.title || section.label || ''
|
|
89
|
+
if (sectionHasActiveItem(section) && collapsedSections.value[title]) {
|
|
90
|
+
// Auto-expand but don't save (user can collapse again if they want)
|
|
91
|
+
collapsedSections.value[title] = false
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<template>
|
|
98
|
+
<!-- Use sidebar-nav class to inherit global nav-item styles from _sidebar.scss -->
|
|
99
|
+
<nav class="sidebar-nav">
|
|
100
|
+
<div v-for="section in navSections" :key="section.title || section.label" class="nav-section">
|
|
101
|
+
<div
|
|
102
|
+
class="nav-section-title"
|
|
103
|
+
:class="{ 'nav-section-active': sectionHasActiveItem(section) }"
|
|
104
|
+
@click="toggleSection(section.title || section.label || '')"
|
|
105
|
+
>
|
|
106
|
+
<span>{{ section.title }}</span>
|
|
107
|
+
<i
|
|
108
|
+
class="nav-section-chevron pi"
|
|
109
|
+
:class="isSectionExpanded(section) ? 'pi-chevron-down' : 'pi-chevron-right'"
|
|
110
|
+
></i>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="nav-section-items" :class="{ 'nav-section-collapsed': !isSectionExpanded(section) }">
|
|
113
|
+
<RouterLink
|
|
114
|
+
v-for="item in section.items"
|
|
115
|
+
:key="item.route"
|
|
116
|
+
:to="{ name: item.route }"
|
|
117
|
+
class="nav-item"
|
|
118
|
+
:class="{ 'nav-item-active': isNavActive(item) }"
|
|
119
|
+
@click="handleNavClick($event, item)"
|
|
120
|
+
>
|
|
121
|
+
<i :class="item.icon"></i>
|
|
122
|
+
<span>{{ item.label }}</span>
|
|
123
|
+
</RouterLink>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</nav>
|
|
127
|
+
</template>
|
|
128
|
+
|
|
129
|
+
<style scoped>
|
|
130
|
+
/*
|
|
131
|
+
* Only keep styles here that REQUIRE scoping (:deep, dynamic binding, component-specific overrides).
|
|
132
|
+
* Generic/reusable styles belong in src/styles/ partials (see _forms.scss, _cards.scss, etc.).
|
|
133
|
+
*/
|
|
134
|
+
/* DefaultMenu uses sidebar-nav class to inherit global nav-item styles from _sidebar.scss
|
|
135
|
+
Only component-specific styles remain here */
|
|
136
|
+
|
|
137
|
+
.sidebar-nav {
|
|
138
|
+
flex: 1;
|
|
139
|
+
overflow-y: auto;
|
|
140
|
+
padding: 1rem 0;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.nav-section {
|
|
144
|
+
margin-bottom: 0.5rem;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.nav-section-title {
|
|
148
|
+
padding: 0.5rem 1.5rem;
|
|
149
|
+
font-size: 0.75rem;
|
|
150
|
+
font-weight: 600;
|
|
151
|
+
text-transform: uppercase;
|
|
152
|
+
letter-spacing: 0.05em;
|
|
153
|
+
color: var(--p-surface-400, #94a3b8);
|
|
154
|
+
display: flex;
|
|
155
|
+
justify-content: space-between;
|
|
156
|
+
align-items: center;
|
|
157
|
+
cursor: pointer;
|
|
158
|
+
user-select: none;
|
|
159
|
+
transition: color 0.15s;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.nav-section-title:hover {
|
|
163
|
+
color: var(--p-surface-200, #e2e8f0);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.nav-section-active {
|
|
167
|
+
color: var(--p-primary-400, #60a5fa);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.nav-section-chevron {
|
|
171
|
+
font-size: 0.625rem;
|
|
172
|
+
transition: transform 0.2s;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.nav-section-items {
|
|
176
|
+
max-height: 500px;
|
|
177
|
+
overflow: hidden;
|
|
178
|
+
transition: max-height 0.2s ease-out, opacity 0.15s;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.nav-section-collapsed {
|
|
182
|
+
max-height: 0;
|
|
183
|
+
opacity: 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/* Nav item styles are in _sidebar.scss (global styles)
|
|
187
|
+
See .sidebar-nav .nav-item for base styles */
|
|
188
|
+
</style>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* DefaultPagination - Default pagination component for ListLayout
|
|
4
|
+
*
|
|
5
|
+
* Renders a PrimeVue Paginator with standard configuration.
|
|
6
|
+
* Used as the default for the "pagination" zone in ListLayout.
|
|
7
|
+
*
|
|
8
|
+
* When `lazy` is false and pagination is handled by DataTable,
|
|
9
|
+
* this component is typically not shown (DataTable has its own paginator).
|
|
10
|
+
*
|
|
11
|
+
* This is useful for:
|
|
12
|
+
* - Separate pagination controls outside the table
|
|
13
|
+
* - Custom pagination layouts
|
|
14
|
+
* - Server-side (lazy) pagination with separate paginator
|
|
15
|
+
*/
|
|
16
|
+
import Paginator from 'primevue/paginator'
|
|
17
|
+
|
|
18
|
+
interface PageEvent {
|
|
19
|
+
first: number
|
|
20
|
+
rows: number
|
|
21
|
+
page: number
|
|
22
|
+
pageCount?: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface Props {
|
|
26
|
+
/** First record index (0-based) */
|
|
27
|
+
first?: number
|
|
28
|
+
/** Number of rows per page */
|
|
29
|
+
rows?: number
|
|
30
|
+
/** Total number of records */
|
|
31
|
+
totalRecords?: number
|
|
32
|
+
/** Options for rows per page dropdown */
|
|
33
|
+
rowsPerPageOptions?: number[]
|
|
34
|
+
/** Template for the paginator layout */
|
|
35
|
+
template?: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
withDefaults(defineProps<Props>(), {
|
|
39
|
+
first: 0,
|
|
40
|
+
rows: 10,
|
|
41
|
+
totalRecords: 0,
|
|
42
|
+
rowsPerPageOptions: () => [10, 25, 50, 100],
|
|
43
|
+
template: 'FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown'
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const emit = defineEmits<{
|
|
47
|
+
page: [event: PageEvent]
|
|
48
|
+
}>()
|
|
49
|
+
|
|
50
|
+
function onPage(event: PageEvent): void {
|
|
51
|
+
emit('page', event)
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template>
|
|
56
|
+
<Paginator
|
|
57
|
+
:first="first"
|
|
58
|
+
:rows="rows"
|
|
59
|
+
:totalRecords="totalRecords"
|
|
60
|
+
:rowsPerPageOptions="rowsPerPageOptions"
|
|
61
|
+
:template="template"
|
|
62
|
+
@page="onPage"
|
|
63
|
+
class="default-pagination"
|
|
64
|
+
/>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<style scoped>
|
|
68
|
+
/*
|
|
69
|
+
* Only keep styles here that REQUIRE scoping (:deep, dynamic binding, component-specific overrides).
|
|
70
|
+
* Generic/reusable styles belong in src/styles/ partials (see _forms.scss, _cards.scss, etc.).
|
|
71
|
+
*/
|
|
72
|
+
.default-pagination {
|
|
73
|
+
margin-top: 1rem;
|
|
74
|
+
}
|
|
75
|
+
</style>
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* DefaultTable - Default table component for ListLayout
|
|
4
|
+
*
|
|
5
|
+
* Renders a PrimeVue DataTable with standard configuration.
|
|
6
|
+
* Used as the default for the "table" zone in ListLayout.
|
|
7
|
+
*
|
|
8
|
+
* Accepts props from the list page builder for data binding.
|
|
9
|
+
* Supports slots for column definitions.
|
|
10
|
+
*
|
|
11
|
+
* This is a minimal wrapper that expects:
|
|
12
|
+
* - items: Array of data to display
|
|
13
|
+
* - loading: Boolean loading state
|
|
14
|
+
* - Columns via default slot
|
|
15
|
+
*/
|
|
16
|
+
import { computed, useSlots, type PropType } from 'vue'
|
|
17
|
+
import DataTable, { type DataTableSortEvent } from 'primevue/datatable'
|
|
18
|
+
import Column from 'primevue/column'
|
|
19
|
+
|
|
20
|
+
defineProps({
|
|
21
|
+
/**
|
|
22
|
+
* Data items to display in the table
|
|
23
|
+
*/
|
|
24
|
+
items: {
|
|
25
|
+
type: Array as PropType<unknown[]>,
|
|
26
|
+
default: () => []
|
|
27
|
+
},
|
|
28
|
+
/**
|
|
29
|
+
* Loading state
|
|
30
|
+
*/
|
|
31
|
+
loading: {
|
|
32
|
+
type: Boolean,
|
|
33
|
+
default: false
|
|
34
|
+
},
|
|
35
|
+
/**
|
|
36
|
+
* Field to use as unique key for rows
|
|
37
|
+
*/
|
|
38
|
+
dataKey: {
|
|
39
|
+
type: String,
|
|
40
|
+
default: 'id'
|
|
41
|
+
},
|
|
42
|
+
/**
|
|
43
|
+
* Current selection (for selectable tables)
|
|
44
|
+
*/
|
|
45
|
+
selection: {
|
|
46
|
+
type: Array as PropType<unknown[] | undefined>,
|
|
47
|
+
default: undefined
|
|
48
|
+
},
|
|
49
|
+
/**
|
|
50
|
+
* Enable row selection
|
|
51
|
+
*/
|
|
52
|
+
selectable: {
|
|
53
|
+
type: Boolean,
|
|
54
|
+
default: false
|
|
55
|
+
},
|
|
56
|
+
/**
|
|
57
|
+
* Enable striped rows
|
|
58
|
+
*/
|
|
59
|
+
stripedRows: {
|
|
60
|
+
type: Boolean,
|
|
61
|
+
default: true
|
|
62
|
+
},
|
|
63
|
+
/**
|
|
64
|
+
* Enable removable sort
|
|
65
|
+
*/
|
|
66
|
+
removableSort: {
|
|
67
|
+
type: Boolean,
|
|
68
|
+
default: true
|
|
69
|
+
},
|
|
70
|
+
/**
|
|
71
|
+
* Current sort field
|
|
72
|
+
*/
|
|
73
|
+
sortField: {
|
|
74
|
+
type: String as PropType<string | undefined>,
|
|
75
|
+
default: undefined
|
|
76
|
+
},
|
|
77
|
+
/**
|
|
78
|
+
* Current sort order (1 = asc, -1 = desc)
|
|
79
|
+
*/
|
|
80
|
+
sortOrder: {
|
|
81
|
+
type: Number as PropType<1 | -1>,
|
|
82
|
+
default: 1
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const emit = defineEmits<{
|
|
87
|
+
'update:selection': [value: unknown[]]
|
|
88
|
+
'sort': [event: DataTableSortEvent]
|
|
89
|
+
}>()
|
|
90
|
+
|
|
91
|
+
const slots = useSlots()
|
|
92
|
+
const hasDefaultSlot = computed<boolean>(() => !!slots.default)
|
|
93
|
+
|
|
94
|
+
function onSelectionChange(value: unknown[]): void {
|
|
95
|
+
emit('update:selection', value)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function onSort(event: DataTableSortEvent): void {
|
|
99
|
+
emit('sort', event)
|
|
100
|
+
}
|
|
101
|
+
</script>
|
|
102
|
+
|
|
103
|
+
<template>
|
|
104
|
+
<DataTable
|
|
105
|
+
:value="items"
|
|
106
|
+
:loading="loading"
|
|
107
|
+
:dataKey="dataKey"
|
|
108
|
+
:selection="selection"
|
|
109
|
+
:sortField="sortField"
|
|
110
|
+
:sortOrder="sortOrder"
|
|
111
|
+
:stripedRows="stripedRows"
|
|
112
|
+
:removableSort="removableSort"
|
|
113
|
+
@update:selection="onSelectionChange"
|
|
114
|
+
@sort="onSort"
|
|
115
|
+
class="default-table"
|
|
116
|
+
>
|
|
117
|
+
<!-- Columns from slot -->
|
|
118
|
+
<slot />
|
|
119
|
+
|
|
120
|
+
<!-- Selection column if selectable -->
|
|
121
|
+
<Column
|
|
122
|
+
v-if="selectable && !hasDefaultSlot"
|
|
123
|
+
selectionMode="multiple"
|
|
124
|
+
headerStyle="width: 3rem"
|
|
125
|
+
/>
|
|
126
|
+
</DataTable>
|
|
127
|
+
</template>
|
|
128
|
+
|
|
129
|
+
<style scoped>
|
|
130
|
+
/*
|
|
131
|
+
* Only keep styles here that REQUIRE scoping (:deep, dynamic binding, component-specific overrides).
|
|
132
|
+
* Generic/reusable styles belong in src/styles/ partials (see _forms.scss, _cards.scss, etc.).
|
|
133
|
+
*/
|
|
134
|
+
.default-table {
|
|
135
|
+
width: 100%;
|
|
136
|
+
}
|
|
137
|
+
</style>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* DefaultUserInfo - Default user info component for sidebar
|
|
4
|
+
*
|
|
5
|
+
* Displays user avatar, name, role, and logout button.
|
|
6
|
+
* Extracted from AppLayout for zone-based architecture.
|
|
7
|
+
*
|
|
8
|
+
* This component is used when auth is enabled.
|
|
9
|
+
*/
|
|
10
|
+
import { computed, type ComputedRef } from 'vue'
|
|
11
|
+
import { useRouter } from 'vue-router'
|
|
12
|
+
import { useAuth } from '../../../composables/useAuth'
|
|
13
|
+
import Button from 'primevue/button'
|
|
14
|
+
|
|
15
|
+
interface UserData {
|
|
16
|
+
username?: string
|
|
17
|
+
name?: string
|
|
18
|
+
email?: string
|
|
19
|
+
role?: string
|
|
20
|
+
[key: string]: unknown
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const router = useRouter()
|
|
24
|
+
const { user, logout, authEnabled } = useAuth()
|
|
25
|
+
|
|
26
|
+
// Cast user to expected shape (useAuth returns unknown)
|
|
27
|
+
const userData = computed<UserData | null>(() => user.value as UserData | null)
|
|
28
|
+
|
|
29
|
+
const userInitials: ComputedRef<string> = computed(() => {
|
|
30
|
+
const username = userData.value?.username
|
|
31
|
+
if (!username) return '?'
|
|
32
|
+
return username.substring(0, 2).toUpperCase()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const userDisplayName: ComputedRef<string> = computed(() => {
|
|
36
|
+
return userData.value?.username || userData.value?.name || 'User'
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const userSubtitle: ComputedRef<string> = computed(() => {
|
|
40
|
+
return userData.value?.email || userData.value?.role || ''
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
function handleLogout(): void {
|
|
44
|
+
logout()
|
|
45
|
+
router.push({ name: 'login' })
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<template>
|
|
50
|
+
<div v-if="authEnabled" class="default-user-info">
|
|
51
|
+
<div class="user-avatar">{{ userInitials }}</div>
|
|
52
|
+
<div class="user-details">
|
|
53
|
+
<div class="user-name">{{ userDisplayName }}</div>
|
|
54
|
+
<div class="user-role">{{ userSubtitle }}</div>
|
|
55
|
+
</div>
|
|
56
|
+
<Button
|
|
57
|
+
icon="pi pi-sign-out"
|
|
58
|
+
severity="secondary"
|
|
59
|
+
text
|
|
60
|
+
rounded
|
|
61
|
+
@click="handleLogout"
|
|
62
|
+
v-tooltip.top="'Logout'"
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<style scoped>
|
|
68
|
+
/*
|
|
69
|
+
* Only keep styles here that REQUIRE scoping (:deep, dynamic binding, component-specific overrides).
|
|
70
|
+
* Generic/reusable styles belong in src/styles/ partials (see _forms.scss, _cards.scss, etc.).
|
|
71
|
+
*/
|
|
72
|
+
.default-user-info {
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
gap: 0.75rem;
|
|
76
|
+
padding: 1rem 1.5rem;
|
|
77
|
+
border-top: 1px solid var(--p-surface-700, #334155);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.user-avatar {
|
|
81
|
+
width: 36px;
|
|
82
|
+
height: 36px;
|
|
83
|
+
border-radius: 50%;
|
|
84
|
+
background: var(--p-primary-600, #2563eb);
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
justify-content: center;
|
|
88
|
+
font-size: 0.875rem;
|
|
89
|
+
font-weight: 600;
|
|
90
|
+
color: var(--p-surface-0, white);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.user-details {
|
|
94
|
+
flex: 1;
|
|
95
|
+
min-width: 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.user-name {
|
|
99
|
+
font-weight: 500;
|
|
100
|
+
font-size: 0.875rem;
|
|
101
|
+
color: var(--p-surface-0, white);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.user-role {
|
|
105
|
+
font-size: 0.75rem;
|
|
106
|
+
color: var(--p-surface-400, #94a3b8);
|
|
107
|
+
overflow: hidden;
|
|
108
|
+
text-overflow: ellipsis;
|
|
109
|
+
white-space: nowrap;
|
|
110
|
+
}
|
|
111
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default zone components for layouts
|
|
3
|
+
*
|
|
4
|
+
* These components are rendered when no blocks are registered
|
|
5
|
+
* in their respective zones.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// BaseLayout defaults
|
|
9
|
+
export { default as DefaultHeader } from './DefaultHeader.vue'
|
|
10
|
+
export { default as DefaultMenu } from './DefaultMenu.vue'
|
|
11
|
+
export { default as DefaultFooter } from './DefaultFooter.vue'
|
|
12
|
+
export { default as DefaultUserInfo } from './DefaultUserInfo.vue'
|
|
13
|
+
export { default as DefaultBreadcrumb } from './DefaultBreadcrumb.vue'
|
|
14
|
+
|
|
15
|
+
// FormLayout defaults
|
|
16
|
+
export { default as DefaultFormActions } from './DefaultFormActions.vue'
|