gendesign/frontend/src/components/analytics/PrinzipFunnelMonthly.tsx
lekss361 522ecca5c4 ship #13-16: multi-target Dockerfile, per-object drill-in, CRM pipeline,
442-sweep instructions
#16 Dockerfile: runner (lean, 1.6 GB) + runner-with-chromium (2.9 GB).
   Compose + deploy.yml pushes 2 GHCR images. Backend saves 1.3 GB.
#15 5 backend object endpoints + /analytics/objects/[id] page (gallery,
   POI table, sale chart) + sparkline-driven PrinzipObjectsTable.
#14 prinzip_leads/deals + 2 MVs. Import script with PII-hashing &
   --inspect-only mode. 3 funnel endpoints + Sankey/Monthly charts.
#13 README section with UI/CLI/SQL for 442-object sweep.
2026-04-27 19:32:37 +03:00

96 lines
2.7 KiB
TypeScript

"use client";
import { useMemo } from "react";
import { usePrinzipFunnelMonthly } from "@/lib/analytics-api";
import { ChartShell } from "./ChartShell";
export function PrinzipFunnelMonthly({ months = 24 }: { months?: number }) {
const { data, isLoading } = usePrinzipFunnelMonthly(months);
const option = useMemo(() => {
const rows = data ?? [];
if (rows.length === 0) {
return {
title: {
text: "Нет данных CRM",
left: "center",
top: "middle",
textStyle: { color: "#9ca3af", fontWeight: 400, fontSize: 13 },
},
xAxis: { show: false },
yAxis: { show: false },
};
}
// Aggregate by month (sum across sources).
const byMonth: Record<
string,
{ leads: number; engaged: number; converted: number }
> = {};
for (const r of rows) {
const m = (r.month ?? "").slice(0, 7);
if (!byMonth[m]) byMonth[m] = { leads: 0, engaged: 0, converted: 0 };
byMonth[m].leads += r.leads;
byMonth[m].engaged += r.engaged;
byMonth[m].converted += r.converted;
}
const months = Object.keys(byMonth).sort();
const leads = months.map((m) => byMonth[m].leads);
const engaged = months.map((m) => byMonth[m].engaged);
const converted = months.map((m) => byMonth[m].converted);
const conv = months.map((m) =>
byMonth[m].leads > 0
? Math.round((byMonth[m].converted / byMonth[m].leads) * 1000) / 10
: null,
);
return {
tooltip: { trigger: "axis" },
legend: { data: ["Заявки", "Engaged", "Сделки", "Conversion %"] },
grid: { left: 56, right: 64, top: 40, bottom: 36 },
xAxis: { type: "category", data: months },
yAxis: [
{ type: "value", name: "шт", position: "left" },
{
type: "value",
name: "%",
position: "right",
axisLabel: { formatter: "{value}%" },
},
],
series: [
{
name: "Заявки",
type: "bar",
data: leads,
itemStyle: { color: "#9ca3af" },
},
{
name: "Engaged",
type: "bar",
data: engaged,
itemStyle: { color: "#3b82f6" },
},
{
name: "Сделки",
type: "bar",
data: converted,
itemStyle: { color: "#0a7a3a" },
},
{
name: "Conversion %",
type: "line",
yAxisIndex: 1,
smooth: true,
data: conv,
lineStyle: { color: "#c2410c", width: 2 },
itemStyle: { color: "#c2410c" },
},
],
};
}, [data, months]);
return (
<ChartShell option={option} loading={isLoading} height={320} notMerge />
);
}