"use client"; // ============================================================================ // Placement Detail Page // ============================================================================ import { useEffect, useState } from "react"; import { useParams, useRouter } from "next/navigation"; import Link from "next/link"; import { Loader2, ArrowLeft, ExternalLink, Copy, Check, Archive, Eye, Users, TrendingUp, } from "lucide-react"; import { DashboardHeader } from "@/components/layout/dashboard-header"; import { useWorkspace } from "@/components/providers/workspace-provider"; import { placementsApi } from "@/lib/api"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import type { Placement } from "@/lib/types/api"; // ============================================================================ // Helpers // ============================================================================ const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString("ru-RU", { day: "numeric", month: "long", year: "numeric", }); }; const formatDateTime = (dateString: string | null | undefined) => { if (!dateString) return "—"; return new Date(dateString).toLocaleString("ru-RU", { day: "numeric", month: "short", year: "numeric", hour: "2-digit", minute: "2-digit", }); }; const formatCurrency = (value: number | null | undefined) => { if (value === null || value === undefined) return "—"; return new Intl.NumberFormat("ru-RU", { style: "currency", currency: "RUB", minimumFractionDigits: 0, maximumFractionDigits: 0, }).format(value); }; const formatNumber = (value: number | null | undefined) => { if (value === null || value === undefined) return "—"; return new Intl.NumberFormat("ru-RU").format(value); }; // ============================================================================ // Component // ============================================================================ export default function PlacementDetailPage() { const params = useParams(); const router = useRouter(); const workspaceId = params?.workspaceId as string; const placementId = params?.placementId as string; const { hasPermission } = useWorkspace(); const [placement, setPlacement] = useState(null); const [loading, setLoading] = useState(true); const [copiedLink, setCopiedLink] = useState(false); const canWrite = hasPermission("placements_write"); useEffect(() => { loadPlacement(); }, [workspaceId, placementId]); const loadPlacement = async () => { try { setLoading(true); const data = await placementsApi.get(workspaceId, placementId); setPlacement(data); } catch (err) { console.error("Failed to load placement:", err); } finally { setLoading(false); } }; const handleArchive = async () => { try { await placementsApi.delete(workspaceId, placementId); router.push(`/dashboard/${workspaceId}/placements`); } catch (err) { console.error("Failed to archive:", err); } }; const handleCopyLink = () => { if (placement?.invite_link) { navigator.clipboard.writeText(placement.invite_link); setCopiedLink(true); setTimeout(() => setCopiedLink(false), 2000); } }; if (loading) { return ( <>
); } if (!placement) { return ( <>

Размещение не найдено

); } // Calculate metrics const cps = placement.cost && placement.subscriptions_count > 0 ? placement.cost / placement.subscriptions_count : null; const cpm = placement.cost && placement.views_count ? (placement.cost / placement.views_count) * 1000 : null; return ( <>
{/* Header */}

{placement.placement_channel_title}

{placement.status === "active" ? "Активно" : "Архив"}

{formatDate(placement.placement_date)}

{canWrite && placement.status === "active" && ( Архивировать размещение? Размещение будет перемещено в архив. Статистика сохранится. Отмена Архивировать )}
{/* Stats Cards */}
Стоимость
{formatCurrency(placement.cost)}
Подписчиков
{formatNumber(placement.subscriptions_count)}

CPS: {cps ? formatCurrency(cps) : "—"}

Просмотров
{formatNumber(placement.views_count)}

CPM: {cpm ? formatCurrency(cpm) : "—"}

Тип ссылки
{placement.invite_link_type === "approval" ? "С заявками" : "Публичная"}
{/* Invite Link */} Пригласительная ссылка {placement.invite_link_type === "approval" ? "С одобрением — бот отслеживает подписчиков" : "Публичная ссылка"}
{placement.invite_link}
{/* Creative */} Креатив {placement.creative_name}
{/* Details */} Детали размещения
Проект
{placement.project_channel_title}
Дата размещения
{formatDate(placement.placement_date)}
Статус просмотров
{placement.views_availability === "available" ? "Автообновление" : placement.views_availability === "manual" ? "Ручной ввод" : placement.views_availability === "unavailable" ? "Недоступно" : "Не проверено"}
Последнее обновление просмотров
{formatDateTime(placement.last_views_fetch_at)}
{placement.ad_post_url && (
Ссылка на пост
{placement.ad_post_url}
)} {placement.comment && (
Комментарий
{placement.comment}
)}
); }