Files
tgex-frontend/app/(dashboard)/creatives/[id]/page.tsx
2025-11-19 12:56:04 +03:00

287 lines
8.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
// ============================================================================
// Creative Detail Page
// ============================================================================
import React, { useEffect, useState } from "react";
import { use } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
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 { Alert, AlertDescription } from "@/components/ui/alert";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { creativesApi } from "@/lib/api";
import {
formatNumber,
formatMetric,
formatCurrency,
formatDate,
} from "@/lib/utils/format";
import {
Folder,
ArrowLeft,
Loader2,
AlertCircle,
Archive,
Trash2,
BarChart3,
Users,
TrendingUp,
} from "lucide-react";
import type { Creative } from "@/lib/types/api";
interface PageProps {
params: Promise<{ id: string }>;
}
export default function CreativeDetailPage({ params }: PageProps) {
const resolvedParams = use(params);
const router = useRouter();
const [creative, setCreative] = useState<Creative | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const loadCreative = async () => {
try {
setLoading(true);
const data = await creativesApi.get(resolvedParams.id);
setCreative(data);
} catch (err: any) {
setError(err?.error?.message || "Ошибка загрузки креатива");
} finally {
setLoading(false);
}
};
loadCreative();
}, [resolvedParams.id]);
const handleArchive = async () => {
if (!creative) return;
try {
await creativesApi.update(creative.id, {
status: creative.status === "active" ? "archived" : "active",
});
setCreative({
...creative,
status: creative.status === "active" ? "archived" : "active",
});
} catch (err: any) {
alert(err?.error?.message || "Ошибка обновления креатива");
}
};
const handleDelete = async () => {
if (!creative) return;
if (!confirm(`Удалить креатив "${creative.name}"?`)) return;
try {
await creativesApi.delete(creative.id);
router.push("/creatives");
} catch (err: any) {
alert(err?.error?.message || "Ошибка удаления креатива");
}
};
if (loading) {
return (
<>
<DashboardHeader
breadcrumbs={[
{ label: "Креативы", href: "/creatives" },
{ label: "Загрузка..." },
]}
/>
<div className="flex flex-1 items-center justify-center">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
</>
);
}
if (error || !creative) {
return (
<>
<DashboardHeader
breadcrumbs={[
{ label: "Креативы", href: "/creatives" },
{ label: "Ошибка" },
]}
/>
<div className="flex flex-1 flex-col gap-4 p-4 pt-0 mt-4">
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{error || "Креатив не найден"}</AlertDescription>
</Alert>
<Button asChild variant="outline">
<Link href="/creatives">
<ArrowLeft className="mr-2 h-4 w-4" />
Вернуться к креативам
</Link>
</Button>
</div>
</>
);
}
const previewText = creative.text.replace(
"{link}",
"https://t.me/+InviteLinkExample"
);
return (
<>
<DashboardHeader
breadcrumbs={[
{ label: "Главная", href: "/" },
{ label: "Креативы", href: "/creatives" },
{ label: creative.name },
]}
/>
<div className="flex flex-1 flex-col gap-4 p-4 pt-0 mt-4">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-2 mb-2">
<Folder className="h-6 w-6" />
<h1 className="text-3xl font-bold tracking-tight">
{creative.name}
</h1>
{creative.status === "archived" && (
<Badge variant="secondary">Архив</Badge>
)}
</div>
<div className="text-sm text-muted-foreground">
Целевой канал: {creative.target_channel_title}
</div>
</div>
<div className="flex gap-2">
<Button variant="outline" onClick={handleArchive}>
<Archive className="mr-2 h-4 w-4" />
{creative.status === "archived"
? "Разархивировать"
: "Архивировать"}
</Button>
<Button variant="destructive" onClick={handleDelete}>
<Trash2 className="mr-2 h-4 w-4" />
Удалить
</Button>
</div>
</div>
<div className="grid gap-4 md:grid-cols-3">
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Всего закупов
</CardTitle>
<BarChart3 className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">
{formatNumber(creative.placements_count)}
</div>
<p className="text-xs text-muted-foreground mt-1">
Всего размещений
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
Целевой канал
</CardTitle>
<Users className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-lg font-medium">
{creative.target_channel_title}
</div>
<p className="text-xs text-muted-foreground mt-1">
Канал для рекламы
</p>
</CardContent>
</Card>
</div>
<div className="grid gap-4 md:grid-cols-2">
<Card>
<CardHeader>
<CardTitle>Текст креатива</CardTitle>
<CardDescription>Шаблон с переменной {"{link}"}</CardDescription>
</CardHeader>
<CardContent>
<div className="rounded-lg border bg-muted/50 p-4">
<pre className="text-sm whitespace-pre-wrap font-sans">
{creative.text}
</pre>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Предпросмотр</CardTitle>
<CardDescription>
С подставленной пригласительной ссылкой
</CardDescription>
</CardHeader>
<CardContent>
<div className="rounded-lg border bg-muted/50 p-4">
<p className="text-sm whitespace-pre-wrap">{previewText}</p>
</div>
</CardContent>
</Card>
</div>
<Card>
<CardHeader>
<CardTitle>Информация о размещениях</CardTitle>
<CardDescription>Статистика использования креатива</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div>
<h4 className="text-sm font-medium mb-2">Всего размещений:</h4>
<p className="text-2xl font-bold">
{creative.placements_count}
</p>
</div>
<div>
<h4 className="text-sm font-medium mb-2">Создан:</h4>
<p className="text-sm">{formatDate(creative.created_at)}</p>
</div>
<div>
<Button asChild variant="outline" className="w-full">
<Link href={`/purchases?creative_id=${creative.id}`}>
Посмотреть все размещения
</Link>
</Button>
</div>
</div>
</CardContent>
</Card>
</div>
</>
);
}