"use client"; // ============================================================================ // Spending Analytics Page - Enhanced with Charts // ============================================================================ import { useEffect, useState, useMemo } from "react"; import { useParams } from "next/navigation"; import { Loader2, TrendingUp, BarChart3, LineChart as LineChartIcon, Info } from "lucide-react"; import { LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, LabelList, } from "recharts"; import { DashboardHeader } from "@/components/layout/dashboard-header"; import { useWorkspace } from "@/components/providers/workspace-provider"; import { demoAwareAnalyticsApi } from "@/lib/demo"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, type ChartConfig, } from "@/components/ui/chart"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; import type { SpendingAnalytics, DateGrouping } from "@/lib/types/api"; // ============================================================================ // Helpers // ============================================================================ const formatCurrency = (value: number | null) => { if (value === null) return "—"; return new Intl.NumberFormat("ru-RU", { style: "currency", currency: "RUB", minimumFractionDigits: 0, maximumFractionDigits: 0, }).format(value); }; const formatNumber = (value: number | null) => { if (value === null) return "—"; return new Intl.NumberFormat("ru-RU").format(value); }; const formatShortCurrency = (value: number) => { if (value >= 1000000) return `${(value / 1000000).toFixed(1)}M`; if (value >= 1000) return `${(value / 1000).toFixed(0)}K`; return value.toString(); }; const formatDateLabel = (date: string, grouping: DateGrouping) => { const d = new Date(date); const g = grouping.toLowerCase(); if (g === "day") { return d.toLocaleDateString("ru-RU", { day: "numeric", month: "short" }); } else if (g === "week") { return `Нед. ${d.toLocaleDateString("ru-RU", { day: "numeric", month: "short" })}`; } else { return d.toLocaleDateString("ru-RU", { month: "short", year: "2-digit" }); } }; // ============================================================================ // Chart Types // ============================================================================ type ChartType = "line" | "bar"; type MetricKey = "cost" | "subscriptions" | "views"; // ============================================================================ // Chart Configurations // ============================================================================ const chartConfig: ChartConfig = { cost: { label: "Расходы", color: "hsl(var(--chart-1))", }, subscriptions: { label: "Подписчики", color: "hsl(var(--chart-2))", }, views: { label: "Просмотры", color: "hsl(var(--chart-3))", }, }; // ============================================================================ // Component // ============================================================================ export default function SpendingAnalyticsPage() { const params = useParams(); const workspaceId = params?.workspaceId as string; const { selectedProjects, getProjectFilterId, allProjectsSelected } = useWorkspace(); const projectFilterId = getProjectFilterId(); const [spending, setSpending] = useState(null); const [loading, setLoading] = useState(true); const [grouping, setGrouping] = useState("month"); // Chart settings const [chartType, setChartType] = useState("line"); const [showLabels, setShowLabels] = useState(false); const [selectedMetrics, setSelectedMetrics] = useState(["cost", "subscriptions"]); useEffect(() => { loadData(); }, [workspaceId, projectFilterId, grouping]); const loadData = async () => { try { setLoading(true); const data = await demoAwareAnalyticsApi.spending(workspaceId, { project_id: projectFilterId, grouping, }); setSpending(data); } catch (err) { console.error("Failed to load spending:", err); } finally { setLoading(false); } }; // Format chart data const chartData = useMemo(() => { if (!spending?.chart_data) return []; return spending.chart_data.map((item) => ({ period: item.period, dateLabel: formatDateLabel(item.period, grouping), cost: item.cost, subscriptions: item.subscriptions, views: item.views, })); }, [spending, grouping]); // Toggle metric const handleToggleMetric = (metric: MetricKey) => { if (selectedMetrics.includes(metric)) { if (selectedMetrics.length > 1) { setSelectedMetrics(selectedMetrics.filter((m) => m !== metric)); } } else { setSelectedMetrics([...selectedMetrics, metric]); } }; // Render chart const renderChart = () => { if (chartData.length === 0) return null; const ChartComponent = chartType === "line" ? LineChart : BarChart; return ( { if (payload && payload[0]) { return payload[0].payload.period; } return ""; }} /> } /> } /> {selectedMetrics.map((metric) => chartType === "line" ? ( {showLabels && ( metric === "cost" ? formatShortCurrency(value) : formatNumber(value) } className="fill-foreground text-[10px]" /> )} ) : ( {showLabels && ( metric === "cost" ? formatShortCurrency(value) : formatNumber(value) } className="fill-foreground text-[10px]" /> )} ) )} ); }; return (

Динамика расходов

Анализ расходов по периодам

{loading ? (
) : spending ? ( <> {/* Summary Cards */}
Всего потрачено
{formatCurrency(spending.total_cost)}
Подписчиков
{formatNumber(spending.total_subscriptions)}
Просмотров
{formatNumber(spending.total_views)}
{/* Chart with Settings */} {chartData.length > 0 && (
График динамики Визуализация данных по выбранным метрикам
{/* Chart Settings */}
{/* Chart Type Toggle */}
{/* Labels Toggle */}
setShowLabels(checked === true)} /> Показывать значения на точках графика
{/* Metrics Selection */}
Метрики: {(["cost", "subscriptions", "views"] as MetricKey[]).map((metric) => (
handleToggleMetric(metric)} />
{renderChart()} )} {/* Data Table */} {spending.chart_data && spending.chart_data.length > 0 ? ( Данные по периодам {spending.chart_data.length} периодов Период Расходы Подписчики Просмотры
CPS Cost Per Subscription
CPM Cost Per Mille (1000 views)
{spending.chart_data.map((item, index) => { const cps = item.subscriptions > 0 ? item.cost / item.subscriptions : null; const cpm = item.views > 0 ? (item.cost / item.views) * 1000 : null; return ( {item.period} {formatCurrency(item.cost)} {formatNumber(item.subscriptions)} {formatNumber(item.views)} {cps ? formatCurrency(cps) : "—"} {cpm ? formatCurrency(cpm) : "—"} ); })}
) : (

Нет данных

Создайте размещения для отображения статистики

)} ) : null}
); }