import { useState } from 'react'; import { useQuery } from '../lib/hooks'; import { num, when } from '../lib/format'; import { Banner, Card, EmptyRow, Field, Loading, PageHead, TableWrap, Tag, Tiles, } from '../components/ui'; interface SearchTerm { query: string; searches: number; viewers: number; lastAt: string; } interface SearchEvent { occurredAt: string; userId: string; username: string; query: string; } interface SearchesResponse { days: number; retentionDays: number; totals: { searches: number; queries: number; viewers: number }; terms: SearchTerm[] | null; recent: SearchEvent[] | null; } /* Two tables of the same rows on purpose: the summary groups by query and answers "what does this house look for", which is what a library is organised against; the log is uncollapsed and newest-first and answers "what happened just now", which is the one to read when somebody reports that search is not finding something. */ export function SearchesPage() { const [days, setDays] = useState(7); const { data, error, loading } = useQuery(`/admin/api/searches?days=${days}`); const terms = data?.terms ?? []; const recent = data?.recent ?? []; const totals = data?.totals; return ( <> {loading ? ( ) : ( <> } > {terms.length === 0 ? ( Nothing searched in this window. ) : ( terms.map((term) => ( )) )}
Query Searches Viewers Last searched
{term.query} {num(term.searches)} {num(term.viewers)} {when(term.lastAt)}
{recent.length === 0 ? ( No searches in this window. ) : ( recent.map((event, index) => ( {/* An unattributed search keeps its row and shows the id: the query is the point, and a viewer whose sessions have all expired is still one searcher rather than nobody. */} )) )}
When Viewer Query
{when(event.occurredAt)} {event.username || {event.userId || 'unknown'}} {event.query}
)} ); }