'use client' import React, { useCallback, useEffect, useState } from 'react' import { useParams, useRouter } from 'next/navigation' import { ApiError } from '@/lib/api/client' import { getFunnel, getFunnelStats, getFunnelTrends, deleteFunnel, type Funnel, type FunnelStats, type FunnelTrends } from '@/lib/api/funnels' import FilterBar from '@/components/dashboard/FilterBar' import AddFilterDropdown from '@/components/dashboard/AddFilterDropdown' import { type DimensionFilter, serializeFilters } from '@/lib/filters' import { toast, Select, DatePicker, ChevronLeftIcon, ArrowRightIcon, TrashIcon, Button } from '@ciphera-net/ui' import { PencilSimple } from '@phosphor-icons/react' import { FunnelDetailSkeleton, useMinimumLoading, useSkeletonFade } from '@/components/skeletons' import Link from 'next/link' import { FunnelChart } from '@/components/ui/funnel-chart' import { getDateRange } from '@ciphera-net/ui' import BreakdownDrawer from '@/components/funnels/BreakdownDrawer' import { ResponsiveContainer, LineChart, Line, XAxis, YAxis, Tooltip, CartesianGrid } from 'recharts' export default function FunnelReportPage() { const params = useParams() const router = useRouter() const siteId = params.id as string const funnelId = params.funnelId as string const [funnel, setFunnel] = useState(null) const [stats, setStats] = useState(null) const [loading, setLoading] = useState(true) const [dateRange, setDateRange] = useState(() => getDateRange(30)) const [datePreset, setDatePreset] = useState<'7' | '30' | 'custom'>('30') const [isDatePickerOpen, setIsDatePickerOpen] = useState(false) const [loadError, setLoadError] = useState<'not_found' | 'forbidden' | 'error' | null>(null) const [filters, setFilters] = useState([]) const [expandedExitStep, setExpandedExitStep] = useState(null) const [trends, setTrends] = useState(null) const [visibleSteps, setVisibleSteps] = useState>(new Set()) const [breakdownStep, setBreakdownStep] = useState(null) const loadData = useCallback(async () => { setLoadError(null) try { setLoading(true) const filterStr = serializeFilters(filters) || undefined const [funnelData, statsData, trendsData] = await Promise.all([ getFunnel(siteId, funnelId), getFunnelStats(siteId, funnelId, dateRange.start, dateRange.end, filterStr), getFunnelTrends(siteId, funnelId, dateRange.start, dateRange.end, 'day', filterStr) ]) setFunnel(funnelData) setStats(statsData) setTrends(trendsData) } catch (error) { const status = error instanceof ApiError ? error.status : 0 if (status === 404) setLoadError('not_found') else if (status === 403) setLoadError('forbidden') else setLoadError('error') if (status !== 404 && status !== 403) toast.error('Failed to load funnel details') } finally { setLoading(false) } }, [siteId, funnelId, dateRange, filters]) useEffect(() => { loadData() }, [loadData]) const handleDelete = async () => { if (!confirm('Are you sure you want to delete this funnel?')) return try { await deleteFunnel(siteId, funnelId) toast.success('Funnel deleted') router.push(`/sites/${siteId}/funnels`) } catch (error) { toast.error('Failed to delete funnel') } } const showSkeleton = useMinimumLoading(loading && !funnel) const fadeClass = useSkeletonFade(showSkeleton) if (showSkeleton) { return } if (loadError === 'not_found' || (!funnel && !stats && !loadError)) { return (

Funnel not found

) } if (loadError === 'forbidden') { return (

Access denied

) } if (loadError === 'error') { return (

Unable to load funnel

) } if (!funnel || !stats) { return (

Funnel not found

) } const chartData = stats.steps.map(s => ({ label: s.step.name, value: s.visitors, })) const STEP_COLORS = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6', '#EC4899', '#06B6D4', '#84CC16'] const trendsChartData = trends ? trends.dates.map((date, idx) => { const point: Record = { date: new Date(date).toLocaleDateString('en-GB', { day: 'numeric', month: 'short' }), overall: Math.round(trends.overall[idx] * 10) / 10, } for (const [stepKey, values] of Object.entries(trends.steps)) { if (visibleSteps.has(stepKey)) { point[`step_${stepKey}`] = Math.round(values[idx] * 10) / 10 } } return point }) : [] return (

{funnel.name}

{funnel.description && (

{funnel.description}

)}