"use client"; import { useCallback, useEffect, useState } from 'react'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { Button } from '@/components/ui/button'; import { TextInput } from '@/components/ui/TextInput/TextInput'; import { Plus, Search, Copy, Trash2, Edit, Eye, RefreshCw } from 'lucide-react'; import { cn } from '@/lib/utils'; interface FunnelListItem { _id: string; name: string; description?: string; status: 'draft' | 'published' | 'archived'; version: number; createdAt: string; updatedAt: string; publishedAt?: string; usage: { totalViews: number; totalCompletions: number; lastUsed?: string; }; funnelData?: { meta?: { id?: string; title?: string; description?: string; }; }; } interface PaginationInfo { current: number; total: number; count: number; totalItems: number; } export default function AdminCatalogPage() { const [funnels, setFunnels] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const router = useRouter(); // Фильтры и поиск const [searchQuery, setSearchQuery] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); const [sortBy, setSortBy] = useState('updatedAt'); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); // Пагинация const [pagination, setPagination] = useState({ current: 1, total: 1, count: 0, totalItems: 0 }); // Выделенные элементы - TODO: реализовать в будущем // const [selectedFunnels, setSelectedFunnels] = useState>(new Set()); // Загрузка данных const loadFunnels = useCallback(async (page: number = 1) => { try { setLoading(true); setError(null); const params = new URLSearchParams({ page: page.toString(), limit: '20', sortBy, sortOrder, ...(searchQuery && { search: searchQuery }), ...(statusFilter !== 'all' && { status: statusFilter }) }); const response = await fetch(`/api/funnels?${params}`); if (!response.ok) { throw new Error('Failed to fetch funnels'); } const data = await response.json(); setFunnels(data.funnels); setPagination({ current: data.pagination.current, total: data.pagination.total, count: data.pagination.count, totalItems: data.pagination.totalItems }); } catch (err) { setError(err instanceof Error ? err.message : 'Unknown error'); } finally { setLoading(false); } }, [searchQuery, statusFilter, sortBy, sortOrder]); // Эффекты useEffect(() => { loadFunnels(1); }, [loadFunnels]); // Создание новой воронки const handleCreateFunnel = async () => { try { const newFunnelData = { name: 'Новая воронка', description: 'Описание новой воронки', funnelData: { meta: { id: `funnel-${Date.now()}`, title: 'Новая воронка', description: 'Описание новой воронки', firstScreenId: 'screen-1' }, defaultTexts: { nextButton: 'Далее', continueButton: 'Продолжить' }, screens: [ { id: 'screen-1', template: 'info', title: { text: 'Добро пожаловать!', font: 'manrope', weight: 'bold' }, description: { text: 'Это ваша новая воронка. Начните редактирование.', color: 'muted' }, icon: { type: 'emoji', value: '🎯', size: 'lg' } } ] } }; const response = await fetch('/api/funnels', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(newFunnelData) }); if (!response.ok) { throw new Error('Failed to create funnel'); } const createdFunnel = await response.json(); // Переходим к редактированию новой воронки router.push(`/admin/builder/${createdFunnel._id}`); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to create funnel'); } }; // Дублирование воронки const handleDuplicateFunnel = async (funnelId: string, funnelName: string) => { try { const response = await fetch(`/api/funnels/${funnelId}/duplicate`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: `${funnelName} (копия)` }) }); if (!response.ok) { throw new Error('Failed to duplicate funnel'); } // Обновляем список loadFunnels(pagination.current); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to duplicate funnel'); } }; // Удаление воронки const handleDeleteFunnel = async (funnelId: string, funnelName: string) => { if (!confirm(`Вы уверены, что хотите удалить воронку "${funnelName}"?`)) { return; } try { const response = await fetch(`/api/funnels/${funnelId}`, { method: 'DELETE' }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to delete funnel'); } // Обновляем список loadFunnels(pagination.current); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to delete funnel'); } }; // Статус badges const getStatusBadge = (status: string) => { const variants = { draft: 'bg-yellow-100 text-yellow-800 border-yellow-200', published: 'bg-green-100 text-green-800 border-green-200', archived: 'bg-gray-100 text-gray-800 border-gray-200' }; const labels = { draft: 'Черновик', published: 'Опубликована', archived: 'Архивирована' }; return ( {labels[status as keyof typeof labels]} ); }; // Форматирование дат const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); }; return (
{/* Header */}

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

Управляйте своими воронками и создавайте новые

{/* Фильтры и поиск */}
{/* Поиск */}
setSearchQuery(e.target.value)} placeholder="Поиск по названию, описанию..." className="pl-10" />
{/* Фильтр статуса */} {/* Сортировка */}
{/* Ошибка */} {error && (
{error}
)} {/* Список воронок */}
{loading ? (
Загружается...
) : funnels.length === 0 ? (
Воронки не найдены
) : (
{funnels.map((funnel) => ( ))}
Название Статус Статистика Обновлена Действия
{funnel.name}
ID: {funnel.funnelData?.meta?.id || 'N/A'}
{funnel.description && (
{funnel.description}
)}
{getStatusBadge(funnel.status)}
{funnel.usage.totalViews} просмотров
{funnel.usage.totalCompletions} завершений
{formatDate(funnel.updatedAt)}
v{funnel.version}
{/* Просмотр воронки */} {/* Редактирование */} {/* Дублировать */} {/* Удалить (только черновики) */} {funnel.status === 'draft' && ( )}
)}
{/* Пагинация */} {pagination.total > 1 && (
Показано {pagination.count} из {pagination.totalItems} воронок
{pagination.current} / {pagination.total}
)}
); }