"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, 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 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 [projectId, setProjectId] = useState(null); const canWrite = hasPermission("placements_write"); useEffect(() => { loadPlacement(); }, [workspaceId, placementId]); const loadPlacement = async () => { try { setLoading(true); // First, try to load placement without project_id (deprecated endpoint) // If that fails, we'd need projectId const data = await placementsApi.get(workspaceId, placementId); setPlacement(data); // Store projectId from placement for future operations if (data.project?.id) { setProjectId(data.project.id); } } catch (err) { console.error("Failed to load placement:", err); } finally { setLoading(false); } }; 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 cost = placement.details?.cost?.value; const subscriptionsCount = placement.placement_post?.subscriptions_count ?? 0; const viewsCount = placement.placement_post?.views_count ?? 0; const cps = cost && subscriptionsCount > 0 ? cost / subscriptionsCount : null; const cpm = cost && viewsCount > 0 ? (cost / viewsCount) * 1000 : null; return ( <>
{/* Header */}

{placement.channel.title}

{placement.status === "Оплачено" ? "Активно" : placement.status}

{placement.details?.placement_at ? formatDate(placement.details.placement_at) : "—"}

{/* Stats Cards */}
Стоимость
{formatCurrency(cost)}
Подписчиков
{formatNumber(subscriptionsCount)}

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

Просмотров
{formatNumber(viewsCount)}

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

Тип ссылки
{placement.invite_link_type === "approval" ? "С заявками" : "Публичная"}
{/* Invite Link */} Пригласительная ссылка {placement.invite_link_type === "approval" ? "С одобрением — бот отслеживает подписчиков" : "Публичная ссылка"}
{placement.invite_link || "—"} {placement.invite_link && ( )}
{/* Creative */} Креатив ID: {placement.creative_id} {placement.creative_id && ( )}
{/* Details */} Детали размещения
Канал размещения
{placement.channel.title}
Дата размещения
{placement.details?.placement_at ? formatDate(placement.details.placement_at) : "—"}
Дата оплаты
{placement.details?.payment_at ? formatDate(placement.details.payment_at) : "—"}
Формат
{placement.details?.format || "—"}
{placement.placement_post?.post?.url && (
Ссылка на пост
{placement.placement_post.post.url}
)} {placement.comment && (
Комментарий
{placement.comment}
)}
); }