@saasicat/ui-vue 0.21.0 → 0.22.1
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/dist/{catalog-BY_t9prv.d.cts → catalog-DJcfm5N2.d.cts} +20 -7
- package/dist/{catalog-BY_t9prv.d.ts → catalog-DJcfm5N2.d.ts} +20 -7
- package/dist/{chunk-JN6HZGLV.js → chunk-F7NRM6KI.js} +54 -28
- package/dist/{chunk-7IFKVGZV.js → chunk-LY52T4N4.js} +21 -2
- package/dist/{chunk-OTP3OSSW.js → chunk-XMMFN2WJ.js} +74 -3
- package/dist/client/index.cjs +132 -29
- package/dist/client/index.d.cts +53 -3
- package/dist/client/index.d.ts +53 -3
- package/dist/client/index.js +14 -2
- package/dist/index.cjs +319 -162
- package/dist/index.d.cts +30 -8
- package/dist/index.d.ts +30 -8
- package/dist/index.js +176 -133
- package/dist/quasar/index.cjs +76 -29
- package/dist/quasar/index.d.cts +2 -2
- package/dist/quasar/index.d.ts +2 -2
- package/dist/quasar/index.js +6 -2
- package/dist/testing-e2e/admin-pages-suite.d.cts +37 -0
- package/dist/testing-e2e/admin-pages-suite.d.ts +37 -0
- package/dist/{use-super-admin-i18n-c_Eqsu9u.d.ts → use-super-admin-i18n-CHNdsSlm.d.ts} +34 -7
- package/dist/{use-super-admin-i18n-DlJRGIVq.d.cts → use-super-admin-i18n-CgTuNi8h.d.cts} +34 -7
- package/package.json +18 -8
- package/src/client/i18n/messages/bundles.ts +0 -2
- package/src/client/i18n/messages/common.ts +22 -0
- package/src/client/i18n/messages/discovery.ts +2 -2
- package/src/client/i18n/messages/marketing.ts +2 -4
- package/src/client/i18n/messages/plan-detail.ts +0 -4
- package/src/client/i18n/messages/plans.ts +20 -8
- package/src/client/i18n/messages/tenants.ts +0 -2
- package/src/client/index.ts +1 -0
- package/src/client/manifest-loader.ts +1 -1
- package/src/client/resolve-plans.ts +144 -0
- package/src/components/BundleVersionPublishDialog.vue +6 -6
- package/src/components/KvBlock.vue +4 -4
- package/src/components/MarketingPromotionsTab.vue +34 -48
- package/src/components/MfaPromptDialog.vue +8 -2
- package/src/components/TenantActionConfirmDialog.vue +4 -4
- package/src/components/VersionDiffPreview.vue +7 -7
- package/src/components/admin-page/AdminFilters.vue +12 -0
- package/src/components/admin-page/AdminHero.vue +22 -5
- package/src/components/admin-page/AdminKpi.vue +84 -0
- package/src/components/admin-page/AdminPaginator.vue +195 -0
- package/src/components/admin-page/AdminRefreshBtn.vue +39 -0
- package/src/components/admin-page/AdminSection.vue +31 -16
- package/src/components/admin-page/AdminStatistics.vue +40 -0
- package/src/components/admin-page/AdminTable.vue +175 -0
- package/src/components/bundle-editor/BundleCreatePanel.vue +23 -23
- package/src/components/bundle-editor/BundleFeaturesEditor.vue +7 -7
- package/src/components/bundle-editor/BundlePlanCompatPicker.vue +9 -9
- package/src/components/bundle-editor/BundleQuotasEditor.vue +11 -11
- package/src/components/bundle-editor/BundleStatusBanner.vue +6 -6
- package/src/components/bundle-editor/BundleVersionInlineEditor.vue +18 -18
- package/src/components/bundle-editor/BundleVersionStrip.vue +12 -12
- package/src/components/dialogs/PilotCreateDialog.vue +12 -12
- package/src/components/dialogs/PilotEditDialog.vue +7 -7
- package/src/components/dialogs/PromoCodeCreateDialog.vue +8 -8
- package/src/components/dialogs/PromoCodeDialogFields.vue +33 -19
- package/src/components/dialogs/PromoCodeEditDialog.vue +10 -10
- package/src/components/plan/PlanGrid.vue +1 -1
- package/src/components/plan-create-dialog/PlanCreateDialog.vue +34 -46
- package/src/components/plan-detail/PlanDetail.vue +92 -302
- package/src/components/plan-detail/PlanDetailKpis.vue +22 -27
- package/src/components/plan-detail/PlanTitleEdit.vue +133 -0
- package/src/components/plan-list/PlanList.vue +72 -290
- package/src/components/plan-matrix/PlanMatrix.vue +66 -183
- package/src/components/plan-review/PlanReview.vue +191 -275
- package/src/components/plan-version-editor/PlanCatalogPreview.vue +7 -5
- package/src/components/plan-version-editor/PlanVersionEditor.vue +200 -295
- package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +23 -55
- package/src/index.ts +1 -0
- package/src/pages-standard/AdminLayout.vue +34 -10
- package/src/pages-standard/AdminManifestErrorPage.vue +79 -9
- package/src/pages-standard/AuditPage.vue +53 -72
- package/src/pages-standard/BundlesPage.vue +128 -144
- package/src/pages-standard/DashboardPage.vue +43 -107
- package/src/pages-standard/DiscoveryPage.vue +158 -200
- package/src/pages-standard/EmailHistoryPage.vue +98 -88
- package/src/pages-standard/MarketingCatalogPage.vue +237 -267
- package/src/pages-standard/PilotsPage.vue +44 -60
- package/src/pages-standard/PlansPage.vue +230 -105
- package/src/pages-standard/PlatformEmailPage.vue +37 -40
- package/src/pages-standard/PromoCodeDetailPage.vue +16 -30
- package/src/pages-standard/PromoCodesPage.vue +86 -112
- package/src/pages-standard/SubscriptionsPage.vue +10 -17
- package/src/pages-standard/SuperAdminLoginPage.vue +12 -12
- package/src/pages-standard/SuperAdminSetupWizard.vue +20 -17
- package/src/pages-standard/TenantDetailPage.vue +45 -41
- package/src/pages-standard/TenantsPage.vue +184 -264
- package/src/pages-standard/UsersPage.vue +69 -104
- package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -17
- package/src/pages-standard/bundles-page/BundlesKpis.vue +13 -22
- package/src/pages-standard/bundles-page/BundlesToolbar.vue +6 -10
- package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +10 -10
- package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -5
- package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +16 -16
- package/src/pages-standard/discovery-page/DiscoveryKpis.vue +31 -27
- package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +7 -7
- package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +1 -1
- package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +2 -13
- package/src/pages-standard/plan-versions/PlanDiffCard.vue +6 -6
- package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +6 -6
- package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +16 -16
- package/src/pages-standard/plans-page/PlanBundleOverview.vue +14 -29
- package/src/pages-standard/plans-page/PlanPublishDialog.vue +1 -1
- package/src/pages-standard/plans-page/PlansPageToast.vue +1 -1
- package/src/pages-standard/sa-theme.css +481 -71
- package/src/pages-standard/tenant-detail/TenantUsers.vue +2 -8
- package/src/pages-standard/tenants/StatusPill.vue +2 -2
- package/src/pages-tenant/MySubscriptionBundlesPage.vue +25 -25
- package/src/pages-tenant/OnboardingConfigurator.vue +1 -5
- package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
- package/src/pages-tenant/TenantPlanSection.vue +12 -20
- package/src/quasar/create-super-admin-app.ts +4 -0
- package/src/vue/entitlement-provider.ts +0 -1
- package/src/vue/feature-router-guard.ts +0 -1
- package/src/vue/super-admin-context.ts +106 -1
- package/src/vue/use-api-list.ts +2 -1
- package/src/vue/use-pagination.ts +63 -0
- package/src/vue/use-plans.ts +1 -2
- package/src/vue/use-sign-out.ts +90 -0
- package/src/vue/use-tenant-billing.ts +1 -1
- package/src/vue/use-tenant-manifest.ts +0 -1
- package/src/components/KpiCard.vue +0 -53
- package/src/components/plan-cockpit/PlanCockpit.vue +0 -1027
- package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +0 -72
- package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +0 -99
- package/src/components/plan-cockpit/PlanCockpitHeader.vue +0 -101
- package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +0 -112
- package/src/components/plan-cockpit/PlanCockpitKpis.vue +0 -68
- package/src/components/plan-cockpit/PlanCockpitVersions.vue +0 -249
- package/src/components/plan-cockpit/types.ts +0 -72
- package/src/components/plan-detail/PlanDetailHeader.vue +0 -194
|
@@ -2,40 +2,40 @@
|
|
|
2
2
|
<AdminPage class="sa-pemail">
|
|
3
3
|
<AdminHero :title="resolvedTitle" :subtitle="msg.provider.subtitle">
|
|
4
4
|
<template #actions>
|
|
5
|
-
<
|
|
5
|
+
<button
|
|
6
6
|
v-if="rows.length === 0"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
icon="add"
|
|
10
|
-
:label="msg.sender"
|
|
7
|
+
class="sa-btn sa-btn--primary"
|
|
8
|
+
type="button"
|
|
11
9
|
@click="openCreate"
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
>
|
|
11
|
+
<q-icon name="add" size="16px" />
|
|
12
|
+
<span>{{ msg.sender }}</span>
|
|
13
|
+
</button>
|
|
14
|
+
<AdminRefreshBtn :loading="loading" @refresh="reload" />
|
|
14
15
|
</template>
|
|
15
16
|
</AdminHero>
|
|
16
17
|
|
|
17
|
-
<
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<q-td>
|
|
18
|
+
<AdminBody>
|
|
19
|
+
<AdminSection class="sa-pemail__card">
|
|
20
|
+
<AdminTable
|
|
21
|
+
:rows="rows"
|
|
22
|
+
:columns="columns"
|
|
23
|
+
:loading="loading"
|
|
24
|
+
storage-key="platform-email"
|
|
25
|
+
>
|
|
26
|
+
<template #body-cell-active="{ row }">
|
|
27
|
+
<q-td>
|
|
28
|
+
<q-badge
|
|
29
|
+
:color="row.active ? 'positive' : 'grey'"
|
|
30
|
+
:label="
|
|
31
|
+
row.active
|
|
32
|
+
? msg.provider.statusActive
|
|
33
|
+
: msg.provider.statusInactive
|
|
34
|
+
"
|
|
35
|
+
/>
|
|
36
|
+
</q-td>
|
|
37
|
+
</template>
|
|
38
|
+
<template #row-actions="{ row }">
|
|
39
39
|
<q-btn
|
|
40
40
|
flat
|
|
41
41
|
dense
|
|
@@ -60,10 +60,10 @@
|
|
|
60
60
|
:title="common.delete"
|
|
61
61
|
@click="onDelete(row)"
|
|
62
62
|
/>
|
|
63
|
-
</
|
|
64
|
-
</
|
|
65
|
-
</
|
|
66
|
-
</
|
|
63
|
+
</template>
|
|
64
|
+
</AdminTable>
|
|
65
|
+
</AdminSection>
|
|
66
|
+
</AdminBody>
|
|
67
67
|
|
|
68
68
|
<q-dialog v-model="showForm">
|
|
69
69
|
<q-card class="sa-pemail__dialog">
|
|
@@ -192,10 +192,14 @@
|
|
|
192
192
|
</template>
|
|
193
193
|
|
|
194
194
|
<script setup lang="ts">
|
|
195
|
+
import AdminTable from '../components/admin-page/AdminTable.vue';
|
|
195
196
|
import { computed, reactive, ref } from 'vue';
|
|
196
197
|
import { useMfaPrompt } from '../vue/use-mfa-prompt.js';
|
|
197
198
|
import { useQuasar } from 'quasar';
|
|
199
|
+
import AdminRefreshBtn from '../components/admin-page/AdminRefreshBtn.vue';
|
|
200
|
+
import AdminBody from '../components/admin-page/AdminBody.vue';
|
|
198
201
|
import AdminHero from '../components/admin-page/AdminHero.vue';
|
|
202
|
+
import AdminSection from '../components/admin-page/AdminSection.vue';
|
|
199
203
|
import AdminPage from '../components/admin-page/AdminPage.vue';
|
|
200
204
|
import { useSuperAdminNotify } from '../quasar/notify.js';
|
|
201
205
|
import MfaPromptDialog from '../components/MfaPromptDialog.vue';
|
|
@@ -264,7 +268,6 @@ const columns = computed(() => [
|
|
|
264
268
|
align: 'left' as const,
|
|
265
269
|
},
|
|
266
270
|
{ name: 'active', label: common.value.status, field: 'active', align: 'left' as const },
|
|
267
|
-
{ name: 'actions', label: '', field: 'id' as never, align: 'right' as 'left' },
|
|
268
271
|
]);
|
|
269
272
|
|
|
270
273
|
const showForm = ref(false);
|
|
@@ -457,12 +460,6 @@ async function onTest(): Promise<void> {
|
|
|
457
460
|
</script>
|
|
458
461
|
|
|
459
462
|
<style scoped>
|
|
460
|
-
.sa-pemail__card {
|
|
461
|
-
background: #fff;
|
|
462
|
-
border: 1px solid var(--sa-border, #e2e8f0);
|
|
463
|
-
border-radius: 12px;
|
|
464
|
-
overflow: hidden;
|
|
465
|
-
}
|
|
466
463
|
.sa-pemail__dialog {
|
|
467
464
|
min-width: 420px;
|
|
468
465
|
}
|
|
@@ -22,14 +22,15 @@
|
|
|
22
22
|
<template v-else>—</template>
|
|
23
23
|
</template>
|
|
24
24
|
<template #actions>
|
|
25
|
-
<
|
|
25
|
+
<button
|
|
26
26
|
v-if="editSubmit && data"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
icon="edit"
|
|
30
|
-
:label="labels.edit"
|
|
27
|
+
class="sa-btn sa-btn--primary"
|
|
28
|
+
type="button"
|
|
31
29
|
@click="openEdit"
|
|
32
|
-
|
|
30
|
+
>
|
|
31
|
+
<q-icon name="edit" size="16px" />
|
|
32
|
+
<span>{{ labels.edit }}</span>
|
|
33
|
+
</button>
|
|
33
34
|
<slot name="header-actions" />
|
|
34
35
|
</template>
|
|
35
36
|
</AdminHero>
|
|
@@ -41,13 +42,13 @@
|
|
|
41
42
|
:empty-text="labels.empty"
|
|
42
43
|
>
|
|
43
44
|
<template v-if="data">
|
|
44
|
-
<AdminSection :title="labels.config"
|
|
45
|
+
<AdminSection :title="labels.config">
|
|
45
46
|
<slot name="config" :promo="data.promo">
|
|
46
47
|
<pre class="sa-promo-detail__kv">{{ resolveFormatPromo(data.promo) }}</pre>
|
|
47
48
|
</slot>
|
|
48
49
|
</AdminSection>
|
|
49
50
|
|
|
50
|
-
<AdminSection :title="labels.stats"
|
|
51
|
+
<AdminSection :title="labels.stats">
|
|
51
52
|
<slot name="stats" :stats="data.stats">
|
|
52
53
|
<pre class="sa-promo-detail__kv">{{
|
|
53
54
|
JSON.stringify(data.stats, null, 2)
|
|
@@ -55,18 +56,12 @@
|
|
|
55
56
|
</slot>
|
|
56
57
|
</AdminSection>
|
|
57
58
|
|
|
58
|
-
<AdminSection
|
|
59
|
-
:title="`${labels.redemptions} (${data.redemptions.length})`"
|
|
60
|
-
class="sa-card sa-promo-detail__section"
|
|
61
|
-
>
|
|
59
|
+
<AdminSection :title="`${labels.redemptions} (${data.redemptions.length})`">
|
|
62
60
|
<slot name="redemptions" :redemptions="data.redemptions">
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
:rows="data.redemptions"
|
|
61
|
+
<AdminTable
|
|
62
|
+
:rows="redemptionRows"
|
|
66
63
|
:columns="redemptionsColumns ?? defaultColumns"
|
|
67
|
-
|
|
68
|
-
:pagination="{ rowsPerPage: 0 }"
|
|
69
|
-
hide-pagination
|
|
64
|
+
storage-key="promo-code-redemptions"
|
|
70
65
|
/>
|
|
71
66
|
</slot>
|
|
72
67
|
</AdminSection>
|
|
@@ -87,6 +82,7 @@
|
|
|
87
82
|
</template>
|
|
88
83
|
|
|
89
84
|
<script setup lang="ts">
|
|
85
|
+
import AdminTable from '../components/admin-page/AdminTable.vue';
|
|
90
86
|
import { computed, onMounted, ref } from 'vue';
|
|
91
87
|
import AdminBody from '../components/admin-page/AdminBody.vue';
|
|
92
88
|
import AdminHero from '../components/admin-page/AdminHero.vue';
|
|
@@ -281,6 +277,8 @@ const defaultColumns = computed<QTableColumn[]>(() => [
|
|
|
281
277
|
align: 'left',
|
|
282
278
|
},
|
|
283
279
|
]);
|
|
280
|
+
|
|
281
|
+
const redemptionRows = computed(() => data.value?.redemptions ?? []);
|
|
284
282
|
</script>
|
|
285
283
|
|
|
286
284
|
<style scoped>
|
|
@@ -298,18 +296,6 @@ const defaultColumns = computed<QTableColumn[]>(() => [
|
|
|
298
296
|
flex-direction: column;
|
|
299
297
|
gap: 14px;
|
|
300
298
|
}
|
|
301
|
-
.sa-promo-detail__section {
|
|
302
|
-
padding: 16px 18px;
|
|
303
|
-
}
|
|
304
|
-
.sa-promo-detail__section-head {
|
|
305
|
-
font-size: 11px;
|
|
306
|
-
font-weight: 700;
|
|
307
|
-
letter-spacing: 0.08em;
|
|
308
|
-
color: var(--sa-muted);
|
|
309
|
-
text-transform: uppercase;
|
|
310
|
-
margin-bottom: 10px;
|
|
311
|
-
font-family: var(--sa-font-head);
|
|
312
|
-
}
|
|
313
299
|
.sa-promo-detail__kv {
|
|
314
300
|
background: #fafbfc;
|
|
315
301
|
border: 1px solid var(--sa-border);
|
|
@@ -3,74 +3,68 @@
|
|
|
3
3
|
<AdminHero :title="msg.title" :subtitle="subtitle">
|
|
4
4
|
<template #actions>
|
|
5
5
|
<slot name="head-actions">
|
|
6
|
-
<
|
|
6
|
+
<button
|
|
7
7
|
v-if="enableCreate"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
icon="add"
|
|
11
|
-
:label="resolvedCreateLabel"
|
|
8
|
+
class="sa-btn sa-btn--primary"
|
|
9
|
+
type="button"
|
|
12
10
|
@click="showCreate = true"
|
|
13
|
-
|
|
11
|
+
>
|
|
12
|
+
<q-icon name="add" size="16px" />
|
|
13
|
+
<span>{{ resolvedCreateLabel }}</span>
|
|
14
|
+
</button>
|
|
14
15
|
</slot>
|
|
15
16
|
</template>
|
|
16
17
|
</AdminHero>
|
|
17
18
|
|
|
18
|
-
<
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
</
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
<template #body-cell-status="{ row }">
|
|
68
|
-
<q-td>
|
|
69
|
-
<q-badge :color="statusColor(row.status)" :label="row.status" />
|
|
70
|
-
</q-td>
|
|
71
|
-
</template>
|
|
72
|
-
<template #body-cell-actions="{ row }">
|
|
73
|
-
<q-td>
|
|
19
|
+
<AdminBody>
|
|
20
|
+
<AdminSection>
|
|
21
|
+
<AdminStatistics :label="msg.title">
|
|
22
|
+
<AdminKpi
|
|
23
|
+
v-for="tile in statTiles"
|
|
24
|
+
:key="tile.id"
|
|
25
|
+
:label="tile.label"
|
|
26
|
+
:value="tile.count"
|
|
27
|
+
:sub="tile.hint"
|
|
28
|
+
:tone="tile.tone"
|
|
29
|
+
:selected="statusFilter === tile.id"
|
|
30
|
+
:action="() => onStatusTileClick(tile.id)"
|
|
31
|
+
/>
|
|
32
|
+
</AdminStatistics>
|
|
33
|
+
</AdminSection>
|
|
34
|
+
|
|
35
|
+
<AdminSection class="sa-promo-codes__card">
|
|
36
|
+
<AdminFilters class="q-mb-lg">
|
|
37
|
+
<q-input
|
|
38
|
+
v-model="filter.search"
|
|
39
|
+
outlined
|
|
40
|
+
dense
|
|
41
|
+
:label="msg.list.searchLabel"
|
|
42
|
+
clearable
|
|
43
|
+
/>
|
|
44
|
+
<q-select
|
|
45
|
+
v-model="filter.status"
|
|
46
|
+
outlined
|
|
47
|
+
dense
|
|
48
|
+
emit-value
|
|
49
|
+
map-options
|
|
50
|
+
clearable
|
|
51
|
+
:label="common.status"
|
|
52
|
+
:options="statusOptions"
|
|
53
|
+
/>
|
|
54
|
+
</AdminFilters>
|
|
55
|
+
|
|
56
|
+
<AdminTable
|
|
57
|
+
:rows="filteredRows"
|
|
58
|
+
:columns="effectiveColumns"
|
|
59
|
+
:loading="loading"
|
|
60
|
+
storage-key="promo-codes"
|
|
61
|
+
>
|
|
62
|
+
<template #body-cell-status="{ row }">
|
|
63
|
+
<q-td>
|
|
64
|
+
<q-badge :color="statusColor(row.status)" :label="row.status" />
|
|
65
|
+
</q-td>
|
|
66
|
+
</template>
|
|
67
|
+
<template #row-actions="{ row }">
|
|
74
68
|
<slot name="row-actions" :row="row" :reload="reload">
|
|
75
69
|
<q-btn
|
|
76
70
|
v-for="action in visibleActions(row)"
|
|
@@ -83,10 +77,10 @@
|
|
|
83
77
|
@click="action.handler(row)"
|
|
84
78
|
/>
|
|
85
79
|
</slot>
|
|
86
|
-
</
|
|
87
|
-
</
|
|
88
|
-
</
|
|
89
|
-
</
|
|
80
|
+
</template>
|
|
81
|
+
</AdminTable>
|
|
82
|
+
</AdminSection>
|
|
83
|
+
</AdminBody>
|
|
90
84
|
|
|
91
85
|
<PromoCodeCreateDialog
|
|
92
86
|
v-if="enableCreate && submitCreate"
|
|
@@ -112,6 +106,7 @@
|
|
|
112
106
|
// `<script setup>` (the whole setup section is wrapped in setup()).
|
|
113
107
|
// Pure helpers + constants therefore live here in the regular `<script>` block.
|
|
114
108
|
|
|
109
|
+
import AdminTable from '../components/admin-page/AdminTable.vue';
|
|
115
110
|
import type { PromoCodePlanOption } from '../components/dialogs/types.js';
|
|
116
111
|
|
|
117
112
|
const PLAN_COLOR_PALETTE = ['#0ea5e9', '#10b981', '#f59e0b', '#7c3aed', '#dc2626', '#64748b'];
|
|
@@ -133,8 +128,13 @@ export function computePlanColors(
|
|
|
133
128
|
|
|
134
129
|
<script setup lang="ts">
|
|
135
130
|
import { computed, onMounted, reactive, ref } from 'vue';
|
|
131
|
+
import AdminBody from '../components/admin-page/AdminBody.vue';
|
|
132
|
+
import AdminFilters from '../components/admin-page/AdminFilters.vue';
|
|
136
133
|
import AdminHero from '../components/admin-page/AdminHero.vue';
|
|
134
|
+
import AdminSection from '../components/admin-page/AdminSection.vue';
|
|
135
|
+
import AdminKpi from '../components/admin-page/AdminKpi.vue';
|
|
137
136
|
import AdminPage from '../components/admin-page/AdminPage.vue';
|
|
137
|
+
import AdminStatistics from '../components/admin-page/AdminStatistics.vue';
|
|
138
138
|
import { useQuasar } from 'quasar';
|
|
139
139
|
import { formatMessage } from '../client/i18n/format.js';
|
|
140
140
|
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
@@ -251,11 +251,25 @@ function classifyRow(row: PromoRow): Exclude<StatusFilter, 'all'> | null {
|
|
|
251
251
|
return null;
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
// The table narrows; the tiles above it deliberately do not — see statTiles.
|
|
254
255
|
const filteredRows = computed(() => {
|
|
255
|
-
|
|
256
|
-
|
|
256
|
+
// `clearable` emits null, not '' — see Quasar's use-field clearValue().
|
|
257
|
+
const q = (filter.search ?? '').trim().toLowerCase();
|
|
258
|
+
const status = filter.status?.toUpperCase() ?? null;
|
|
259
|
+
|
|
260
|
+
return rows.value.filter((row) => {
|
|
261
|
+
if (statusFilter.value !== 'all' && classifyRow(row) !== statusFilter.value) return false;
|
|
262
|
+
if (status && String(row.status ?? '').toUpperCase() !== status) return false;
|
|
263
|
+
if (!q) return true;
|
|
264
|
+
return [row.code, row.description, row.campaignTag]
|
|
265
|
+
.filter((v): v is string => typeof v === 'string')
|
|
266
|
+
.some((v) => v.toLowerCase().includes(q));
|
|
267
|
+
});
|
|
257
268
|
});
|
|
258
269
|
|
|
270
|
+
// Absolute counts over everything the tenant has, deliberately independent of
|
|
271
|
+
// the filter below: these tiles are the status filter, so counting the filtered
|
|
272
|
+
// set would drop every unselected tile to zero the moment one is picked.
|
|
259
273
|
const statTiles = computed<
|
|
260
274
|
Array<{
|
|
261
275
|
id: StatusFilter;
|
|
@@ -291,22 +305,8 @@ const statTiles = computed<
|
|
|
291
305
|
];
|
|
292
306
|
});
|
|
293
307
|
|
|
294
|
-
// Tile click: set the filter AND, if a server status match exists, pass it
|
|
295
|
-
// through to the search so `loadPromos` can pre-filter server-side if needed.
|
|
296
308
|
function onStatusTileClick(id: StatusFilter): void {
|
|
297
309
|
statusFilter.value = id;
|
|
298
|
-
const serverStatus =
|
|
299
|
-
id === 'active'
|
|
300
|
-
? 'ACTIVE'
|
|
301
|
-
: id === 'paused'
|
|
302
|
-
? 'PAUSED'
|
|
303
|
-
: id === 'expired'
|
|
304
|
-
? 'EXPIRED'
|
|
305
|
-
: null;
|
|
306
|
-
if (serverStatus !== filter.status) {
|
|
307
|
-
filter.status = serverStatus;
|
|
308
|
-
reload();
|
|
309
|
-
}
|
|
310
310
|
}
|
|
311
311
|
|
|
312
312
|
const baseColumns = computed(() => [
|
|
@@ -394,14 +394,6 @@ const mergedActions = computed<readonly PromoRowAction[]>(() => [
|
|
|
394
394
|
|
|
395
395
|
const effectiveColumns = computed(() => {
|
|
396
396
|
const cols = [...baseColumns.value];
|
|
397
|
-
if (mergedActions.value.length > 0) {
|
|
398
|
-
cols.push({
|
|
399
|
-
name: 'actions',
|
|
400
|
-
label: '',
|
|
401
|
-
field: 'id' as never,
|
|
402
|
-
align: 'right' as 'left',
|
|
403
|
-
});
|
|
404
|
-
}
|
|
405
397
|
return cols;
|
|
406
398
|
});
|
|
407
399
|
|
|
@@ -419,10 +411,9 @@ function statusColor(status: string): string {
|
|
|
419
411
|
async function reload() {
|
|
420
412
|
loading.value = true;
|
|
421
413
|
try {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
});
|
|
414
|
+
// Unfiltered on purpose: the tiles count the tenant's full set, and
|
|
415
|
+
// the table narrows locally, so filtering costs no round trip.
|
|
416
|
+
rows.value = await props.loadPromos({});
|
|
426
417
|
} catch (err) {
|
|
427
418
|
rows.value = [];
|
|
428
419
|
console.warn('[PromoCodesPage] loadPromos failed:', err);
|
|
@@ -559,21 +550,4 @@ function formatDate(iso: string | Date | null | undefined): string | null {
|
|
|
559
550
|
}
|
|
560
551
|
</script>
|
|
561
552
|
|
|
562
|
-
<style scoped>
|
|
563
|
-
.sa-promo-codes__filter {
|
|
564
|
-
display: flex;
|
|
565
|
-
gap: 10px;
|
|
566
|
-
margin-bottom: 12px;
|
|
567
|
-
flex-wrap: wrap;
|
|
568
|
-
}
|
|
569
|
-
.sa-promo-codes__filter > * {
|
|
570
|
-
flex: 1;
|
|
571
|
-
min-width: 200px;
|
|
572
|
-
}
|
|
573
|
-
.sa-promo-codes__card {
|
|
574
|
-
background: #fff;
|
|
575
|
-
border: 1px solid var(--sa-border, #e2e8f0);
|
|
576
|
-
border-radius: 12px;
|
|
577
|
-
overflow: hidden;
|
|
578
|
-
}
|
|
579
|
-
</style>
|
|
553
|
+
<style scoped></style>
|
|
@@ -6,25 +6,25 @@
|
|
|
6
6
|
</template>
|
|
7
7
|
</AdminHero>
|
|
8
8
|
|
|
9
|
-
<
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
flat
|
|
9
|
+
<AdminBody>
|
|
10
|
+
<AdminSection class="sa-subscriptions__body">
|
|
11
|
+
<AdminTable
|
|
13
12
|
:rows="rows"
|
|
14
13
|
:columns="effectiveColumns"
|
|
15
|
-
row-key="id"
|
|
16
|
-
:pagination="{ rowsPerPage: 0 }"
|
|
17
14
|
:loading="loading"
|
|
18
|
-
|
|
15
|
+
storage-key="subscriptions"
|
|
19
16
|
/>
|
|
20
|
-
</
|
|
21
|
-
</
|
|
17
|
+
</AdminSection>
|
|
18
|
+
</AdminBody>
|
|
22
19
|
</AdminPage>
|
|
23
20
|
</template>
|
|
24
21
|
|
|
25
22
|
<script setup lang="ts">
|
|
23
|
+
import AdminTable from '../components/admin-page/AdminTable.vue';
|
|
26
24
|
import { computed, onMounted, ref } from 'vue';
|
|
25
|
+
import AdminBody from '../components/admin-page/AdminBody.vue';
|
|
27
26
|
import AdminHero from '../components/admin-page/AdminHero.vue';
|
|
27
|
+
import AdminSection from '../components/admin-page/AdminSection.vue';
|
|
28
28
|
import AdminPage from '../components/admin-page/AdminPage.vue';
|
|
29
29
|
import { useSaMessages, useSuperAdminI18n } from '../vue/use-super-admin-i18n.js';
|
|
30
30
|
|
|
@@ -128,11 +128,4 @@ function formatDate(iso: string | null | undefined): string | null {
|
|
|
128
128
|
}
|
|
129
129
|
</script>
|
|
130
130
|
|
|
131
|
-
<style scoped>
|
|
132
|
-
.sa-subscriptions__card {
|
|
133
|
-
background: #fff;
|
|
134
|
-
border: 1px solid var(--sa-border, #e2e8f0);
|
|
135
|
-
border-radius: 12px;
|
|
136
|
-
overflow: hidden;
|
|
137
|
-
}
|
|
138
|
-
</style>
|
|
131
|
+
<style scoped></style>
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
<h1 class="sa-login-title">{{ msg.login.signIn }}</h1>
|
|
25
25
|
<p v-if="subtitle" class="sa-login-subtitle">{{ subtitle }}</p>
|
|
26
26
|
|
|
27
|
-
<q-form
|
|
27
|
+
<q-form class="sa-login-form" @submit.prevent="handleSubmit">
|
|
28
28
|
<q-input
|
|
29
29
|
v-model="form.email"
|
|
30
30
|
:label="msg.emailLabel"
|
|
@@ -111,7 +111,7 @@ interface Props {
|
|
|
111
111
|
subtitle?: string;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
defineProps<Props>();
|
|
115
115
|
|
|
116
116
|
const router = useRouter();
|
|
117
117
|
const msg = useSaMessages('shell');
|
|
@@ -199,7 +199,7 @@ async function handleSubmit(): Promise<void> {
|
|
|
199
199
|
on top of the centered card on short viewports. */
|
|
200
200
|
margin-left: auto;
|
|
201
201
|
align-self: flex-start;
|
|
202
|
-
color: var(--sa-login-tag-color,
|
|
202
|
+
color: var(--sa-login-tag-color, var(--sa-muted-dark));
|
|
203
203
|
}
|
|
204
204
|
.sa-login-wrap {
|
|
205
205
|
min-height: 100vh;
|
|
@@ -208,7 +208,7 @@ async function handleSubmit(): Promise<void> {
|
|
|
208
208
|
align-items: center;
|
|
209
209
|
justify-content: center;
|
|
210
210
|
padding: 24px;
|
|
211
|
-
background: linear-gradient(180deg,
|
|
211
|
+
background: linear-gradient(180deg, var(--sa-heading) 0%, #1e293b 100%);
|
|
212
212
|
}
|
|
213
213
|
.sa-login-card {
|
|
214
214
|
width: 420px;
|
|
@@ -234,7 +234,7 @@ async function handleSubmit(): Promise<void> {
|
|
|
234
234
|
overflow: hidden;
|
|
235
235
|
}
|
|
236
236
|
.sa-login-logo--text {
|
|
237
|
-
background: var(--sa-login-logo-bg, linear-gradient(135deg,
|
|
237
|
+
background: var(--sa-login-logo-bg, linear-gradient(135deg, var(--sa-muted-dark), #1e293b));
|
|
238
238
|
color: var(--sa-login-logo-color, #fff);
|
|
239
239
|
font-weight: 800;
|
|
240
240
|
font-size: 18px;
|
|
@@ -248,12 +248,12 @@ async function handleSubmit(): Promise<void> {
|
|
|
248
248
|
.sa-login-brand__name {
|
|
249
249
|
font-weight: 800;
|
|
250
250
|
font-size: 16px;
|
|
251
|
-
color:
|
|
251
|
+
color: var(--sa-heading);
|
|
252
252
|
line-height: 1.1;
|
|
253
253
|
}
|
|
254
254
|
.sa-login-brand__tag {
|
|
255
255
|
font-size: 12px;
|
|
256
|
-
color: var(--sa-login-tag-color,
|
|
256
|
+
color: var(--sa-login-tag-color, var(--sa-muted-dark));
|
|
257
257
|
text-transform: uppercase;
|
|
258
258
|
letter-spacing: 0.08em;
|
|
259
259
|
}
|
|
@@ -261,12 +261,12 @@ async function handleSubmit(): Promise<void> {
|
|
|
261
261
|
.sa-login-title {
|
|
262
262
|
font-size: 22px;
|
|
263
263
|
font-weight: 800;
|
|
264
|
-
color:
|
|
264
|
+
color: var(--sa-heading);
|
|
265
265
|
margin: 0 0 6px;
|
|
266
266
|
}
|
|
267
267
|
.sa-login-subtitle {
|
|
268
268
|
font-size: 13px;
|
|
269
|
-
color:
|
|
269
|
+
color: var(--sa-muted-dark);
|
|
270
270
|
margin: 0 0 20px;
|
|
271
271
|
line-height: 1.5;
|
|
272
272
|
}
|
|
@@ -290,13 +290,13 @@ async function handleSubmit(): Promise<void> {
|
|
|
290
290
|
.sa-login-hint {
|
|
291
291
|
margin-top: 18px;
|
|
292
292
|
padding: 10px 12px;
|
|
293
|
-
background:
|
|
293
|
+
background: var(--sa-border-soft);
|
|
294
294
|
border-radius: 8px;
|
|
295
295
|
font-size: 12px;
|
|
296
|
-
color:
|
|
296
|
+
color: var(--sa-muted-dark);
|
|
297
297
|
}
|
|
298
298
|
.sa-login-hint code {
|
|
299
|
-
background:
|
|
299
|
+
background: var(--sa-border);
|
|
300
300
|
padding: 1px 5px;
|
|
301
301
|
border-radius: 4px;
|
|
302
302
|
}
|