"use client"; // ============================================================================ // Projects Page (Target Channels) - Enhanced with Statistics // ============================================================================ import { useEffect, useState } from "react"; import { useParams } from "next/navigation"; import Link from "next/link"; import { Loader2, Tv, ExternalLink, Grid, List, TrendingUp, Users, Eye, ShoppingCart, Info, } from "lucide-react"; 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 { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; import { BOT_USERNAME } from "@/lib/utils/constants"; import type { Project, SpendingAnalytics } from "@/lib/types/api"; // ============================================================================ // Types // ============================================================================ interface ProjectWithStats extends Project { stats?: { total_cost: number; total_subscriptions: number; total_views: number; avg_cps: number | null; }; } // ============================================================================ // Helpers // ============================================================================ const formatNumber = (value: number | null | undefined): string => { if (value === null || value === undefined) return "—"; return new Intl.NumberFormat("ru-RU").format(value); }; const formatCurrency = (value: number | null | undefined): string => { if (value === null || value === undefined) return "—"; return new Intl.NumberFormat("ru-RU", { style: "currency", currency: "RUB", minimumFractionDigits: 0, maximumFractionDigits: 0, }).format(value); }; // ============================================================================ // Component // ============================================================================ export default function ProjectsPage() { const params = useParams(); const workspaceId = params?.workspaceId as string; const { projects, isLoadingProjects, currentWorkspace } = useWorkspace(); const [projectsWithStats, setProjectsWithStats] = useState([]); const [loadingStats, setLoadingStats] = useState(false); const [viewMode, setViewMode] = useState<"grid" | "table">("grid"); // Load statistics for each project useEffect(() => { if (projects.length === 0) { setProjectsWithStats([]); return; } const loadStats = async () => { setLoadingStats(true); try { const statsPromises = projects.map(async (project) => { try { const spending = await demoAwareAnalyticsApi.spending(workspaceId, { project_id: project.id, }); return { ...project, stats: { total_cost: spending.total_cost, total_subscriptions: spending.total_subscriptions, total_views: spending.total_views, avg_cps: spending.total_subscriptions > 0 ? spending.total_cost / spending.total_subscriptions : null, }, }; } catch { return { ...project, stats: undefined }; } }); const results = await Promise.all(statsPromises); setProjectsWithStats(results); } catch (err) { console.error("Failed to load project stats:", err); setProjectsWithStats(projects); } finally { setLoadingStats(false); } }; loadStats(); }, [projects, workspaceId]); const isLoading = isLoadingProjects || loadingStats; return (

Проекты

Telegram каналы, которые вы продвигаете

Чтобы добавить новый проект, добавьте бота{" "} @{BOT_USERNAME} {" "} администратором в ваш Telegram канал. {isLoading ? (
) : projectsWithStats.length === 0 ? (

Нет проектов

Добавьте бота администратором в ваш Telegram канал, и он автоматически появится здесь как проект.

) : viewMode === "table" ? ( // Table View Канал Статус
Потрачено Сумма всех размещений по проекту
Подписчики Привлеченные подписчики по пригласительным ссылкам
Просмотры Суммарные просмотры рекламных постов
Сред. CPS Cost Per Subscription — средняя стоимость подписчика
{projectsWithStats.map((project) => (
{project.title}
{project.username && ( @{project.username} )}
{project.status === "active" ? "Активен" : project.status === "inactive" ? "Неактивен" : "Архив"} {formatCurrency(project.stats?.total_cost)} {formatNumber(project.stats?.total_subscriptions)} {formatNumber(project.stats?.total_views)} {formatCurrency(project.stats?.avg_cps)}
))}
) : ( // Grid View
{projectsWithStats.map((project) => (
{project.title} {project.username && ( @{project.username} )}
{project.status === "active" ? "Активен" : project.status === "inactive" ? "Неактивен" : "Архив"}
{/* Stats Grid */} {project.stats && (
{formatCurrency(project.stats.total_cost)}
Потрачено
{formatNumber(project.stats.total_subscriptions)}
Подписчиков
{formatNumber(project.stats.total_views)}
Просмотров
{formatCurrency(project.stats.avg_cps)}
Сред. CPS
)} {/* Actions */}
{project.username && ( )}
))}
)}
); }