refactor(portal): delete data/customers.ts fixture entirely
Replace the last two holdouts: the dashboard partner-identity fallback now uses the real useMe().partner name, and the decorative MRR sparkline (dashboard + reports) moves to a generated useMrrTrendline() — deterministic, clearly placeholder-only, until a daily MRR-snapshot job exists. Removes the dead sparkLast/sparkTrendPct vars. The data/customers.ts fixture module is now fully deleted; the partner portal carries no mock business data.
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
// Decorative MRR trendline for the partner dashboard + reports sparklines.
|
||||||
|
//
|
||||||
|
// Historical MRR isn't stored yet (that needs a daily MRR-snapshot job, same
|
||||||
|
// future work as the operator metrics pipeline), so the sparkline shape is
|
||||||
|
// PURELY VISUAL — a deterministic ease-out ramp with a gentle wave so it reads
|
||||||
|
// as organic growth. It is not real data and must not be presented as such;
|
||||||
|
// when a real series lands, swap this for the snapshot query. Deterministic
|
||||||
|
// (no Math.random) so SSR and client renders match.
|
||||||
|
export function useMrrTrendline(points = 90): number[] {
|
||||||
|
return Array.from({ length: points }, (_, i) => {
|
||||||
|
const t = i / (points - 1)
|
||||||
|
const eased = 1 - (1 - t) ** 2
|
||||||
|
return Math.round(38000 + eased * 17000 + Math.sin(i / 7) * 320)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
// Remaining partner fixtures. `partner` (dashboard fallback identity) and
|
|
||||||
// `partnerMrrSparkline` (decorative dashboard/reports sparkline) are the last
|
|
||||||
// two holdouts — kept until the dashboard partner header and a real historical
|
|
||||||
// MRR series replace them. The customer list now comes from /api/partner/tenants.
|
|
||||||
|
|
||||||
export const partner = {
|
|
||||||
id: 'p-nordicmsp',
|
|
||||||
name: 'NordicMSP',
|
|
||||||
domain: 'nordicmsp.dk',
|
|
||||||
contact: 'Anne Baslund',
|
|
||||||
email: 'partners@nordicmsp.dk',
|
|
||||||
marginPct: 20,
|
|
||||||
customers: 8,
|
|
||||||
brandColor: '#3F6BFF',
|
|
||||||
founded: '2024',
|
|
||||||
}
|
|
||||||
|
|
||||||
// 90-day MRR sparkline · matches the synthetic generator at partner-screens.jsx:198.
|
|
||||||
// Deterministic seeded values (no Math.random calls each render).
|
|
||||||
export const partnerMrrSparkline = [
|
|
||||||
38580, 38980, 39520, 40180, 40720, 41080, 41420, 41820, 42100, 42460,
|
|
||||||
42820, 43240, 43620, 43980, 44320, 44660, 44960, 45280, 45680, 46020,
|
|
||||||
46300, 46580, 46880, 47220, 47540, 47820, 48160, 48420, 48760, 49080,
|
|
||||||
49420, 49680, 50020, 50360, 50640, 50920, 51180, 51460, 51720, 51980,
|
|
||||||
52220, 52480, 52720, 52960, 53180, 53420, 53620, 53860, 54080, 54260,
|
|
||||||
54440, 54620, 54780, 54940, 55080, 55180, 55280, 55360, 55440, 55510,
|
|
||||||
55570, 55610, 55650, 55670, 55690, 55700, 55710, 55720, 55730, 55740,
|
|
||||||
55745, 55748, 55750, 55750, 55750, 55750, 55750, 55750, 55750, 55750,
|
|
||||||
55750, 55750, 55750, 55750, 55750, 55750, 55750, 55750, 55750, 55750,
|
|
||||||
]
|
|
||||||
@@ -6,18 +6,16 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
import { partnerMrrSparkline, partner as fixturePartner } from '~/data/customers'
|
|
||||||
import type { CustomerOrg } from '~/types/partner'
|
import type { CustomerOrg } from '~/types/partner'
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const partnerMode = usePartnerMode()
|
const partnerMode = usePartnerMode()
|
||||||
|
|
||||||
// Real partner identity from /api/me; falls back to the fixture if the user
|
// Real partner identity from /api/me. Middleware redirects non-partner users
|
||||||
// somehow lands here without partner data (middleware should've redirected
|
// away, but we default the name defensively so the header never crashes.
|
||||||
// them, but defending the read keeps the page from crashing).
|
|
||||||
const { partner: realPartner } = useMe()
|
const { partner: realPartner } = useMe()
|
||||||
const partner = computed(() => realPartner.value ?? fixturePartner)
|
const partnerName = computed(() => realPartner.value?.name ?? 'Partner')
|
||||||
|
|
||||||
const wizardOpen = ref(false)
|
const wizardOpen = ref(false)
|
||||||
const entryCustomer = ref<CustomerOrg | null>(null)
|
const entryCustomer = ref<CustomerOrg | null>(null)
|
||||||
@@ -102,12 +100,9 @@ const newUsers30d = computed(
|
|||||||
const customersDelta = computed(() => (newCustomers30d.value > 0 ? `+${newCustomers30d.value} / 30d` : ''))
|
const customersDelta = computed(() => (newCustomers30d.value > 0 ? `+${newCustomers30d.value} / 30d` : ''))
|
||||||
const usersDelta = computed(() => (newUsers30d.value > 0 ? `+${newUsers30d.value} / 30d` : ''))
|
const usersDelta = computed(() => (newUsers30d.value > 0 ? `+${newUsers30d.value} / 30d` : ''))
|
||||||
|
|
||||||
// Sparkline is still fixture-driven — historical MRR isn't stored yet, so
|
// Decorative MRR sparkline shape — historical MRR isn't stored yet (see
|
||||||
// the chart shape is decorative. Keep it for the design until we wire a
|
// useMrrTrendline). Purely visual until a daily MRR-snapshot job exists.
|
||||||
// daily MRR snapshot job.
|
const sparkline = useMrrTrendline()
|
||||||
const sparkline = partnerMrrSparkline
|
|
||||||
const sparkLast = sparkline[sparkline.length - 1]
|
|
||||||
const sparkTrendPct = '18.2' // matches source label
|
|
||||||
|
|
||||||
// Attention list — derived from real tenant state (no fixtures). Surfaces
|
// Attention list — derived from real tenant state (no fixtures). Surfaces
|
||||||
// suspended customers, provisioning errors, seat pressure, and pending/trial
|
// suspended customers, provisioning errors, seat pressure, and pending/trial
|
||||||
@@ -270,7 +265,7 @@ function provisioned() {
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<PageHeader
|
<PageHeader
|
||||||
:eyebrow="`${partner.name} · Partner console`"
|
:eyebrow="`${partnerName} · Partner console`"
|
||||||
title="Portfolio overview"
|
title="Portfolio overview"
|
||||||
:subtitle="`${totalCustomers} customer organizations · ${totalUsers} end users · ${totalsLine} MRR${hasCustomPriced ? ' (+ custom-priced)' : ''}`"
|
:subtitle="`${totalCustomers} customer organizations · ${totalUsers} end users · ${totalsLine} MRR${hasCustomPriced ? ' (+ custom-priced)' : ''}`"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -8,14 +8,16 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Decorative MRR sparkline shape only — historical MRR isn't stored yet (a
|
|
||||||
// daily-snapshot job lands later). The live numbers below are all real.
|
|
||||||
import { partnerMrrSparkline } from '~/data/customers'
|
|
||||||
import type { CustomerOrg, CustomerStatus } from '~/types/partner'
|
import type { CustomerOrg, CustomerStatus } from '~/types/partner'
|
||||||
import type { TaskContext } from '~/components/partner/CustomerTaskPanel.vue'
|
import type { TaskContext } from '~/components/partner/CustomerTaskPanel.vue'
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
|
// Decorative MRR sparkline shape only — historical MRR isn't stored yet (a
|
||||||
|
// daily-snapshot job lands later; see useMrrTrendline). The live numbers
|
||||||
|
// below are all real.
|
||||||
|
const mrrTrend = useMrrTrendline()
|
||||||
|
|
||||||
// ── Real data sources ─────────────────────────────────────────────────────
|
// ── Real data sources ─────────────────────────────────────────────────────
|
||||||
const { tenants } = usePartnerTenants()
|
const { tenants } = usePartnerTenants()
|
||||||
const { mrrByTenant } = usePartnerMrr()
|
const { mrrByTenant } = usePartnerMrr()
|
||||||
@@ -450,7 +452,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="big-chart">
|
<div class="big-chart">
|
||||||
<PartnerSparkline :values="partnerMrrSparkline" :width="1080" :height="160" stroke="var(--text)" fill="var(--row-hover)" />
|
<PartnerSparkline :values="mrrTrend" :width="1080" :height="160" stroke="var(--text)" fill="var(--row-hover)" />
|
||||||
<div class="chart-foot">
|
<div class="chart-foot">
|
||||||
<span>90-day trend · illustrative</span>
|
<span>90-day trend · illustrative</span>
|
||||||
<span>{{ totalMrr.toLocaleString('da-DK') }} DKK / mo now</span>
|
<span>{{ totalMrr.toLocaleString('da-DK') }} DKK / mo now</span>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// Shared partner-domain types. These were previously coupled to the fixture
|
// Shared partner-domain types. The partner UI is now fully on real data (the
|
||||||
// module (data/customers.ts), which blocked deleting the mock data without
|
// old data/customers.ts fixture module has been deleted); these view-model
|
||||||
// breaking every type-only import. Living here, the types outlive the
|
// types live here and are imported by pages and composables. CustomerOrg in
|
||||||
// fixtures: pages and composables import from `~/types/partner`, and the
|
// particular remains the shape the customer screens bind to, populated by
|
||||||
// fixture data exports can be removed page-by-page as each one goes real.
|
// mapping real /api/partner/tenants rows onto it.
|
||||||
|
|
||||||
// Currencies the platform prices in. Subscriptions are always reported
|
// Currencies the platform prices in. Subscriptions are always reported
|
||||||
// per-currency (never FX-summed), so this propagates through MRR + billing.
|
// per-currency (never FX-summed), so this propagates through MRR + billing.
|
||||||
|
|||||||
Reference in New Issue
Block a user