feat(ops): wall board härlett ur summary-värden
This commit is contained in:
@@ -13,6 +13,8 @@ import type {
|
||||
OpsSubscriptionsBlock,
|
||||
OpsStoreBlock,
|
||||
OpsFeedbackBlock,
|
||||
OpsWallBlock,
|
||||
OpsWallTile,
|
||||
} from "@app/shared-types";
|
||||
import { SUBSCRIPTION_PLANS, type SubscriptionPlan } from "@app/shared-types";
|
||||
import type { Database } from "./client.js";
|
||||
@@ -45,6 +47,24 @@ function asNumber(value: unknown): number {
|
||||
return Number(value ?? 0);
|
||||
}
|
||||
|
||||
function formatSekKr(oer: number | null): string {
|
||||
if (oer === null || Number.isNaN(oer)) return "—";
|
||||
const kr = oer / 100;
|
||||
// Mellanslag (0x20) som tusentalsavgränsare, utan ören.
|
||||
const formatted = new Intl.NumberFormat("sv-SE", { maximumFractionDigits: 0 }).format(kr);
|
||||
return `${formatted.replace(/\s/g, " ")} kr`;
|
||||
}
|
||||
|
||||
function formatPercent(ratio: number | null): string {
|
||||
if (ratio === null || Number.isNaN(ratio)) return "—";
|
||||
return `${Math.round(ratio * 100)} %`;
|
||||
}
|
||||
|
||||
function formatInt(n: number | null): string {
|
||||
if (n === null || Number.isNaN(n)) return "—";
|
||||
return String(n);
|
||||
}
|
||||
|
||||
function safeDiv(numerator: number, denominator: number): number | null {
|
||||
if (denominator === 0) return null;
|
||||
return numerator / denominator;
|
||||
@@ -533,6 +553,45 @@ function storeBlock(): OpsStoreBlock {
|
||||
return { butik: null, all_fields_phase_2: null };
|
||||
}
|
||||
|
||||
export function buildWall(summary: OpsSummary): OpsWallBlock {
|
||||
const tiles: OpsWallTile[] = [
|
||||
{
|
||||
label: "MRR",
|
||||
value: formatSekKr(summary.ekonomi.mrr),
|
||||
tone: summary.ekonomi.mrr === null ? "warn" : "ok",
|
||||
},
|
||||
{
|
||||
label: "Aktiva nu",
|
||||
value: formatInt(summary.anvandare.aktiva_nu),
|
||||
tone: "ok",
|
||||
},
|
||||
{
|
||||
label: "Betalande",
|
||||
value: formatInt(summary.prenumerationer.betalande),
|
||||
tone: "ok",
|
||||
},
|
||||
{
|
||||
label: "Trial-konv",
|
||||
value: formatPercent(summary.prenumerationer.konverteringsgrad_30d),
|
||||
tone: summary.prenumerationer.konverteringsgrad_30d === null ? "warn" : "ok",
|
||||
},
|
||||
{
|
||||
label: "Workers",
|
||||
value: summary.jobb.workers_ok ? "OK" : "NERE",
|
||||
tone: summary.jobb.workers_ok ? "ok" : "warn",
|
||||
},
|
||||
{
|
||||
label: "Allergen-brott",
|
||||
value: formatInt(summary.sakerhet.allergen_invariant_brott),
|
||||
tone: summary.sakerhet.allergen_invariant_brott === 0 ? "ok" : "warn",
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
boards: [{ title: "Cibello", tiles }],
|
||||
};
|
||||
}
|
||||
|
||||
async function feedbackBlock(db: Database): Promise<OpsFeedbackBlock> {
|
||||
const counts = await db.execute(sql`
|
||||
SELECT
|
||||
@@ -589,7 +648,7 @@ export async function computeOpsSummary(options: ComputeOpsSummaryOptions): Prom
|
||||
safetyBlock(db),
|
||||
]);
|
||||
|
||||
return {
|
||||
const base: OpsSummary = {
|
||||
app,
|
||||
ekonomi: economy,
|
||||
anvandare: users,
|
||||
@@ -602,6 +661,11 @@ export async function computeOpsSummary(options: ComputeOpsSummaryOptions): Prom
|
||||
betalning: payment,
|
||||
jobb: queueSummary ?? defaultQueueBlock(),
|
||||
sakerhet: safety,
|
||||
wall: { boards: [] },
|
||||
as_of: new Date().toISOString(),
|
||||
};
|
||||
|
||||
base.wall = buildWall(base);
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user