"use client"; // ============================================================================ // Purchases List Page // ============================================================================ import React, { useEffect, useState } from "react"; import Link from "next/link"; import { DashboardHeader } from "@/components/layout/dashboard-header"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { purchasesApi, channelsApi } from "@/lib/api"; import { formatNumber, formatMetric, formatCurrency, formatDate, formatPercent, } from "@/lib/utils/format"; import { ShoppingCart, Loader2, AlertCircle, Plus, Search, Filter, Eye, CheckCircle, Clock, Archive, } from "lucide-react"; import type { Purchase, TargetChannel } from "@/lib/types/api"; import { Alert, AlertDescription } from "@/components/ui/alert"; export default function PurchasesPage() { const [purchases, setPurchases] = useState([]); const [channels, setChannels] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [searchQuery, setSearchQuery] = useState(""); const [filterChannel, setFilterChannel] = useState("all"); const [filterStatus, setFilterStatus] = useState("all"); useEffect(() => { loadData(); }, []); const loadData = async () => { try { setLoading(true); const [purchasesRes, channelsRes] = await Promise.all([ purchasesApi.list(), channelsApi.list({}), ]); setPurchases(purchasesRes.data); setChannels(channelsRes.data); } catch (err: any) { setError(err?.error?.message || "Ошибка загрузки данных"); } finally { setLoading(false); } }; const filteredPurchases = purchases.filter((purchase) => { // Поиск const matchesSearch = purchase.external_channel.title .toLowerCase() .includes(searchQuery.toLowerCase()) || purchase.target_channel.title .toLowerCase() .includes(searchQuery.toLowerCase()) || purchase.creative.name.toLowerCase().includes(searchQuery.toLowerCase()); // Фильтр по каналу const matchesChannel = filterChannel === "all" || purchase.target_channel.id === filterChannel; // Фильтр по статусу let matchesStatus = true; if (filterStatus === "completed") { matchesStatus = !!purchase.actual_date; } else if (filterStatus === "scheduled") { matchesStatus = !purchase.actual_date; } else if (filterStatus === "archived") { matchesStatus = purchase.is_archived; } return matchesSearch && matchesChannel && matchesStatus; }); // Статистика const stats = { total: purchases.length, completed: purchases.filter((p) => p.actual_date).length, scheduled: purchases.filter((p) => !p.actual_date).length, totalCost: purchases.reduce((sum, p) => sum + (p.cost || 0), 0), totalSubscriptions: purchases.reduce( (sum, p) => sum + p.subscriptions_count, 0 ), avgCpf: purchases.length > 0 ? purchases.reduce((sum, p) => sum + (p.cpf || 0), 0) / purchases.length : 0, }; return ( <>

Закупы

Рекламные размещения во внешних каналах

{error && ( {error} )}
Всего закупов
{formatNumber(stats.total)}

{stats.completed} завершено, {stats.scheduled} запланировано

Общие затраты
{formatCurrency(stats.totalCost)}

За все закупы

Подписчиков привлечено
{formatNumber(stats.totalSubscriptions)}

Всего

Средний CPF
₽{formatMetric(stats.avgCpf)}

По всем закупам

Фильтры
setSearchQuery(e.target.value)} className="pl-8" />
Найдено: {formatNumber(filteredPurchases.length)}
Список закупов Все рекламные размещения {loading ? (
) : filteredPurchases.length === 0 ? (

{searchQuery || filterChannel !== "all" || filterStatus !== "all" ? "Закупы не найдены" : "Нет закупов"}

{searchQuery || filterChannel !== "all" || filterStatus !== "all" ? "Попробуйте изменить фильтры" : "Создайте первый закуп"}

) : ( Статус Целевой канал Внешний канал Креатив Дата Стоимость Подписчики CPF Конверсия {filteredPurchases.map((purchase) => ( {purchase.is_archived ? ( Архив ) : purchase.actual_date ? ( Завершено ) : ( Запланировано )} {purchase.target_channel.title} {purchase.external_channel.title} {purchase.creative.name} {formatDate( purchase.actual_date || purchase.scheduled_date )} {formatCurrency(purchase.cost)} {formatNumber(purchase.subscriptions_count)} {purchase.cpf ? `₽${formatMetric(purchase.cpf)}` : "—"} {purchase.conversion_rate ? formatPercent(purchase.conversion_rate) : "—"} ))}
)}
); }