import { useEffect, useState } from "react";
import { api } from "../../api.js";
import InfoHint from "./InfoHint.jsx";
import MiniLineChart from "./MiniLineChart.jsx";
/**
* CustomerHealthSection — the three remaining signal consumers in one block.
*
* retention — D1 / D7 / D30 cohort return rates
* satisfaction — NPS-style score from thumbs_up/thumbs_down (per-week trend)
* engagement — behavioral segments (deep_divers, explorers, etc.)
*
* All three are GLOBAL (don't depend on the inspected user) and CUMULATIVE
* in nature — they describe long-running product health metrics. The date
* window selector applies via the `days` parameter.
*/
export default function CustomerHealthSection({ windowLabel = "", days = 30 }) {
const [data, setData] = useState(null);
const [err, setErr] = useState(null);
const [busy, setBusy] = useState(false);
async function load() {
setBusy(true);
setErr(null);
try {
const d = await api.customerHealth(days, 4);
setData(d);
} catch (e) {
setErr(e.message);
setData(null);
} finally {
setBusy(false);
}
}
// Refetch on mount AND whenever the parent's window changes.
useEffect(() => { load(); /* eslint-disable-next-line */ }, [days]);
if (err) {
return (
Failed/analytics/customer-health
{err}
);
}
if (!data) {
return (
Loading customer health…
);
}
return (
Customer health
{windowLabel}
Retention cohorts, NPS-style satisfaction score, and behavioral
engagement segments — the three signal consumers that turn{" "}
thumbs_up, thumbs_down,{" "}
deeper_question, and session-continuity signals into
product-health rollups.
Cohort return-rate analysis. Users are grouped by the week they were
first seen; D1 / D7 / D30 retention = % of that cohort
that had at least one more turn within 1 / 7 / 30 days of their
first visit. Only mature cohorts (old enough to measure) contribute
to the overall average.
Score = (positive% − negative%) × 100 from
thumbs_up / thumbs_down signals.
Range [−100, +100].{" "}
Very satisfied ≥ 50 · Mixed 0-50 · Concerning < 0.
{" "}Note: thumbs_up/down are not used to update the bandit
(they're ambiguous about cause) — but they ARE the cleanest signal
for overall satisfaction.
);
}
// ─── Engagement card ──────────────────────────────────────────────────────
function EngagementCard({ engagement }) {
const e = engagement || {};
const segments = e.segments || [];
const total = e.total_users || 0;
const max = Math.max(...segments.map((s) => s.count), 1);
return (
Engagement segments
Behavioral segmentation. Each active user falls into exactly one segment
based on their in-window pattern:
Deep divers: >5 deeper_questions on ≤3 topics — they want depth on a few subjects.
Explorers: >3 topics covered — broad curiosity.
Power users: >20 turns AND >2 sessions — habitual.
One-and-done: ≤2 turns lifetime — never came back after first try.
Casual: everyone else — moderate usage.
{total} active user{total === 1 ? "" : "s"} in window