/** * Saddle Analytics as ONE page (ANALYTICS-ONE-PAGE.md, like datafa.st): the * date row, the numbers and the chart in one block, then six cards two to a * row: Sources, Pages, Country, Tech, AI crawlers and Visitors. A card's * Details opens its full table in a window; a visitor opens in a side panel. * Both are the `&view=`, so Back and shared links work. The gear opens * Settings, which Core draws from the descriptor's schema. * * Data flows: rest.php → api.js → the hero, the cards and the windows. */ import { useCallback, useState } from 'react'; import { __, sprintf } from '@wordpress/i18n'; import { useUi } from './ui-context'; import Toolbar, { useRange } from './toolbar'; import Hero from './hero'; import Breakdown from './breakdown'; import { CrawlersCard } from './crawlers'; import { VisitorsCard } from './visitors'; import Details from './details'; import VisitorPanel from './visitor-panel'; import { readView, visitorView } from './address'; import { useApi } from './api'; import LoadError from './load-error'; const CARDS = () => [ { id: 'sources', title: __( 'Sources', 'saddle-analytics' ), dims: [ 'channel', 'referrer', 'campaign' ] }, { id: 'pages', title: __( 'Pages', 'saddle-analytics' ), dims: [ 'page', 'entry_page', 'exit_page', 'exit_link' ] }, { id: 'country', title: __( 'Country', 'saddle-analytics' ), dims: [ 'country' ] }, { id: 'tech', title: __( 'Tech', 'saddle-analytics' ), dims: [ 'browser', 'os', 'device' ] }, { id: 'crawlers', title: __( 'AI crawlers', 'saddle-analytics' ), kind: 'crawlers' }, { id: 'visitors', title: __( 'Visitors', 'saddle-analytics' ), kind: 'visitors' }, ]; export default function Overview( { view, navigate } ) { const { Card, Chip } = useUi(); const { data: meta, error, retry } = useApi( 'meta', {} ); const r = useRange(); const [ filters, setFilters ] = useState( {} ); const [ metric, setMetric ] = useState( 'visitors' ); const [ crawlerTab, setCrawlerTab ] = useState( 'all' ); // Where the visitor panel returns: the Visitors window when it was opened from there. const [ back, setBack ] = useState( '' ); const go = useCallback( ( next ) => navigate( { view: next } ), [ navigate ] ); const openVisitor = ( id, from = '' ) => { setBack( from ); go( visitorView( id ) ); }; // A row on a filterable card narrows every number; Pages rows do not. const addFilter = useCallback( ( dimension, value, label ) => { setFilters( ( prev ) => ( { ...prev, [ dimension ]: { value, label } } ) ); }, [] ); const removeFilter = ( key ) => setFilters( ( prev ) => { const next = { ...prev }; delete next[ key ]; return next; } ); if ( error ) { return ( ); } const cards = CARDS(); const open = readView( view, meta ? meta.dimensions.map( ( d ) => d.key ) : [] ); const card = 'dimension' === open.kind ? cards.find( ( c ) => c.dims?.includes( open.key ) ) : cards.find( ( c ) => c.kind === open.kind ); const filterParams = Object.fromEntries( Object.entries( filters ).map( ( [ k, f ] ) => [ k, f.value ] ) ); const dim = ( key ) => meta?.dimensions.find( ( d ) => d.key === key ); return (
navigate( { tab: 'settings' } ) } /> { Object.keys( filters ).length > 0 && (
{ Object.entries( filters ).map( ( [ key, f ] ) => ( removeFilter( key ) }> { sprintf( /* translators: 1: a report dimension such as Country, 2: its value such as France. */ __( '%1$s is %2$s', 'saddle-analytics' ), dim( key )?.label ?? key, f.label ) } ) ) }
) } { meta && (
{ cards .filter( ( c ) => c.dims ) .map( ( c ) => ( ) ) } { setCrawlerTab( tab ); go( 'crawlers' ); } } /> openVisitor( id ) } onDetails={ () => go( 'visitors' ) } />
) } { meta && (
go( '' ) } onFilter={ ( dimension, value, label ) => { addFilter( dimension, value, label ); go( '' ); } } onOpenVisitor={ ( id ) => openVisitor( id, 'visitors' ) } /> ) } { meta && ( go( 'visitors' === back ? 'visitors' : '' ) } /> ) }
); }