spb/company-atlas
Public
Python 66.3%
TypeScript 22.7%
JavaScript 8.6%
HTML 1.4%
CSS 0.7%
1import type { Metadata } from 'next';2import { Suspense } from 'react';3import { EventFilters } from '@/components/events/event-filters';4import { EventList } from '@/components/events/event-row';5import { EventTypeBadge } from '@/components/ui/badges';6import { Pagination, withParams } from '@/components/ui/pagination';7import { Container, Note, PageHeader, Unavailable } from '@/components/ui/section';8import { api, safe } from '@/lib/api';9import { fmtInt, fmtPctSigned } from '@/lib/format';10import { flat, int, str, type SP } from '@/lib/params';1112export const metadata: Metadata = { title: 'Events', description: 'Structured corporate events detected across monitored public surfaces, with filters by type, importance, confidence, country and industry.' };13export const revalidate = 60;1415export default async function EventsPage({ searchParams }: { searchParams: Promise<SP> }) {16 const sp = await searchParams;17 const cur = flat(sp);18 const page = int(sp.page, 1);19 const query = { event_type: str(sp.event_type), event_subtype: str(sp.event_subtype), country: str(sp.country), industry: str(sp.industry), min_importance: str(sp.min_importance), min_confidence: str(sp.min_confidence), q: str(sp.q), origin: str(sp.origin), surface: str(sp.surface), company: str(sp.company), sort: str(sp.sort), since: str(sp.since), until: str(sp.until), page, per_page: 50 };20 const [data, summary, countries, industries] = await Promise.all([safe(api.events(query)), safe(api.eventSummary(7, 'type')), safe(api.countries()), safe(api.industries())]);21 return (22 <Container wide>23 <PageHeader eyebrow="Events" title="Structured corporate events" lede="Each event is an interpreted change on a monitored public page, with the source URL, detection time, before/after values and a confidence label. Wording is deliberately careful: a listing that disappears is “no longer listed”, never more." />24 {summary?.items.length ? (25 <div className="no-scrollbar -mx-4 mb-4 flex gap-4 overflow-x-auto px-4 pb-1 md:mx-0 md:px-0">26 {summary.items.slice(0, 12).map((s) => (27 <div key={s.key} className="shrink-0">28 <EventTypeBadge type={s.key} small />29 <p className="tnum mt-0.5 text-sm font-medium">30 {fmtInt(s.count)} <span className={`text-[11px] font-normal ${(s.delta_pct ?? 0) > 0 ? 'text-positive' : (s.delta_pct ?? 0) < 0 ? 'text-danger' : 'text-ink-3'}`}>{s.delta_pct === null ? '' : fmtPctSigned(s.delta_pct, 0)}</span>31 </p>32 </div>33 ))}34 <p className="shrink-0 self-end text-[10px] uppercase tracking-wider text-ink-3">last 7 days vs previous 7</p>35 </div>36 ) : null}37 <Suspense>38 <EventFilters countries={(countries?.items ?? []).map((c) => ({ value: c.code, label: c.name }))} industries={(industries?.items ?? []).map((i) => ({ value: i.slug, label: i.name }))} className="mb-4" />39 </Suspense>40 <form method="get" action="/events" className="mb-3 flex max-w-lg gap-2">41 {Object.entries(cur)42 .filter(([k, v]) => k !== 'q' && k !== 'page' && v)43 .map(([k, v]) => (44 <input key={k} type="hidden" name={k} value={v} />45 ))}46 <input type="search" name="q" defaultValue={str(sp.q)} placeholder="Search event titles and summaries…" className="field flex-1" aria-label="Search events" />47 <button type="submit" className="btn">48 Search49 </button>50 </form>51 {!data ? (52 <Unavailable what="Events" />53 ) : (54 <>55 <p className="tnum mb-1 text-xs text-ink-3">{fmtInt(data.total)} events</p>56 <EventList events={data.items} variant="table" />57 <Pagination total={data.total} page={data.page} pages={data.pages} perPage={data.per_page} makeHref={(p) => withParams('/events', cur, { page: p > 1 ? p : undefined })} className="mt-4" />58 </>59 )}60 <Note className="mt-6">Retracted events remain visible with a strike-through and are excluded from metrics. Duplicate detections are merged into one canonical event whose sources list every surface that corroborated it.</Note>61 </Container>62 );63}64