"use client"; // ============================================================================ // Channels Catalog Page - Enhanced with Sorting // ============================================================================ import { useEffect, useState, useMemo } from "react"; import { useParams } from "next/navigation"; import { Loader2, Search, Building2, Users, ExternalLink, Grid, List, ArrowUpDown, ArrowUp, ArrowDown, Info, } from "lucide-react"; import { DashboardHeader } from "@/components/layout/dashboard-header"; import { channelsApi } from "@/lib/api"; import { isDemoWorkspace, demoChannels } from "@/lib/demo"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { useWorkspace } from "@/components/providers/workspace-provider"; import type { Channel, Project } from "@/lib/types/api"; // ============================================================================ // Helpers // ============================================================================ const formatNumber = (value: number | null | undefined): string => { if (value === null || value === undefined) return "—"; return new Intl.NumberFormat("ru-RU").format(value); }; // ============================================================================ // Sort Types // ============================================================================ type SortField = "title" | "username" | "subscribers"; type SortOrder = "asc" | "desc" | null; // ============================================================================ // Component // ============================================================================ export default function ChannelsCatalogPage() { const params = useParams(); const workspaceId = params?.workspaceId as string; const { currentProject, projects } = useWorkspace(); const [channels, setChannels] = useState([]); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(""); const [viewMode, setViewMode] = useState<"table" | "grid">("table"); const [sortField, setSortField] = useState(null); const [sortOrder, setSortOrder] = useState(null); // Placement dialog const [selectedChannel, setSelectedChannel] = useState(null); const [showPlacementDialog, setShowPlacementDialog] = useState(false); const [placementProjectId, setPlacementProjectId] = useState(""); useEffect(() => { const loadChannels = async () => { try { setLoading(true); // Demo mode - use mock data const isDemo = isDemoWorkspace(workspaceId); const response = isDemo ? { items: demoChannels.filter(c => !search || c.username?.includes(search) || c.title.includes(search)), total: demoChannels.length, page: 1, size: 100, pages: 1 } : await channelsApi.list({ username: search || undefined, size: 100, }); setChannels(response.items); } catch (err) { console.error("Failed to load channels:", err); } finally { setLoading(false); } }; const debounce = setTimeout(loadChannels, 300); return () => clearTimeout(debounce); }, [search]); // Handle sort const handleSort = (field: SortField) => { if (sortField === field) { if (sortOrder === "asc") { setSortOrder("desc"); } else if (sortOrder === "desc") { setSortField(null); setSortOrder(null); } else { setSortOrder("asc"); } } else { setSortField(field); setSortOrder("asc"); } }; const getSortIcon = (field: SortField) => { if (sortField !== field) return ; if (sortOrder === "asc") return ; if (sortOrder === "desc") return ; return ; }; const handleOpenPlacement = (channel: Channel) => { setSelectedChannel(channel); if (currentProject) { setPlacementProjectId(currentProject.id); } setShowPlacementDialog(true); }; const handleConfirmPlacement = () => { if (!placementProjectId || !selectedChannel) return; window.location.href = `/dashboard/${workspaceId}/purchase-plans/${placementProjectId}?openPlacement=true&channel_id=${selectedChannel.id}`; }; const activeProjects = useMemo(() => { return projects.filter(p => p.status === "active"); }, [projects]); // Filter and sort channels const filteredChannels = useMemo(() => { let result = [...channels]; // Sort if (sortField && sortOrder) { result.sort((a, b) => { let aVal: string | number = ""; let bVal: string | number = ""; switch (sortField) { case "title": aVal = a.title.toLowerCase(); bVal = b.title.toLowerCase(); return sortOrder === "asc" ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal); case "username": aVal = (a.username || "").toLowerCase(); bVal = (b.username || "").toLowerCase(); return sortOrder === "asc" ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal); case "subscribers": aVal = a.subscribers_count || 0; bVal = b.subscribers_count || 0; return sortOrder === "asc" ? aVal - bVal : bVal - aVal; } return 0; }); } return result; }, [channels, sortField, sortOrder]); return (

Каталог каналов

{filteredChannels.length} каналов для размещения рекламы

setSearch(e.target.value)} className="pl-10" />
{loading ? (
) : channels.length === 0 ? (

Каналы не найдены

{search ? "Попробуйте изменить поисковый запрос" : "В каталоге пока нет каналов"}

) : viewMode === "table" ? (
Количество подписчиков на момент последнего обновления
{filteredChannels.map((channel) => (
{channel.title}
{channel.description && (
{channel.description}
)}
{channel.username ? ( @{channel.username} ) : ( )}
{formatNumber(channel.subscribers_count)}
))}
) : (
{filteredChannels.map((channel) => (
{channel.title} {channel.username && ( @{channel.username} )}
{channel.description && (

{channel.description}

)}
{formatNumber(channel.subscribers_count)}
))}
)}
Создание размещения Выберите проект для размещения на канале {selectedChannel?.title}
{activeProjects.length === 0 && (

Нет активных проектов. Создайте проект в настройках.

)}
); }