diff --git a/app/dashboard/[workspaceId]/creatives/page.tsx b/app/dashboard/[workspaceId]/creatives/page.tsx index 32aa487..9d25e00 100644 --- a/app/dashboard/[workspaceId]/creatives/page.tsx +++ b/app/dashboard/[workspaceId]/creatives/page.tsx @@ -74,6 +74,8 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { toast } from "sonner"; +import { handleApiError } from "@/lib/utils/error-handler"; import { BOT_USERNAME } from "@/lib/utils/constants"; import { ProjectSelector } from "@/components/project-selector"; import type { Creative, CreativeAnalyticsItem } from "@/lib/types/api"; @@ -157,17 +159,21 @@ export default function CreativesPage() { const [includeArchived, setIncludeArchived] = useState(false); const [viewMode, setViewMode] = useState<"table" | "grid">("table"); - const canWrite = hasPermission("placements_write"); + // Permission checks + const canReadCreatives = hasPermission("creatives_read"); + const canWriteCreatives = hasPermission("creatives_write"); + const canViewAnalytics = hasPermission("analytics_read") || hasPermission("analytics_without_clicks") || hasPermission("analytics_own_creatives"); + const canViewSubscriptions = hasPermission("analytics_read") || hasPermission("analytics_own_creatives"); useEffect(() => { loadCreatives(); }, [workspaceId, projectFilterId, includeArchived]); useEffect(() => { - if (creatives.length > 0) { + if (creatives.length > 0 && canViewAnalytics) { loadAnalytics(); } - }, [creatives, workspaceId, projectFilterId]); + }, [creatives, workspaceId, projectFilterId, canViewAnalytics]); const loadCreatives = async () => { try { @@ -179,13 +185,18 @@ export default function CreativesPage() { }); setCreatives(response.items); } catch (err) { - console.error("Failed to load creatives:", err); + handleApiError(err); } finally { setLoading(false); } }; const loadAnalytics = async () => { + if (!canViewAnalytics) { + setAnalyticsData([]); + return; + } + try { setLoadingAnalytics(true); const response = await demoAwareAnalyticsApi.creatives(workspaceId, { @@ -209,24 +220,42 @@ export default function CreativesPage() { }, [creatives, analyticsData]); const handleArchive = async (creative: Creative) => { - try { - const newStatus = creative.status === "active" ? "archived" : "active"; - await creativesApi.update(workspaceId, creative.id, { status: newStatus }); - loadCreatives(); - } catch (err) { - console.error("Failed to archive creative:", err); - } + await toast.promise( + (async () => { + const newStatus = creative.status === "active" ? "archived" : "active"; + await creativesApi.update(workspaceId, creative.id, { status: newStatus }); + await loadCreatives(); + })(), + { + loading: creative.status === "active" ? "Архивирование..." : "Восстановление...", + success: () => { + return creative.status === "active" + ? `Креатив «${creative.name}» архивирован` + : `Креатив «${creative.name}» восстановлен`; + }, + error: (err) => { + handleApiError(err); + return "Не удалось изменить статус"; + }, + } + ); }; - const handleDelete = async (creativeId: string) => { - if (!confirm("Удалить креатив? Это действие необратимо.")) return; - - try { - await creativesApi.delete(workspaceId, creativeId); - loadCreatives(); - } catch (err) { - console.error("Failed to delete creative:", err); - } + const handleDelete = async (creativeId: string, creativeName: string) => { + await toast.promise( + (async () => { + await creativesApi.delete(workspaceId, creativeId); + await loadCreatives(); + })(), + { + loading: "Удаление креатива...", + success: () => `Креатив «${creativeName}» удалён`, + error: (err) => { + handleApiError(err); + return "Не удалось удалить креатив"; + }, + } + ); }; @@ -254,14 +283,16 @@ export default function CreativesPage() { // Totals const totals = useMemo(() => { - const data = filteredCreatives.filter((c) => c.analytics); + const data = filteredCreatives.filter((c) => canViewAnalytics && c.analytics); return { placements: data.reduce((sum, c) => sum + (c.analytics?.placements_count || 0), 0), cost: data.reduce((sum, c) => sum + (c.analytics?.total_cost || 0), 0), - subscriptions: data.reduce((sum, c) => sum + (c.analytics?.total_subscriptions || 0), 0), + subscriptions: canViewSubscriptions + ? data.reduce((sum, c) => sum + (c.analytics?.total_subscriptions || 0), 0) + : 0, views: data.reduce((sum, c) => sum + (c.analytics?.total_views || 0), 0), }; - }, [filteredCreatives]); + }, [filteredCreatives, canViewAnalytics, canViewSubscriptions]); return ( @@ -300,7 +331,7 @@ export default function CreativesPage() { - {canWrite && ( + {canWriteCreatives && (