@saasicat/ui-vue 0.4.0 → 0.6.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 (214) hide show
  1. package/README.md +64 -4
  2. package/dist/chunk-A3W2N26K.js +714 -0
  3. package/dist/chunk-QZOSDCNP.js +98 -0
  4. package/dist/chunk-X5SQ5HMB.js +3231 -0
  5. package/dist/client/index.cjs +3988 -0
  6. package/dist/client/index.d.cts +321 -0
  7. package/dist/client/index.d.ts +321 -0
  8. package/dist/client/index.js +70 -0
  9. package/dist/index.cjs +4446 -1249
  10. package/dist/index.d.cts +297 -888
  11. package/dist/index.d.ts +297 -888
  12. package/dist/index.js +609 -1348
  13. package/dist/messages-7b0P8W75.d.cts +1652 -0
  14. package/dist/messages-7b0P8W75.d.ts +1652 -0
  15. package/dist/quasar/index.cjs +3340 -0
  16. package/dist/quasar/index.d.cts +95 -0
  17. package/dist/quasar/index.d.ts +95 -0
  18. package/dist/quasar/index.js +96 -0
  19. package/dist/testing-e2e/admin-pages-suite.cjs +14 -11
  20. package/dist/testing-e2e/admin-pages-suite.js +14 -11
  21. package/dist/use-super-admin-i18n-B3v3-SWZ.d.ts +210 -0
  22. package/dist/use-super-admin-i18n-DMktRVEt.d.cts +210 -0
  23. package/package.json +23 -3
  24. package/src/{action-registry.ts → client/action-registry.ts} +4 -4
  25. package/src/{batch-column-fetcher.ts → client/batch-column-fetcher.ts} +4 -4
  26. package/src/{boot-loader.ts → client/boot-loader.ts} +3 -3
  27. package/src/client/i18n/currency.ts +42 -0
  28. package/src/client/i18n/define.ts +59 -0
  29. package/src/client/i18n/format.ts +12 -0
  30. package/src/client/i18n/index.ts +14 -0
  31. package/src/client/i18n/locale.ts +15 -0
  32. package/src/client/i18n/messages/audit.ts +42 -0
  33. package/src/client/i18n/messages/bundles.ts +459 -0
  34. package/src/client/i18n/messages/common.ts +147 -0
  35. package/src/client/i18n/messages/dashboard.ts +16 -0
  36. package/src/client/i18n/messages/discovery.ts +229 -0
  37. package/src/client/i18n/messages/email.ts +125 -0
  38. package/src/client/i18n/messages/marketing.ts +270 -0
  39. package/src/client/i18n/messages/nav.ts +72 -0
  40. package/src/client/i18n/messages/pilots.ts +192 -0
  41. package/src/client/i18n/messages/plan-detail.ts +266 -0
  42. package/src/client/i18n/messages/plan-editor.ts +177 -0
  43. package/src/client/i18n/messages/plan-versions.ts +251 -0
  44. package/src/client/i18n/messages/plans.ts +503 -0
  45. package/src/client/i18n/messages/promos.ts +202 -0
  46. package/src/client/i18n/messages/shell.ts +145 -0
  47. package/src/client/i18n/messages/tenants.ts +104 -0
  48. package/src/client/i18n/messages/users.ts +78 -0
  49. package/src/client/i18n/messages.ts +84 -0
  50. package/src/client/index.ts +27 -0
  51. package/src/{manifest-loader.ts → client/manifest-loader.ts} +4 -4
  52. package/src/{nav-builder.ts → client/nav-builder.ts} +44 -51
  53. package/src/{plan-versions-catalog.ts → client/plan-versions-catalog.ts} +42 -30
  54. package/src/components/BundleVersionPublishDialog.vue +35 -24
  55. package/src/components/FeatureGate.vue +1 -1
  56. package/src/components/MarketingPromotionsTab.vue +112 -59
  57. package/src/components/MfaPromptDialog.vue +11 -11
  58. package/src/components/TenantActionConfirmDialog.vue +19 -17
  59. package/src/components/VersionDiffPreview.vue +72 -43
  60. package/src/components/bundle-editor/BundleCreatePanel.vue +76 -63
  61. package/src/components/bundle-editor/BundleFeaturesEditor.vue +13 -6
  62. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +19 -14
  63. package/src/components/bundle-editor/BundleQuotasEditor.vue +7 -4
  64. package/src/components/bundle-editor/BundleStatusBanner.vue +36 -19
  65. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +51 -35
  66. package/src/components/bundle-editor/BundleVersionStrip.vue +24 -12
  67. package/src/components/bundle-editor/bundle-version-status.ts +29 -29
  68. package/src/components/dialogs/PilotCreateDialog.vue +58 -63
  69. package/src/components/dialogs/PilotEditDialog.vue +40 -34
  70. package/src/components/dialogs/PromoCodeCreateDialog.vue +89 -63
  71. package/src/components/dialogs/PromoCodeEditDialog.vue +98 -66
  72. package/src/components/dialogs/types.ts +3 -13
  73. package/src/components/plan/PlanCycleToggle.vue +21 -10
  74. package/src/components/plan/PlanGrid.vue +2 -2
  75. package/src/components/plan/PriceSummary.vue +1 -1
  76. package/src/components/plan/PromoCodeInput.vue +1 -1
  77. package/src/components/plan/PublicBundleGrid.vue +1 -1
  78. package/src/components/plan-cockpit/PlanCockpit.vue +34 -15
  79. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +13 -6
  80. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +18 -9
  81. package/src/components/plan-cockpit/PlanCockpitHeader.vue +9 -6
  82. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +24 -11
  83. package/src/components/plan-cockpit/PlanCockpitKpis.vue +28 -17
  84. package/src/components/plan-cockpit/PlanCockpitVersions.vue +50 -25
  85. package/src/components/plan-create-dialog/PlanCreateDialog.vue +45 -35
  86. package/src/components/plan-detail/PlanAuditLog.vue +11 -6
  87. package/src/components/plan-detail/PlanDetail.vue +21 -23
  88. package/src/components/plan-detail/PlanDetailHeader.vue +20 -11
  89. package/src/components/plan-detail/PlanDetailKpis.vue +30 -17
  90. package/src/components/plan-detail/PlanTerminateDialog.vue +22 -9
  91. package/src/components/plan-detail/PlanVersionDiffPanel.vue +61 -27
  92. package/src/components/plan-detail/PlanVersionsPanel.vue +71 -36
  93. package/src/components/plan-list/PlanList.vue +99 -57
  94. package/src/components/plan-matrix/PlanMatrix.vue +85 -38
  95. package/src/components/plan-review/PlanReview.vue +105 -80
  96. package/src/components/plan-version-editor/PlanCatalogPreview.vue +43 -20
  97. package/src/components/plan-version-editor/PlanComponentPool.vue +15 -7
  98. package/src/components/plan-version-editor/PlanVersionBasket.vue +45 -24
  99. package/src/components/plan-version-editor/PlanVersionDiffDialog.vue +8 -7
  100. package/src/components/plan-version-editor/PlanVersionEditor.vue +74 -40
  101. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +24 -12
  102. package/src/index.ts +82 -79
  103. package/src/pages-standard/AdminLayout.vue +43 -18
  104. package/src/pages-standard/AdminManifestErrorPage.vue +10 -10
  105. package/src/pages-standard/AuditPage.vue +58 -19
  106. package/src/pages-standard/BundlesPage.vue +29 -18
  107. package/src/pages-standard/DashboardPage.vue +31 -17
  108. package/src/pages-standard/DiscoveryPage.vue +40 -28
  109. package/src/pages-standard/EmailHistoryPage.vue +98 -69
  110. package/src/pages-standard/MarketingCatalogPage.vue +40 -21
  111. package/src/pages-standard/PilotsPage.vue +64 -52
  112. package/src/pages-standard/PlanVersionsPage.vue +9 -5
  113. package/src/pages-standard/PlansPage.vue +91 -59
  114. package/src/pages-standard/PlatformEmailPage.vue +120 -53
  115. package/src/pages-standard/PromoCodeDetailPage.vue +66 -63
  116. package/src/pages-standard/PromoCodesPage.vue +51 -54
  117. package/src/pages-standard/SubscriptionsPage.vue +14 -9
  118. package/src/pages-standard/SuperAdminLoginPage.vue +19 -16
  119. package/src/pages-standard/SuperAdminSetupWizard.vue +53 -49
  120. package/src/pages-standard/TenantDetailPage.vue +54 -40
  121. package/src/pages-standard/TenantsPage.vue +57 -44
  122. package/src/pages-standard/UsersPage.vue +70 -52
  123. package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -8
  124. package/src/pages-standard/bundles-page/BundleDetailPanel.vue +28 -18
  125. package/src/pages-standard/bundles-page/BundlesFilterBar.vue +4 -1
  126. package/src/pages-standard/bundles-page/BundlesHeader.vue +9 -8
  127. package/src/pages-standard/bundles-page/BundlesKpis.vue +29 -11
  128. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +20 -8
  129. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -3
  130. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +43 -24
  131. package/src/pages-standard/discovery-page/DiscoveryHeader.vue +7 -4
  132. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +28 -15
  133. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +20 -10
  134. package/src/pages-standard/discovery-page/DiscoveryStatusControl.vue +9 -6
  135. package/src/pages-standard/discovery-page/discovery-ui.ts +40 -52
  136. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +81 -49
  137. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +13 -10
  138. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +24 -20
  139. package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +6 -3
  140. package/src/pages-standard/plan-versions/PlanDiffCard.vue +16 -6
  141. package/src/pages-standard/plan-versions/PlanVersionHeader.vue +42 -26
  142. package/src/pages-standard/plan-versions/PlanVersionsAudit.vue +46 -27
  143. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +8 -6
  144. package/src/pages-standard/plan-versions/PlanVersionsList.vue +40 -17
  145. package/src/pages-standard/plan-versions/PlanVersionsMatrix.vue +1 -1
  146. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +25 -13
  147. package/src/pages-standard/plan-versions/format.ts +33 -17
  148. package/src/pages-standard/plans-page/PlanArchiveDialog.vue +12 -8
  149. package/src/pages-standard/plans-page/PlanBundleOverview.vue +15 -6
  150. package/src/pages-standard/plans-page/PlanDiscardDraftDialog.vue +18 -7
  151. package/src/pages-standard/plans-page/PlanPublishDialog.vue +23 -12
  152. package/src/pages-standard/tenants/format.ts +3 -2
  153. package/src/pages-tenant/MySubscriptionBundlesPage.vue +73 -38
  154. package/src/pages-tenant/OnboardingConfigurator.vue +3 -3
  155. package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
  156. package/src/pages-tenant/PlanChangeWizard.vue +10 -6
  157. package/src/pages-tenant/TenantPlanSection.vue +20 -16
  158. package/src/pages-tenant/default-i18n.ts +217 -1
  159. package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +9 -7
  160. package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +11 -12
  161. package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +4 -1
  162. package/src/pages-tenant/tenant-plan-section/TenantPlanCardHeader.vue +1 -1
  163. package/src/pages-tenant/tenant-plan-section/TenantUsageGrid.vue +1 -1
  164. package/src/quasar/create-super-admin-app.ts +192 -0
  165. package/src/quasar/index.ts +11 -0
  166. package/src/quasar/notify.ts +30 -0
  167. package/src/testing-e2e/admin-pages-suite.ts +14 -11
  168. package/src/{manifest-store-factory.ts → vue/manifest-store-factory.ts} +1 -1
  169. package/src/{platform-loaders.ts → vue/platform-loaders.ts} +4 -4
  170. package/src/{project-page-host.ts → vue/project-page-host.ts} +6 -4
  171. package/src/{create-super-admin-app.ts → vue/super-admin-context.ts} +20 -139
  172. package/src/vue/ui-notify.ts +30 -0
  173. package/src/{use-actions.ts → vue/use-actions.ts} +1 -1
  174. package/src/{use-api-list.ts → vue/use-api-list.ts} +1 -1
  175. package/src/{use-audit-entries.ts → vue/use-audit-entries.ts} +3 -3
  176. package/src/{use-batch-columns.ts → vue/use-batch-columns.ts} +1 -1
  177. package/src/{use-bulk-publish.ts → vue/use-bulk-publish.ts} +7 -5
  178. package/src/{use-bundle-versions-map.ts → vue/use-bundle-versions-map.ts} +7 -5
  179. package/src/{use-bundles.ts → vue/use-bundles.ts} +14 -14
  180. package/src/{use-catalog-entries.ts → vue/use-catalog-entries.ts} +14 -11
  181. package/src/{use-discovery.ts → vue/use-discovery.ts} +9 -6
  182. package/src/{use-entitlement.ts → vue/use-entitlement.ts} +4 -4
  183. package/src/{use-live-plan-versions.ts → vue/use-live-plan-versions.ts} +5 -5
  184. package/src/{use-manifest.ts → vue/use-manifest.ts} +1 -1
  185. package/src/{use-marketing-projections.ts → vue/use-marketing-projections.ts} +10 -7
  186. package/src/{use-nav.ts → vue/use-nav.ts} +9 -3
  187. package/src/{use-plan-editor.ts → vue/use-plan-editor.ts} +2 -2
  188. package/src/{use-plan-versions.ts → vue/use-plan-versions.ts} +3 -3
  189. package/src/{use-plans.ts → vue/use-plans.ts} +13 -13
  190. package/src/{use-platform-tenant-actions.ts → vue/use-platform-tenant-actions.ts} +9 -6
  191. package/src/{use-promotions.ts → vue/use-promotions.ts} +10 -11
  192. package/src/{use-public-boot.ts → vue/use-public-boot.ts} +1 -1
  193. package/src/{use-subscription-draft.ts → vue/use-subscription-draft.ts} +2 -3
  194. package/src/{use-super-admin-context.ts → vue/use-super-admin-context.ts} +5 -5
  195. package/src/vue/use-super-admin-i18n.ts +82 -0
  196. package/src/{use-tenant-action-flow.ts → vue/use-tenant-action-flow.ts} +12 -7
  197. package/src/{use-tenant-billing-catalog.ts → vue/use-tenant-billing-catalog.ts} +2 -2
  198. package/src/{use-tenant-billing.ts → vue/use-tenant-billing.ts} +2 -2
  199. package/src/{use-tenant-manifest.ts → vue/use-tenant-manifest.ts} +2 -4
  200. package/src/{use-tenant-subscription-bundles.ts → vue/use-tenant-subscription-bundles.ts} +5 -5
  201. package/src/{use-tenants.ts → vue/use-tenants.ts} +3 -3
  202. package/dist/testing-e2e/admin-pages-suite.d.cts +0 -37
  203. package/dist/testing-e2e/admin-pages-suite.d.ts +0 -37
  204. package/src/components/BusinessTypeVersionEditorDialog.vue +0 -417
  205. package/src/components/BusinessTypeVersionPublishDialog.vue +0 -258
  206. package/src/pages-standard/BusinessTypesPage.vue +0 -662
  207. package/src/pages-standard/plan-versions/VersionDiffPreview.vue +0 -167
  208. package/src/use-business-types.ts +0 -269
  209. /package/src/{http-json.ts → client/http-json.ts} +0 -0
  210. /package/src/{types.ts → client/types.ts} +0 -0
  211. /package/src/{version.ts → client/version.ts} +0 -0
  212. /package/src/{create-admin-routes.ts → vue/create-admin-routes.ts} +0 -0
  213. /package/src/{entitlement-provider.ts → vue/entitlement-provider.ts} +0 -0
  214. /package/src/{feature-router-guard.ts → vue/feature-router-guard.ts} +0 -0
@@ -1,662 +0,0 @@
1
- <template>
2
- <div class="sa-bts">
3
- <header class="sa-bts__head">
4
- <div>
5
- <h1 class="sa-bts__title">BusinessTypes</h1>
6
- <p class="sa-bts__sub">
7
- {{ businessTypes.length }} BusinessType{{
8
- businessTypes.length === 1 ? '' : 's'
9
- }}
10
- · Projekt {{ projectKey }}
11
- </p>
12
- </div>
13
- <div class="sa-bts__head-actions">
14
- <q-btn
15
- unelevated
16
- color="primary"
17
- icon="add"
18
- label="Neuer BusinessType"
19
- @click="openCreateDialog"
20
- />
21
- <q-btn flat icon="refresh" :loading="loading" @click="load" />
22
- </div>
23
- </header>
24
-
25
- <q-banner v-if="error" class="sa-bts__error" inline-actions rounded>
26
- <template #avatar><q-icon name="warning" color="negative" /></template>
27
- Fehler beim Laden: {{ error.message }}
28
- </q-banner>
29
-
30
- <q-banner
31
- v-if="businessTypes.length === 0 && !loading && !error"
32
- class="sa-bts__empty"
33
- inline-actions
34
- rounded
35
- >
36
- <template #avatar><q-icon name="info" color="info" /></template>
37
- Noch keine BusinessTypes angelegt. BusinessTypes komponieren mehrere published
38
- BundleVersions zu einer fachlichen Vertikale (z. B. Vereinstyp).
39
- </q-banner>
40
-
41
- <div v-if="businessTypes.length > 0" class="sa-bts__card">
42
- <q-table
43
- flat
44
- :rows="businessTypes"
45
- :columns="columns"
46
- row-key="id"
47
- :pagination="{ rowsPerPage: 0 }"
48
- hide-pagination
49
- @row-click="(_, row) => openDetail(row)"
50
- >
51
- <template #body-cell-businessTypeKey="{ row }">
52
- <q-td>
53
- <code class="sa-bts__key">{{ row.businessTypeKey }}</code>
54
- </q-td>
55
- </template>
56
- <template #body-cell-actions="{ row }">
57
- <q-td>
58
- <q-btn
59
- flat
60
- dense
61
- icon="edit"
62
- color="primary"
63
- @click.stop="openDetail(row)"
64
- />
65
- <q-btn
66
- flat
67
- dense
68
- icon="delete"
69
- color="negative"
70
- @click.stop="confirmDelete(row)"
71
- />
72
- </q-td>
73
- </template>
74
- </q-table>
75
- </div>
76
-
77
- <!-- Create dialog -->
78
- <q-dialog v-model="createOpen">
79
- <q-card style="min-width: 480px; max-width: 96vw">
80
- <q-card-section>
81
- <div class="text-h6">Neuer BusinessType</div>
82
- </q-card-section>
83
- <q-card-section class="sa-bts__form">
84
- <q-input
85
- v-model="createForm.businessTypeKey"
86
- outlined
87
- dense
88
- label="BusinessType-Key (SCREAMING_SNAKE_CASE)"
89
- />
90
- <q-input v-model="createForm.label" outlined dense label="Label" />
91
- <q-input
92
- v-model="createForm.description"
93
- outlined
94
- dense
95
- type="textarea"
96
- autogrow
97
- label="Beschreibung"
98
- />
99
- <q-input v-model="createForm.icon" outlined dense label="Icon (optional)" />
100
- <q-input
101
- v-model.number="createForm.sortOrder"
102
- outlined
103
- dense
104
- type="number"
105
- label="Sortier-Reihenfolge"
106
- />
107
- </q-card-section>
108
- <q-card-actions align="right">
109
- <q-btn v-close-popup flat label="Abbrechen" />
110
- <q-btn
111
- unelevated
112
- color="primary"
113
- label="Anlegen"
114
- :loading="createSubmitting"
115
- :disable="!canSubmitCreate"
116
- @click="submitCreate"
117
- />
118
- </q-card-actions>
119
- </q-card>
120
- </q-dialog>
121
-
122
- <!-- Detail drawer -->
123
- <q-drawer v-model="detailOpen" side="right" :width="560" bordered overlay>
124
- <div v-if="detailType" class="sa-bts__drawer">
125
- <header class="sa-bts__drawer-head">
126
- <div>
127
- <code class="sa-bts__key">{{ detailType.businessTypeKey }}</code>
128
- <h2 class="sa-bts__drawer-title">{{ detailType.label }}</h2>
129
- </div>
130
- <q-btn flat dense icon="close" @click="detailOpen = false" />
131
- </header>
132
-
133
- <section class="sa-bts__form">
134
- <q-input v-model="editForm.label" outlined dense label="Label" />
135
- <q-input
136
- v-model="editForm.description"
137
- outlined
138
- dense
139
- type="textarea"
140
- autogrow
141
- label="Beschreibung"
142
- />
143
- <q-input v-model="editForm.icon" outlined dense label="Icon" />
144
- <q-input
145
- v-model.number="editForm.sortOrder"
146
- outlined
147
- dense
148
- type="number"
149
- label="Sortier-Reihenfolge"
150
- />
151
- <q-btn
152
- unelevated
153
- color="primary"
154
- label="Speichern"
155
- :loading="editSubmitting"
156
- @click="submitEdit"
157
- />
158
- </section>
159
-
160
- <q-separator class="q-my-md" />
161
-
162
- <section>
163
- <div class="sa-bts__drawer-subhead">
164
- <strong>Versionen</strong>
165
- <span class="text-caption text-grey-7">
166
- {{ detailVersions.length }}
167
- </span>
168
- </div>
169
- <q-banner
170
- v-if="detailVersions.length === 0"
171
- class="sa-bts__empty"
172
- inline-actions
173
- rounded
174
- >
175
- Noch keine Version. „Neuer Draft" anlegen.
176
- </q-banner>
177
- <q-list v-else bordered separator>
178
- <q-item v-for="v in detailVersions" :key="v.id">
179
- <q-item-section>
180
- <q-item-label>v{{ v.version }}</q-item-label>
181
- <q-item-label caption>
182
- {{ v.bundles.length }} Bundle{{
183
- v.bundles.length === 1 ? '' : 's'
184
- }}
185
- · {{ v.monthlyNet ?? 'Σ Bundle-Preise' }}
186
- </q-item-label>
187
- </q-item-section>
188
- <q-item-section side>
189
- <div class="sa-bts__version-actions">
190
- <q-chip dense :color="versionStatusColor(v)" text-color="white">
191
- {{ versionStatusLabel(v) }}
192
- </q-chip>
193
- <q-btn
194
- v-if="v.publishedAt === null"
195
- flat
196
- dense
197
- icon="edit"
198
- color="primary"
199
- @click="openEditDraft(v)"
200
- />
201
- <q-btn
202
- v-if="v.publishedAt === null"
203
- flat
204
- dense
205
- icon="rocket_launch"
206
- color="positive"
207
- @click="openPublish(v)"
208
- />
209
- </div>
210
- </q-item-section>
211
- </q-item>
212
- </q-list>
213
-
214
- <div v-if="!hasDraft && detailType" class="sa-bts__new-draft">
215
- <q-btn
216
- unelevated
217
- color="primary"
218
- icon="add"
219
- label="Neuer Draft"
220
- @click="openCreateDraft"
221
- />
222
- </div>
223
- <q-banner v-else-if="hasDraft" class="sa-bts__hint" inline-actions rounded>
224
- Hat bereits eine Draft-Version.
225
- </q-banner>
226
- </section>
227
- </div>
228
- </q-drawer>
229
-
230
- <!-- Strict mode warnings -->
231
- <q-banner
232
- v-if="lastWarnings.length > 0"
233
- class="sa-bts__warnings-banner"
234
- inline-actions
235
- rounded
236
- >
237
- <template #avatar><q-icon name="warning" color="warning" /></template>
238
- <strong>{{ lastWarnings.length }} Strict-Mode-Warnung(en) bei letzter Operation</strong>
239
- <ul class="sa-bts__warnings-list">
240
- <li v-for="(w, i) in lastWarnings" :key="i">
241
- <code>{{ w.code }}</code>
242
- <template v-if="w.value">
243
- · <code>{{ w.value }}</code></template
244
- >
245
- — {{ w.message }}
246
- </li>
247
- </ul>
248
- <template #action>
249
- <q-btn flat dense label="Schließen" @click="lastWarnings = []" />
250
- </template>
251
- </q-banner>
252
-
253
- <!-- Editor + publish dialogs -->
254
- <BusinessTypeVersionEditorDialog
255
- v-if="detailType && editorOpen"
256
- v-model="editorOpen"
257
- :mode="editorMode"
258
- :business-type-key="detailType.businessTypeKey"
259
- :draft="editorDraft"
260
- :version-number="editorVersionNumber"
261
- :available-bundles="availableBundles"
262
- :submit="onEditorSubmit"
263
- @submitted="onEditorSubmitted"
264
- />
265
- <BusinessTypeVersionPublishDialog
266
- v-if="detailType && publishOpen && publishDraft"
267
- v-model="publishOpen"
268
- :business-type-key="detailType.businessTypeKey"
269
- :draft="publishDraft"
270
- :previous="publishPrevious"
271
- :warnings="lastWarnings"
272
- :classify-diff="classifyDiff"
273
- :submit="onPublishSubmit"
274
- @submitted="onPublishSubmitted"
275
- />
276
- </div>
277
- </template>
278
-
279
- <script setup lang="ts">
280
- import { computed, ref, watch } from 'vue';
281
- import type {
282
- BundleVersionRow,
283
- BusinessTypeRow,
284
- BusinessTypeVersionMutationResult,
285
- BusinessTypeVersionRow,
286
- CreateBusinessTypeData,
287
- CreateBusinessTypeVersionDraftData,
288
- StrictModeWarning,
289
- UpdateBusinessTypeData,
290
- UpdateBusinessTypeVersionDraftData,
291
- VersionChange,
292
- } from '@saasicat/types';
293
-
294
- import BusinessTypeVersionEditorDialog from '../components/BusinessTypeVersionEditorDialog.vue';
295
- import BusinessTypeVersionPublishDialog from '../components/BusinessTypeVersionPublishDialog.vue';
296
-
297
- interface QTableColumn {
298
- name: string;
299
- label: string;
300
- field: string | ((row: unknown) => unknown);
301
- align?: 'left' | 'right' | 'center';
302
- sortable?: boolean;
303
- }
304
-
305
- const props = defineProps<{
306
- projectKey: string;
307
- businessTypes: BusinessTypeRow[];
308
- loading: boolean;
309
- error: Error | null;
310
- load: () => Promise<void>;
311
- create: (data: CreateBusinessTypeData) => Promise<BusinessTypeRow>;
312
- update: (businessTypeId: string, data: UpdateBusinessTypeData) => Promise<BusinessTypeRow>;
313
- softDelete: (businessTypeId: string) => Promise<void>;
314
- loadVersions: (businessTypeId: string) => Promise<BusinessTypeVersionRow[]>;
315
- createDraft: (
316
- businessTypeId: string,
317
- data: Omit<CreateBusinessTypeVersionDraftData, 'businessTypeId'>,
318
- ) => Promise<BusinessTypeVersionMutationResult>;
319
- updateDraft: (
320
- versionId: string,
321
- data: UpdateBusinessTypeVersionDraftData,
322
- ) => Promise<BusinessTypeVersionMutationResult>;
323
- publish: (
324
- versionId: string,
325
- opts: { forceRegressive?: boolean },
326
- ) => Promise<BusinessTypeVersionMutationResult>;
327
- /** List of all available published BundleVersions in the project. */
328
- availableBundles: BundleVersionRow[];
329
- /** Pure-function diff classifier. */
330
- classifyDiff: (
331
- previous: BusinessTypeVersionRow,
332
- draft: BusinessTypeVersionRow,
333
- ) => { changes: VersionChange[]; nonRegressive: boolean };
334
- }>();
335
-
336
- const columns: QTableColumn[] = [
337
- {
338
- name: 'businessTypeKey',
339
- label: 'Key',
340
- field: 'businessTypeKey',
341
- align: 'left',
342
- sortable: true,
343
- },
344
- { name: 'label', label: 'Label', field: 'label', align: 'left', sortable: true },
345
- {
346
- name: 'description',
347
- label: 'Beschreibung',
348
- field: (row) => (row as BusinessTypeRow).description ?? '—',
349
- align: 'left',
350
- },
351
- {
352
- name: 'sortOrder',
353
- label: 'Sort',
354
- field: 'sortOrder',
355
- align: 'right',
356
- sortable: true,
357
- },
358
- { name: 'actions', label: '', field: () => '', align: 'right' },
359
- ];
360
-
361
- // ─── Create dialog ───
362
- const createOpen = ref(false);
363
- const createSubmitting = ref(false);
364
- const createForm = ref<{
365
- businessTypeKey: string;
366
- label: string;
367
- description: string;
368
- icon: string;
369
- sortOrder: number;
370
- }>({
371
- businessTypeKey: '',
372
- label: '',
373
- description: '',
374
- icon: '',
375
- sortOrder: 0,
376
- });
377
-
378
- const canSubmitCreate = computed(
379
- () => createForm.value.businessTypeKey.length > 0 && createForm.value.label.length > 0,
380
- );
381
-
382
- function openCreateDialog(): void {
383
- createForm.value = {
384
- businessTypeKey: '',
385
- label: '',
386
- description: '',
387
- icon: '',
388
- sortOrder: 0,
389
- };
390
- createOpen.value = true;
391
- }
392
-
393
- async function submitCreate(): Promise<void> {
394
- createSubmitting.value = true;
395
- try {
396
- await props.create({
397
- projectKey: props.projectKey,
398
- businessTypeKey: createForm.value.businessTypeKey,
399
- label: createForm.value.label,
400
- description: createForm.value.description || undefined,
401
- icon: createForm.value.icon || undefined,
402
- sortOrder: createForm.value.sortOrder,
403
- });
404
- createOpen.value = false;
405
- } finally {
406
- createSubmitting.value = false;
407
- }
408
- }
409
-
410
- // ─── Detail drawer ───
411
- const detailOpen = ref(false);
412
- const detailType = ref<BusinessTypeRow | null>(null);
413
- const detailVersions = ref<BusinessTypeVersionRow[]>([]);
414
- const editForm = ref<UpdateBusinessTypeData>({
415
- label: '',
416
- description: '',
417
- icon: '',
418
- sortOrder: 0,
419
- });
420
- const editSubmitting = ref(false);
421
-
422
- async function openDetail(bt: BusinessTypeRow): Promise<void> {
423
- detailType.value = bt;
424
- editForm.value = {
425
- label: bt.label,
426
- description: bt.description ?? '',
427
- icon: bt.icon ?? '',
428
- sortOrder: bt.sortOrder,
429
- };
430
- detailOpen.value = true;
431
- detailVersions.value = await props.loadVersions(bt.id);
432
- }
433
-
434
- async function submitEdit(): Promise<void> {
435
- if (!detailType.value) return;
436
- editSubmitting.value = true;
437
- try {
438
- const updated = await props.update(detailType.value.id, {
439
- label: editForm.value.label,
440
- description: editForm.value.description || null,
441
- icon: editForm.value.icon || null,
442
- sortOrder: editForm.value.sortOrder,
443
- });
444
- detailType.value = updated;
445
- } finally {
446
- editSubmitting.value = false;
447
- }
448
- }
449
-
450
- async function confirmDelete(bt: BusinessTypeRow): Promise<void> {
451
- const ok = window.confirm(`BusinessType '${bt.businessTypeKey}' wirklich soft-deleten?`);
452
- if (!ok) return;
453
- await props.softDelete(bt.id);
454
- if (detailType.value?.id === bt.id) {
455
- detailOpen.value = false;
456
- detailType.value = null;
457
- }
458
- }
459
-
460
- watch(
461
- () => props.businessTypes,
462
- (next) => {
463
- if (detailType.value && !next.some((b) => b.id === detailType.value!.id)) {
464
- detailOpen.value = false;
465
- detailType.value = null;
466
- }
467
- },
468
- );
469
-
470
- // ─── Strict mode warnings ───
471
- const lastWarnings = ref<StrictModeWarning[]>([]);
472
-
473
- // ─── Editor modal ───
474
- const editorOpen = ref(false);
475
- const editorMode = ref<'create' | 'edit'>('create');
476
- const editorDraft = ref<BusinessTypeVersionRow | null>(null);
477
-
478
- const hasDraft = computed(() => detailVersions.value.some((v) => v.publishedAt === null));
479
-
480
- const editorVersionNumber = computed(() => {
481
- if (editorMode.value === 'edit' && editorDraft.value) return editorDraft.value.version;
482
- if (detailVersions.value.length === 0) return 1;
483
- return Math.max(...detailVersions.value.map((v) => v.version)) + 1;
484
- });
485
-
486
- function openCreateDraft(): void {
487
- if (hasDraft.value) return;
488
- editorMode.value = 'create';
489
- editorDraft.value = null;
490
- editorOpen.value = true;
491
- }
492
-
493
- function openEditDraft(v: BusinessTypeVersionRow): void {
494
- if (v.publishedAt !== null) return;
495
- editorMode.value = 'edit';
496
- editorDraft.value = v;
497
- editorOpen.value = true;
498
- }
499
-
500
- async function onEditorSubmit(
501
- data: CreateBusinessTypeVersionDraftData | UpdateBusinessTypeVersionDraftData,
502
- ): Promise<BusinessTypeVersionMutationResult> {
503
- if (editorMode.value === 'create' && detailType.value) {
504
- return props.createDraft(detailType.value.id, data as CreateBusinessTypeVersionDraftData);
505
- }
506
- if (editorMode.value === 'edit' && editorDraft.value) {
507
- return props.updateDraft(editorDraft.value.id, data as UpdateBusinessTypeVersionDraftData);
508
- }
509
- throw new Error('BusinessTypesPage: editor submit ohne Kontext');
510
- }
511
-
512
- async function onEditorSubmitted(result: BusinessTypeVersionMutationResult): Promise<void> {
513
- lastWarnings.value = result.warnings;
514
- if (detailType.value) {
515
- detailVersions.value = await props.loadVersions(detailType.value.id);
516
- }
517
- }
518
-
519
- // ─── Publish modal ───
520
- const publishOpen = ref(false);
521
- const publishDraft = ref<BusinessTypeVersionRow | null>(null);
522
-
523
- const publishPrevious = computed<BusinessTypeVersionRow | null>(() => {
524
- if (!publishDraft.value) return null;
525
- return (
526
- detailVersions.value
527
- .filter((v) => v.publishedAt !== null && v.supersededAt === null)
528
- .sort((a, b) => b.version - a.version)[0] ?? null
529
- );
530
- });
531
-
532
- function openPublish(v: BusinessTypeVersionRow): void {
533
- if (v.publishedAt !== null) return;
534
- publishDraft.value = v;
535
- publishOpen.value = true;
536
- }
537
-
538
- async function onPublishSubmit(opts: {
539
- forceRegressive: boolean;
540
- }): Promise<BusinessTypeVersionMutationResult> {
541
- if (!publishDraft.value) {
542
- throw new Error('BusinessTypesPage: publish submit ohne Draft');
543
- }
544
- return props.publish(publishDraft.value.id, opts);
545
- }
546
-
547
- async function onPublishSubmitted(result: BusinessTypeVersionMutationResult): Promise<void> {
548
- lastWarnings.value = result.warnings;
549
- publishDraft.value = null;
550
- if (detailType.value) {
551
- detailVersions.value = await props.loadVersions(detailType.value.id);
552
- }
553
- }
554
-
555
- function versionStatusLabel(v: BusinessTypeVersionRow): string {
556
- if (v.publishedAt === null) return 'Draft';
557
- if (v.supersededAt !== null) return 'Superseded';
558
- return v.marketed ? 'Live · marketed' : 'Live · intern';
559
- }
560
-
561
- function versionStatusColor(v: BusinessTypeVersionRow): string {
562
- if (v.publishedAt === null) return 'warning';
563
- if (v.supersededAt !== null) return 'grey';
564
- return v.marketed ? 'positive' : 'info';
565
- }
566
- </script>
567
-
568
- <style scoped>
569
- .sa-bts {
570
- padding: 16px;
571
- display: flex;
572
- flex-direction: column;
573
- gap: 16px;
574
- }
575
- .sa-bts__head {
576
- display: flex;
577
- justify-content: space-between;
578
- align-items: flex-start;
579
- gap: 16px;
580
- }
581
- .sa-bts__title {
582
- font-size: 20px;
583
- font-weight: 600;
584
- margin: 0;
585
- }
586
- .sa-bts__sub {
587
- margin: 4px 0 0;
588
- color: var(--q-grey-7, #757575);
589
- font-size: 12px;
590
- }
591
- .sa-bts__head-actions {
592
- display: flex;
593
- align-items: center;
594
- gap: 12px;
595
- }
596
- .sa-bts__error {
597
- border-left: 4px solid var(--q-negative, #c10015);
598
- }
599
- .sa-bts__empty {
600
- border-left: 4px solid var(--q-info, #31ccec);
601
- }
602
- .sa-bts__card {
603
- background: white;
604
- border-radius: 8px;
605
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
606
- }
607
- .sa-bts__key {
608
- background: rgba(0, 0, 0, 0.05);
609
- padding: 2px 6px;
610
- border-radius: 4px;
611
- font-size: 12px;
612
- font-weight: 500;
613
- }
614
- .sa-bts__form {
615
- display: flex;
616
- flex-direction: column;
617
- gap: 12px;
618
- }
619
- .sa-bts__drawer {
620
- padding: 16px;
621
- display: flex;
622
- flex-direction: column;
623
- gap: 12px;
624
- }
625
- .sa-bts__drawer-head {
626
- display: flex;
627
- justify-content: space-between;
628
- align-items: flex-start;
629
- gap: 12px;
630
- }
631
- .sa-bts__drawer-title {
632
- font-size: 18px;
633
- font-weight: 600;
634
- margin: 4px 0 0;
635
- }
636
- .sa-bts__drawer-subhead {
637
- display: flex;
638
- justify-content: space-between;
639
- align-items: baseline;
640
- margin-bottom: 8px;
641
- }
642
- .sa-bts__version-actions {
643
- display: flex;
644
- align-items: center;
645
- gap: 4px;
646
- }
647
- .sa-bts__new-draft {
648
- margin-top: 12px;
649
- }
650
- .sa-bts__hint {
651
- border-left: 4px solid var(--q-info, #31ccec);
652
- margin-top: 12px;
653
- }
654
- .sa-bts__warnings-banner {
655
- border-left: 4px solid var(--q-warning, #f2c037);
656
- }
657
- .sa-bts__warnings-list {
658
- margin: 8px 0 0;
659
- padding-left: 20px;
660
- font-size: 13px;
661
- }
662
- </style>