diff --git a/app/sites/[id]/journeys/layout.tsx b/app/sites/[id]/journeys/layout.tsx new file mode 100644 index 0000000..6e6c0f3 --- /dev/null +++ b/app/sites/[id]/journeys/layout.tsx @@ -0,0 +1,9 @@ +import type { Metadata } from 'next' + +export const metadata: Metadata = { + title: 'Journeys | Pulse', +} + +export default function JourneysLayout({ children }: { children: React.ReactNode }) { + return children +} diff --git a/app/sites/[id]/journeys/page.tsx b/app/sites/[id]/journeys/page.tsx new file mode 100644 index 0000000..e2b16a1 --- /dev/null +++ b/app/sites/[id]/journeys/page.tsx @@ -0,0 +1,179 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useParams } from 'next/navigation' +import { getDateRange, formatDate } from '@ciphera-net/ui' +import { Select, DatePicker } from '@ciphera-net/ui' +import SankeyDiagram from '@/components/journeys/SankeyDiagram' +import TopPathsTable from '@/components/journeys/TopPathsTable' +import { SkeletonCard } from '@/components/skeletons' +import { + useDashboard, + useJourneyTransitions, + useJourneyTopPaths, + useJourneyEntryPoints, +} from '@/lib/swr/dashboard' + +function getThisWeekRange(): { start: string; end: string } { + const today = new Date() + const dayOfWeek = today.getDay() + const monday = new Date(today) + monday.setDate(today.getDate() - (dayOfWeek === 0 ? 6 : dayOfWeek - 1)) + return { start: formatDate(monday), end: formatDate(today) } +} + +function getThisMonthRange(): { start: string; end: string } { + const today = new Date() + const firstOfMonth = new Date(today.getFullYear(), today.getMonth(), 1) + return { start: formatDate(firstOfMonth), end: formatDate(today) } +} + +export default function JourneysPage() { + const params = useParams() + const siteId = params.id as string + + const [period, setPeriod] = useState('30') + const [dateRange, setDateRange] = useState(() => getDateRange(30)) + const [isDatePickerOpen, setIsDatePickerOpen] = useState(false) + const [depth, setDepth] = useState(5) + const [entryPath, setEntryPath] = useState('') + + const { data: transitionsData, isLoading: transitionsLoading } = useJourneyTransitions( + siteId, dateRange.start, dateRange.end, depth, 3, entryPath || undefined + ) + const { data: topPaths, isLoading: topPathsLoading } = useJourneyTopPaths( + siteId, dateRange.start, dateRange.end, 20, 3, entryPath || undefined + ) + const { data: entryPoints } = useJourneyEntryPoints(siteId, dateRange.start, dateRange.end) + const { data: dashboard } = useDashboard(siteId, dateRange.start, dateRange.end) + + useEffect(() => { + const domain = dashboard?.site?.domain + document.title = domain ? `Journeys \u00b7 ${domain} | Pulse` : 'Journeys | Pulse' + }, [dashboard?.site?.domain]) + + const entryPointOptions = [ + { value: '', label: 'All entry points' }, + ...(entryPoints ?? []).map((ep) => ({ + value: ep.path, + label: `${ep.path} (${ep.sessions.toLocaleString()})`, + })), + ] + + return ( +
+ {/* Header */} +
+
+

+ Journeys +

+

+ How visitors navigate through your site +

+
+ setDepth(Number(e.target.value))} + className="w-32 accent-brand-orange" + /> + {depth} +
+ +