@saasicat/ui-vue 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +47 -0
  3. package/dist/index.cjs +3776 -0
  4. package/dist/index.d.cts +2395 -0
  5. package/dist/index.d.ts +2395 -0
  6. package/dist/index.js +3659 -0
  7. package/dist/testing-e2e/admin-pages-suite.cjs +134 -0
  8. package/dist/testing-e2e/admin-pages-suite.d.cts +37 -0
  9. package/dist/testing-e2e/admin-pages-suite.d.ts +37 -0
  10. package/dist/testing-e2e/admin-pages-suite.js +109 -0
  11. package/package.json +87 -0
  12. package/src/action-registry.ts +110 -0
  13. package/src/batch-column-fetcher.ts +152 -0
  14. package/src/boot-loader.ts +60 -0
  15. package/src/components/BundleVersionPublishDialog.vue +359 -0
  16. package/src/components/BusinessTypeVersionEditorDialog.vue +417 -0
  17. package/src/components/BusinessTypeVersionPublishDialog.vue +258 -0
  18. package/src/components/FeatureGate.vue +56 -0
  19. package/src/components/KpiCard.vue +53 -0
  20. package/src/components/KvBlock.vue +36 -0
  21. package/src/components/MarketingPromotionsTab.vue +861 -0
  22. package/src/components/MfaPromptDialog.vue +104 -0
  23. package/src/components/TenantActionConfirmDialog.vue +252 -0
  24. package/src/components/VersionDiffPreview.vue +150 -0
  25. package/src/components/bundle-editor/BundleCreatePanel.vue +681 -0
  26. package/src/components/bundle-editor/BundleFeaturesEditor.vue +215 -0
  27. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +316 -0
  28. package/src/components/bundle-editor/BundleQuotasEditor.vue +219 -0
  29. package/src/components/bundle-editor/BundleStatusBanner.vue +189 -0
  30. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +659 -0
  31. package/src/components/bundle-editor/BundleVersionStrip.vue +207 -0
  32. package/src/components/bundle-editor/bundle-version-status.ts +164 -0
  33. package/src/components/bundle-editor/catalog-i18n.ts +85 -0
  34. package/src/components/dialogs/PilotCreateDialog.vue +780 -0
  35. package/src/components/dialogs/PilotEditDialog.vue +548 -0
  36. package/src/components/dialogs/PromoCodeCreateDialog.vue +737 -0
  37. package/src/components/dialogs/PromoCodeEditDialog.vue +859 -0
  38. package/src/components/dialogs/types.ts +126 -0
  39. package/src/components/plan/PlanCycleToggle.vue +99 -0
  40. package/src/components/plan/PlanGrid.vue +211 -0
  41. package/src/components/plan/PriceSummary.vue +293 -0
  42. package/src/components/plan/PromoCodeInput.vue +192 -0
  43. package/src/components/plan/PublicBundleGrid.vue +287 -0
  44. package/src/components/plan-cockpit/PlanCockpit.vue +993 -0
  45. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +65 -0
  46. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +91 -0
  47. package/src/components/plan-cockpit/PlanCockpitHeader.vue +98 -0
  48. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +99 -0
  49. package/src/components/plan-cockpit/PlanCockpitKpis.vue +57 -0
  50. package/src/components/plan-cockpit/PlanCockpitVersions.vue +226 -0
  51. package/src/components/plan-cockpit/types.ts +72 -0
  52. package/src/components/plan-create-dialog/PlanCreateDialog.vue +508 -0
  53. package/src/components/plan-detail/PlanAuditLog.vue +59 -0
  54. package/src/components/plan-detail/PlanDetail.vue +1195 -0
  55. package/src/components/plan-detail/PlanDetailHeader.vue +185 -0
  56. package/src/components/plan-detail/PlanDetailKpis.vue +57 -0
  57. package/src/components/plan-detail/PlanTerminateDialog.vue +76 -0
  58. package/src/components/plan-detail/PlanVersionDiffPanel.vue +149 -0
  59. package/src/components/plan-detail/PlanVersionsPanel.vue +255 -0
  60. package/src/components/plan-detail/types.ts +59 -0
  61. package/src/components/plan-list/PlanList.vue +1067 -0
  62. package/src/components/plan-matrix/PlanMatrix.vue +1113 -0
  63. package/src/components/plan-review/PlanReview.vue +742 -0
  64. package/src/components/plan-version-editor/PlanCatalogPreview.vue +184 -0
  65. package/src/components/plan-version-editor/PlanComponentPool.vue +272 -0
  66. package/src/components/plan-version-editor/PlanVersionBasket.vue +281 -0
  67. package/src/components/plan-version-editor/PlanVersionDiffDialog.vue +84 -0
  68. package/src/components/plan-version-editor/PlanVersionEditor.vue +1728 -0
  69. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +94 -0
  70. package/src/components/plan-version-editor/types.ts +96 -0
  71. package/src/components/wizard-stepper/WizardStepper.vue +107 -0
  72. package/src/create-admin-routes.ts +68 -0
  73. package/src/create-super-admin-app.ts +345 -0
  74. package/src/entitlement-provider.ts +36 -0
  75. package/src/feature-router-guard.ts +77 -0
  76. package/src/http-json.ts +62 -0
  77. package/src/index.ts +98 -0
  78. package/src/manifest-loader.ts +139 -0
  79. package/src/manifest-store-factory.ts +114 -0
  80. package/src/nav-builder.ts +274 -0
  81. package/src/pages-standard/AdminLayout.vue +411 -0
  82. package/src/pages-standard/AdminManifestErrorPage.vue +79 -0
  83. package/src/pages-standard/AuditPage.vue +223 -0
  84. package/src/pages-standard/BundlesPage.vue +910 -0
  85. package/src/pages-standard/BusinessTypesPage.vue +662 -0
  86. package/src/pages-standard/DashboardPage.vue +595 -0
  87. package/src/pages-standard/DiscoveryPage.vue +663 -0
  88. package/src/pages-standard/EmailHistoryPage.vue +576 -0
  89. package/src/pages-standard/MarketingCatalogPage.vue +1771 -0
  90. package/src/pages-standard/PilotsPage.vue +577 -0
  91. package/src/pages-standard/PlanVersionsPage.vue +216 -0
  92. package/src/pages-standard/PlansPage.vue +1222 -0
  93. package/src/pages-standard/PlatformEmailPage.vue +434 -0
  94. package/src/pages-standard/PromoCodeDetailPage.vue +331 -0
  95. package/src/pages-standard/PromoCodesPage.vue +589 -0
  96. package/src/pages-standard/SubscriptionsPage.vue +159 -0
  97. package/src/pages-standard/SuperAdminLoginPage.vue +299 -0
  98. package/src/pages-standard/SuperAdminSetupWizard.vue +405 -0
  99. package/src/pages-standard/TenantDetailPage.vue +432 -0
  100. package/src/pages-standard/TenantsPage.vue +732 -0
  101. package/src/pages-standard/UsersPage.vue +504 -0
  102. package/src/pages-standard/bundles-page/BundleAccordionList.vue +116 -0
  103. package/src/pages-standard/bundles-page/BundleDetailPanel.vue +193 -0
  104. package/src/pages-standard/bundles-page/BundlesFilterBar.vue +48 -0
  105. package/src/pages-standard/bundles-page/BundlesHeader.vue +67 -0
  106. package/src/pages-standard/bundles-page/BundlesKpis.vue +39 -0
  107. package/src/pages-standard/bundles-page/types.ts +15 -0
  108. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +246 -0
  109. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +171 -0
  110. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +410 -0
  111. package/src/pages-standard/discovery-page/DiscoveryHeader.vue +31 -0
  112. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +47 -0
  113. package/src/pages-standard/discovery-page/DiscoveryMetaBanner.vue +26 -0
  114. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +247 -0
  115. package/src/pages-standard/discovery-page/DiscoveryStatusControl.vue +73 -0
  116. package/src/pages-standard/discovery-page/discovery-ui.ts +166 -0
  117. package/src/pages-standard/email-history.types.ts +48 -0
  118. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +448 -0
  119. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +93 -0
  120. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +138 -0
  121. package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +58 -0
  122. package/src/pages-standard/marketing-catalog/types.ts +43 -0
  123. package/src/pages-standard/plan-versions/PlanDiffCard.vue +132 -0
  124. package/src/pages-standard/plan-versions/PlanVersionHeader.vue +293 -0
  125. package/src/pages-standard/plan-versions/PlanVersionStatusBadge.vue +48 -0
  126. package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +307 -0
  127. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +269 -0
  128. package/src/pages-standard/plan-versions/PlanVersionsKpi.vue +65 -0
  129. package/src/pages-standard/plan-versions/PlanVersionsList.vue +292 -0
  130. package/src/pages-standard/plan-versions/PlanVersionsMatrix.vue +261 -0
  131. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +300 -0
  132. package/src/pages-standard/plan-versions/VersionDiffPreview.vue +167 -0
  133. package/src/pages-standard/plan-versions/format.ts +53 -0
  134. package/src/pages-standard/plan-versions/types.ts +33 -0
  135. package/src/pages-standard/plans-page/PlanArchiveDialog.vue +50 -0
  136. package/src/pages-standard/plans-page/PlanBundleOverview.vue +147 -0
  137. package/src/pages-standard/plans-page/PlanDiscardDraftDialog.vue +46 -0
  138. package/src/pages-standard/plans-page/PlanPublishDialog.vue +106 -0
  139. package/src/pages-standard/plans-page/PlansPageToast.vue +69 -0
  140. package/src/pages-standard/plans-page/types.ts +18 -0
  141. package/src/pages-standard/platform-email.types.ts +41 -0
  142. package/src/pages-standard/sa-theme.css +223 -0
  143. package/src/pages-standard/tenants/StatusPill.vue +62 -0
  144. package/src/pages-standard/tenants/format.ts +38 -0
  145. package/src/pages-tenant/LimitsRow.vue +67 -0
  146. package/src/pages-tenant/MySubscriptionBundlesPage.vue +604 -0
  147. package/src/pages-tenant/OnboardingConfigurator.vue +417 -0
  148. package/src/pages-tenant/PackageSnapshotPanel.vue +241 -0
  149. package/src/pages-tenant/PendingVersionBanner.vue +126 -0
  150. package/src/pages-tenant/PlanChangeWizard.vue +630 -0
  151. package/src/pages-tenant/TenantPlanSection.vue +729 -0
  152. package/src/pages-tenant/UsageBar.vue +107 -0
  153. package/src/pages-tenant/default-i18n.ts +280 -0
  154. package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +304 -0
  155. package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +323 -0
  156. package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +122 -0
  157. package/src/pages-tenant/tenant-plan-section/TenantPlanCardHeader.vue +67 -0
  158. package/src/pages-tenant/tenant-plan-section/TenantUsageGrid.vue +31 -0
  159. package/src/plan-versions-catalog.ts +368 -0
  160. package/src/platform-loaders.ts +68 -0
  161. package/src/project-page-host.ts +108 -0
  162. package/src/testing-e2e/admin-pages-suite.ts +187 -0
  163. package/src/types.ts +58 -0
  164. package/src/use-actions.ts +25 -0
  165. package/src/use-api-list.ts +151 -0
  166. package/src/use-audit-entries.ts +48 -0
  167. package/src/use-batch-columns.ts +59 -0
  168. package/src/use-bulk-publish.ts +150 -0
  169. package/src/use-bundle-versions-map.ts +112 -0
  170. package/src/use-bundles.ts +297 -0
  171. package/src/use-business-types.ts +269 -0
  172. package/src/use-catalog-entries.ts +243 -0
  173. package/src/use-discovery.ts +158 -0
  174. package/src/use-entitlement.ts +87 -0
  175. package/src/use-live-plan-versions.ts +126 -0
  176. package/src/use-manifest.ts +57 -0
  177. package/src/use-marketing-projections.ts +160 -0
  178. package/src/use-nav.ts +33 -0
  179. package/src/use-plan-editor.ts +162 -0
  180. package/src/use-plan-versions.ts +83 -0
  181. package/src/use-plans.ts +375 -0
  182. package/src/use-platform-tenant-actions.ts +286 -0
  183. package/src/use-promotions.ts +126 -0
  184. package/src/use-public-boot.ts +40 -0
  185. package/src/use-subscription-draft.ts +355 -0
  186. package/src/use-super-admin-context.ts +93 -0
  187. package/src/use-tenant-action-flow.ts +238 -0
  188. package/src/use-tenant-billing-catalog.ts +160 -0
  189. package/src/use-tenant-billing.ts +453 -0
  190. package/src/use-tenant-manifest.ts +94 -0
  191. package/src/use-tenant-subscription-bundles.ts +148 -0
  192. package/src/use-tenants.ts +61 -0
  193. package/src/version.ts +10 -0
@@ -0,0 +1,184 @@
1
+ <template>
2
+ <section class="pve-col pve-preview">
3
+ <div class="pve-col-header pve-col-header--preview">
4
+ <div>
5
+ <div class="pve-col-title">Live-Vorschau · Public-Catalog</div>
6
+ <div class="pve-col-sub">So sehen Interessenten den Plan</div>
7
+ </div>
8
+ <div class="pve-preview-toggle">
9
+ <button
10
+ type="button"
11
+ :class="[
12
+ 'pve-btn pve-btn--sm',
13
+ { 'pve-btn--ghost': previewMode !== 'desktop' },
14
+ ]"
15
+ @click="$emit('update:previewMode', 'desktop')"
16
+ >
17
+ Desktop
18
+ </button>
19
+ <button
20
+ type="button"
21
+ :class="['pve-btn pve-btn--sm', { 'pve-btn--ghost': previewMode !== 'mobile' }]"
22
+ @click="$emit('update:previewMode', 'mobile')"
23
+ >
24
+ Mobil
25
+ </button>
26
+ </div>
27
+ </div>
28
+
29
+ <div class="pve-prev-window" :class="`pve-prev-window--${previewMode}`">
30
+ <div class="pve-prev-chrome">
31
+ <span class="pve-prev-dot" style="background: #ef4444" />
32
+ <span class="pve-prev-dot" style="background: #f59e0b" />
33
+ <span class="pve-prev-dot" style="background: #10b981" />
34
+ <div class="pve-prev-url">{{ catalogUrl }}</div>
35
+ </div>
36
+ <div class="pve-prev-body">
37
+ <div class="pve-prev-eyebrow">Unsere Pläne · Vorschau Draft v{{ version }}</div>
38
+ <div class="pve-prev-title">{{ planDisplayName }}</div>
39
+ <div class="pve-prev-desc">
40
+ {{ changeNote || 'Plan-Beschreibung wird aus der Change-Note generiert.' }}
41
+ </div>
42
+ <div class="pve-prev-price">
43
+ <span class="pve-prev-price-big">{{ formattedMonthly }}</span>
44
+ <span class="pve-prev-price-unit">/ Monat</span>
45
+ </div>
46
+ <div v-if="yearlySavingsLabel" class="pve-prev-price-yearly">
47
+ oder {{ formattedYearly }} / Jahr · {{ yearlySavingsLabel }}
48
+ </div>
49
+
50
+ <button type="button" class="pve-prev-cta">Jetzt 30 Tage testen</button>
51
+
52
+ <div class="pve-prev-sep">Enthalten</div>
53
+ <ul class="pve-prev-list">
54
+ <li v-for="row in selectedQuotaList" :key="`pq-${row.quotaKey}`">
55
+ <span class="pve-prev-tick" aria-hidden="true">
56
+ <svg
57
+ width="10"
58
+ height="10"
59
+ viewBox="0 0 24 24"
60
+ fill="none"
61
+ stroke="currentColor"
62
+ stroke-width="3"
63
+ >
64
+ <path d="M5 13l4 4L19 7" />
65
+ </svg>
66
+ </span>
67
+ <b>{{ quotas[row.quotaKey] }}</b>
68
+ {{ (row.unit || row.label).toLowerCase() }}
69
+ </li>
70
+ <li v-for="key in sortedSelectedFeatures" :key="`pf-${key}`">
71
+ <span class="pve-prev-tick" aria-hidden="true">
72
+ <svg
73
+ width="10"
74
+ height="10"
75
+ viewBox="0 0 24 24"
76
+ fill="none"
77
+ stroke="currentColor"
78
+ stroke-width="3"
79
+ >
80
+ <path d="M5 13l4 4L19 7" />
81
+ </svg>
82
+ </span>
83
+ {{ featureLabel(key) }}
84
+ </li>
85
+ <li
86
+ v-if="sortedSelectedFeatures.length === 0 && selectedQuotaList.length === 0"
87
+ class="pve-prev-empty"
88
+ >
89
+ Noch keine Inhalte zugewiesen.
90
+ </li>
91
+ </ul>
92
+
93
+ <div class="pve-prev-foot">
94
+ Marketing-Catalog ·
95
+ <code class="pve-mono pve-mono--xs">{{ planKey }}@v{{ version }}</code>
96
+ <template v-if="changeNote">
97
+ · Change-Note „{{ changeNote }}" wird bei Publish veröffentlicht.
98
+ </template>
99
+ </div>
100
+ </div>
101
+ </div>
102
+
103
+ <div class="pve-prev-validate">
104
+ <div class="pve-prev-validate-head">
105
+ <span class="pve-prev-validate-tick" aria-hidden="true">
106
+ <svg
107
+ width="14"
108
+ height="14"
109
+ viewBox="0 0 24 24"
110
+ fill="none"
111
+ stroke="currentColor"
112
+ stroke-width="3"
113
+ >
114
+ <path d="M5 13l4 4L19 7" />
115
+ </svg>
116
+ </span>
117
+ <span class="pve-prev-validate-title">Publish-Checkliste</span>
118
+ <span class="pve-prev-validate-count"
119
+ >{{ checklistOkCount }} / {{ checklist.length }} ok</span
120
+ >
121
+ </div>
122
+ <div
123
+ v-for="item in checklist"
124
+ :key="item.id"
125
+ :class="['pve-vchk', item.ok ? 'pve-vchk--ok' : 'pve-vchk--warn']"
126
+ >
127
+ <span aria-hidden="true">
128
+ <svg
129
+ v-if="item.ok"
130
+ width="14"
131
+ height="14"
132
+ viewBox="0 0 24 24"
133
+ fill="none"
134
+ stroke="currentColor"
135
+ stroke-width="3"
136
+ >
137
+ <path d="M5 13l4 4L19 7" />
138
+ </svg>
139
+ <svg
140
+ v-else
141
+ width="14"
142
+ height="14"
143
+ viewBox="0 0 24 24"
144
+ fill="none"
145
+ stroke="currentColor"
146
+ stroke-width="2"
147
+ >
148
+ <path
149
+ d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"
150
+ />
151
+ <path d="M12 9v4M12 17h.01" />
152
+ </svg>
153
+ </span>
154
+ <span v-html="item.label" />
155
+ </div>
156
+ </div>
157
+ </section>
158
+ </template>
159
+
160
+ <script setup lang="ts">
161
+ import type { ChecklistItem, PreviewMode, SelectedQuotaRow } from './types.js';
162
+
163
+ defineProps<{
164
+ previewMode: PreviewMode;
165
+ catalogUrl: string;
166
+ planKey: string;
167
+ version: number;
168
+ changeNote: string;
169
+ quotas: Record<string, number>;
170
+ planDisplayName: string;
171
+ formattedMonthly: string;
172
+ formattedYearly: string;
173
+ yearlySavingsLabel: string | null;
174
+ selectedQuotaList: SelectedQuotaRow[];
175
+ sortedSelectedFeatures: string[];
176
+ checklist: ChecklistItem[];
177
+ checklistOkCount: number;
178
+ featureLabel: (key: string) => string;
179
+ }>();
180
+
181
+ defineEmits<{
182
+ (e: 'update:previewMode', value: PreviewMode): void;
183
+ }>();
184
+ </script>
@@ -0,0 +1,272 @@
1
+ <template>
2
+ <section class="pve-col pve-pool">
3
+ <div class="pve-col-header">
4
+ <div>
5
+ <div class="pve-col-title">Komponenten</div>
6
+ <div class="pve-col-sub">Aus der Library in den Plan-Korb ziehen</div>
7
+ </div>
8
+ </div>
9
+ <div class="pve-search">
10
+ <span class="pve-search-ico" aria-hidden="true">
11
+ <svg
12
+ width="14"
13
+ height="14"
14
+ viewBox="0 0 24 24"
15
+ fill="none"
16
+ stroke="currentColor"
17
+ stroke-width="2"
18
+ >
19
+ <circle cx="11" cy="11" r="7" />
20
+ <path d="M21 21l-4.35-4.35" />
21
+ </svg>
22
+ </span>
23
+ <input
24
+ :value="searchTerm"
25
+ placeholder="In allen Komponenten suchen…"
26
+ @input="updateSearchTerm"
27
+ />
28
+ <span class="pve-kbd">⌘ K</span>
29
+ </div>
30
+ <div class="pve-tabs" role="tablist">
31
+ <button
32
+ v-for="tab in poolTabs"
33
+ :key="tab.id"
34
+ type="button"
35
+ role="tab"
36
+ :class="['pve-tab', { 'pve-tab--active': activeTab === tab.id }]"
37
+ @click="$emit('update:activeTab', tab.id)"
38
+ >
39
+ {{ tab.label }}
40
+ <span class="pve-tab-count">{{ tab.count }}</span>
41
+ </button>
42
+ </div>
43
+ <div class="pve-pool-list">
44
+ <template v-if="activeTab === 'features'">
45
+ <template v-for="grp in filteredFeatureGroups" :key="grp.key">
46
+ <div class="pve-pool-group">{{ grp.label }}</div>
47
+ <div
48
+ v-for="f in grp.rows"
49
+ :key="f.featureKey"
50
+ class="pve-pool-card"
51
+ :class="{ 'pve-pool-card--selected': isFeatureOn(f.featureKey) }"
52
+ draggable="true"
53
+ @click="$emit('toggle-feature', f.featureKey, !isFeatureOn(f.featureKey))"
54
+ @dragstart="$emit('drag-start', 'feature', f.featureKey, $event)"
55
+ @dragend="$emit('drag-end')"
56
+ >
57
+ <span class="pve-pool-grip" aria-hidden="true">
58
+ <svg
59
+ width="14"
60
+ height="14"
61
+ viewBox="0 0 24 24"
62
+ fill="none"
63
+ stroke="currentColor"
64
+ >
65
+ <circle cx="9" cy="6" r="1.5" fill="currentColor" />
66
+ <circle cx="15" cy="6" r="1.5" fill="currentColor" />
67
+ <circle cx="9" cy="12" r="1.5" fill="currentColor" />
68
+ <circle cx="15" cy="12" r="1.5" fill="currentColor" />
69
+ <circle cx="9" cy="18" r="1.5" fill="currentColor" />
70
+ <circle cx="15" cy="18" r="1.5" fill="currentColor" />
71
+ </svg>
72
+ </span>
73
+ <div class="pve-pool-card-main">
74
+ <div class="pve-pool-card-row">
75
+ <span class="pve-pool-card-label">{{
76
+ featureLabel(f.featureKey)
77
+ }}</span>
78
+ <span
79
+ v-if="isFeatureOn(f.featureKey)"
80
+ class="pve-pool-card-check"
81
+ aria-hidden="true"
82
+ >
83
+ <svg
84
+ width="12"
85
+ height="12"
86
+ viewBox="0 0 24 24"
87
+ fill="none"
88
+ stroke="currentColor"
89
+ stroke-width="3"
90
+ >
91
+ <path d="M5 13l4 4L19 7" />
92
+ </svg>
93
+ </span>
94
+ </div>
95
+ <div class="pve-pool-card-meta">
96
+ <code class="pve-pool-card-key">{{ f.featureKey }}</code>
97
+ <span class="pve-pool-card-dot">·</span>
98
+ <span>{{ featureGroupLabel(f.featureKey) }}</span>
99
+ </div>
100
+ </div>
101
+ </div>
102
+ </template>
103
+ <div v-if="filteredFeatureGroups.length === 0" class="pve-empty">
104
+ Keine Features im Discovery-Snapshot.
105
+ </div>
106
+ </template>
107
+
108
+ <template v-else-if="activeTab === 'quotas'">
109
+ <div
110
+ v-for="q in filteredQuotas"
111
+ :key="q.quotaKey"
112
+ class="pve-pool-card"
113
+ :class="{ 'pve-pool-card--selected': isQuotaOn(q.quotaKey) }"
114
+ draggable="true"
115
+ @click="$emit('pool-quota-click', q)"
116
+ @dragstart="$emit('drag-start', 'quota', q.quotaKey, $event)"
117
+ @dragend="$emit('drag-end')"
118
+ >
119
+ <span class="pve-pool-grip" aria-hidden="true">
120
+ <svg
121
+ width="14"
122
+ height="14"
123
+ viewBox="0 0 24 24"
124
+ fill="none"
125
+ stroke="currentColor"
126
+ >
127
+ <circle cx="9" cy="6" r="1.5" fill="currentColor" />
128
+ <circle cx="15" cy="6" r="1.5" fill="currentColor" />
129
+ <circle cx="9" cy="12" r="1.5" fill="currentColor" />
130
+ <circle cx="15" cy="12" r="1.5" fill="currentColor" />
131
+ <circle cx="9" cy="18" r="1.5" fill="currentColor" />
132
+ <circle cx="15" cy="18" r="1.5" fill="currentColor" />
133
+ </svg>
134
+ </span>
135
+ <div class="pve-pool-card-main">
136
+ <div class="pve-pool-card-row">
137
+ <span class="pve-pool-card-label">{{ q.label || q.quotaKey }}</span>
138
+ <span
139
+ v-if="isQuotaOn(q.quotaKey)"
140
+ class="pve-pool-card-check"
141
+ aria-hidden="true"
142
+ >
143
+ <svg
144
+ width="12"
145
+ height="12"
146
+ viewBox="0 0 24 24"
147
+ fill="none"
148
+ stroke="currentColor"
149
+ stroke-width="3"
150
+ >
151
+ <path d="M5 13l4 4L19 7" />
152
+ </svg>
153
+ </span>
154
+ </div>
155
+ <div class="pve-pool-card-meta">
156
+ <code class="pve-pool-card-key">{{ q.quotaKey }}</code>
157
+ <template v-if="q.unit">
158
+ <span class="pve-pool-card-dot">·</span>
159
+ <span>{{ q.unit }}</span>
160
+ </template>
161
+ </div>
162
+ </div>
163
+ </div>
164
+ <div v-if="filteredQuotas.length === 0" class="pve-empty">
165
+ Keine Quotas im Discovery-Snapshot.
166
+ </div>
167
+ </template>
168
+
169
+ <template v-else>
170
+ <div
171
+ v-for="b in filteredBundles"
172
+ :key="b.bundleKey"
173
+ class="pve-pool-card"
174
+ :class="{
175
+ 'pve-pool-card--selected': isBundleFullyOn(b),
176
+ 'pve-pool-card--partial': isBundlePartiallyOn(b),
177
+ }"
178
+ draggable="true"
179
+ @click="$emit('toggle-bundle', b, !isBundleFullyOn(b))"
180
+ @dragstart="$emit('drag-start', 'bundle', b.bundleKey, $event)"
181
+ @dragend="$emit('drag-end')"
182
+ >
183
+ <span class="pve-pool-grip" aria-hidden="true">
184
+ <svg
185
+ width="14"
186
+ height="14"
187
+ viewBox="0 0 24 24"
188
+ fill="none"
189
+ stroke="currentColor"
190
+ >
191
+ <circle cx="9" cy="6" r="1.5" fill="currentColor" />
192
+ <circle cx="15" cy="6" r="1.5" fill="currentColor" />
193
+ <circle cx="9" cy="12" r="1.5" fill="currentColor" />
194
+ <circle cx="15" cy="12" r="1.5" fill="currentColor" />
195
+ <circle cx="9" cy="18" r="1.5" fill="currentColor" />
196
+ <circle cx="15" cy="18" r="1.5" fill="currentColor" />
197
+ </svg>
198
+ </span>
199
+ <div class="pve-pool-card-main">
200
+ <div class="pve-pool-card-row">
201
+ <span class="pve-pool-card-label">{{ b.label || b.bundleKey }}</span>
202
+ <span
203
+ v-if="isBundleFullyOn(b)"
204
+ class="pve-pool-card-check"
205
+ aria-hidden="true"
206
+ >
207
+ <svg
208
+ width="12"
209
+ height="12"
210
+ viewBox="0 0 24 24"
211
+ fill="none"
212
+ stroke="currentColor"
213
+ stroke-width="3"
214
+ >
215
+ <path d="M5 13l4 4L19 7" />
216
+ </svg>
217
+ </span>
218
+ </div>
219
+ <div class="pve-pool-card-meta">
220
+ <code class="pve-pool-card-key">{{ b.bundleKey }}</code>
221
+ <span class="pve-pool-card-dot">·</span>
222
+ <span>{{ b.features.length }} Feature(s)</span>
223
+ </div>
224
+ </div>
225
+ </div>
226
+ <div v-if="filteredBundles.length === 0" class="pve-empty">
227
+ Keine published Bundles im Catalog.
228
+ </div>
229
+ </template>
230
+ </div>
231
+ </section>
232
+ </template>
233
+
234
+ <script setup lang="ts">
235
+ import type {
236
+ BundleEntry,
237
+ DiscoveryQuota,
238
+ FeatureGroup,
239
+ PoolKind,
240
+ PoolTab,
241
+ PoolTabItem,
242
+ } from './types.js';
243
+
244
+ defineProps<{
245
+ searchTerm: string;
246
+ activeTab: PoolTab;
247
+ poolTabs: PoolTabItem[];
248
+ filteredFeatureGroups: FeatureGroup[];
249
+ filteredQuotas: DiscoveryQuota[];
250
+ filteredBundles: BundleEntry[];
251
+ featureLabel: (key: string) => string;
252
+ featureGroupLabel: (key: string) => string;
253
+ isFeatureOn: (key: string) => boolean;
254
+ isQuotaOn: (key: string) => boolean;
255
+ isBundleFullyOn: (bundle: BundleEntry) => boolean;
256
+ isBundlePartiallyOn: (bundle: BundleEntry) => boolean;
257
+ }>();
258
+
259
+ const emit = defineEmits<{
260
+ (e: 'update:searchTerm', value: string): void;
261
+ (e: 'update:activeTab', value: PoolTab): void;
262
+ (e: 'toggle-feature', key: string, on: boolean): void;
263
+ (e: 'pool-quota-click', quota: DiscoveryQuota): void;
264
+ (e: 'toggle-bundle', bundle: BundleEntry, on: boolean): void;
265
+ (e: 'drag-start', kind: PoolKind, key: string, event: DragEvent): void;
266
+ (e: 'drag-end'): void;
267
+ }>();
268
+
269
+ function updateSearchTerm(event: Event): void {
270
+ emit('update:searchTerm', (event.target as HTMLInputElement | null)?.value ?? '');
271
+ }
272
+ </script>