aai agency logo
OG Components Docs

NEW IN 0.7.0

Event Timeline

An asset events / history component. A clean, light, shadcn/Notion-style history feed of any asset's lifecycle — shown here with a well's: permits, spud, completion, stimulation, workovers, shut-ins — that opens a filled-out detail dialog on click. Works just as well for facilities, pads, pipelines, or equipment. Colors match the chart's annotation bands, so the asset reads consistently across the chart and its history.

Preview

A full single-well lifecycle. Filter by lifecycle group with the chips, then click any event to open its detail dialog — summary, tags, description, details, and attachments (images preview inline; files download). The workover carries an operations log via renderDetail.

Well history16 events
2021
2022
2023
2024
2025
import { EventTimeline, EventActivityLog } from "@aai-agency/og-components";
import { sampleWellEvents } from "@aai-agency/og-components/sample-data";

<EventTimeline
  events={sampleWellEvents}
  title="Well history"
  renderDetail={(event) =>
    Array.isArray(event.meta?.steps) ? (
      <EventActivityLog
        title="Operations log"
        maxHeight={168}
        entries={event.meta.steps}
      />
    ) : null
  }
/>

Overview

The default vertical feed is a grouped history list — a muted date column beside each entry: a color-coded status dot, the title, a soft type tag (shown only when it adds information beyond the title), a duration and date range for spans, a paperclip count when an event has attachments, and the description. Events are grouped into period sections (year or month, chosen from the span) on subtle dividers, and the feed scrolls internally past maxHeight.

A group filter (on by default) sits above the feed: soft toggle chips for the five lifecycle groups, with the header showing "N of M". Set orientation="horizontal" for a compact, time-aligned lane that lines up directly beneath a chart, with optional swim-lanes per workstream.

The package ships three exports: EventTimeline (the history), EventDetailDialog (the detail modal, usable on its own), and EventActivityLog (a compact, time-based, internally-scrollable log primitive).

Detail dialog

Clicking a row opens an accessible modal (Radix Dialog) laid out like a filled-out form: an optional AI-generated Summary section (marked with an AI tag), then name, date, tags, description, a details / property list, and attachments — images preview inline, other files show as cards with an extension badge, name, and size. Click a card to view the file in a new tab or use its download control to save it. The body scrolls, so long records fit. EventDetailDialog is exported on its own, so you can open the same dialog from any list, table, or map marker you already have — no timeline required.

The dialog opens standalone — the summary, tags, description, details, and attachments come straight off the event.

import { useState } from "react";
import { EventDetailDialog, type WellEvent } from "@aai-agency/og-components";

// The same dialog EventTimeline opens on click, available standalone —
// drive it from any list, table, or map marker you already have.
function AssetEvents({ events }: { events: WellEvent[] }) {
  const [selected, setSelected] = useState<WellEvent | null>(null);
  return (
    <>
      {events.map((event) => (
        <button key={event.id} onClick={() => setSelected(event)}>
          {event.title}
        </button>
      ))}
      <EventDetailDialog event={selected} onClose={() => setSelected(null)} />
    </>
  );
}

AI summary

Every event can carry a short summary that leads its detail dialog, marked with an AI tag. Set event.summary to whatever your pipeline generates from the full description, and the dialog puts it up top so nobody has to read the whole report first. Here is the workover exactly as its dialog opens, summary first:

Rod pump repair
Intervention
Summary AI
Rod string parted and the pump was worn. Pulled everything, replaced the pump and 18 rods, and put the well back on production. Took 13 days.
Date
Aug 30, 2022 – Sep 12, 2022 · 13 days
Tags
WorkoverIntervention
Description
The rod string parted near 4,200 ft. Rigged up a workover rig, pulled the rod string and tubing, replaced the downhole pump and 18 worn rods, ran a new pump, and returned the well to production. Tubing and casing checked out fine.

Operations log

EventActivityLog is a reusable primitive — a compact, time-based, internally-scrollable log ({ time?, label, description?, color? }[]) for operations logs, run histories, or audit trails. Drop it into a dialog via renderDetail, or use it anywhere on its own. Only primitive meta values render in the built-in Details list, so arrays and objects (like a step log) are yours to lay out.

import { EventTimeline, EventActivityLog } from "@aai-agency/og-components";

// renderDetail injects your own section(s) into the dialog per event.
// Only primitive meta values render in the built-in Details list;
// arrays/objects (like a step log) are yours to lay out here.
<EventTimeline
  events={events}
  renderDetail={(event) =>
    Array.isArray(event.meta?.steps) ? (
      <EventActivityLog title="Operations log" maxHeight={168} entries={event.meta.steps} />
    ) : null
  }
/>

Installation

bash
pnpm add @aai-agency/og-components

The Event Timeline is self-contained (inline-styled, so no stylesheet import is required) and pulls in @radix-ui/react-dialog for the accessible detail modal.

Usage

Drop-in demo with the bundled single-well lifecycle dataset:

tsx
import { EventTimeline } from "@aai-agency/og-components";
import { sampleWellEvents } from "@aai-agency/og-components/sample-data";

// Vertical history feed (default). Grouped by period, filterable by
// lifecycle group. Click any row to open its detail dialog.
export function WellHistory() {
  return <EventTimeline events={sampleWellEvents} title="Well history" maxHeight={460} />;
}

Extend the dialog (renderDetail)

Inject custom sections per event — an operations log, a sub-table, a chart — without forking the component.

tsx
import { EventTimeline, EventActivityLog } from "@aai-agency/og-components";

// renderDetail injects your own section(s) into the dialog per event.
// Only primitive meta values render in the built-in Details list;
// arrays/objects (like a step log) are yours to lay out here.
<EventTimeline
  events={events}
  renderDetail={(event) =>
    Array.isArray(event.meta?.steps) ? (
      <EventActivityLog title="Operations log" maxHeight={168} entries={event.meta.steps} />
    ) : null
  }
/>

The dialog on its own

Drive EventDetailDialog from your own UI — pass the selected event, or null to close.

tsx
import { useState } from "react";
import { EventDetailDialog, type WellEvent } from "@aai-agency/og-components";

// The same dialog EventTimeline opens on click, available standalone —
// drive it from any list, table, or map marker you already have.
function AssetEvents({ events }: { events: WellEvent[] }) {
  const [selected, setSelected] = useState<WellEvent | null>(null);
  return (
    <>
      {events.map((event) => (
        <button key={event.id} onClick={() => setSelected(event)}>
          {event.title}
        </button>
      ))}
      <EventDetailDialog event={selected} onClose={() => setSelected(null)} />
    </>
  );
}

Align a lane under a chart

Set orientation="horizontal" and match domain and padding to the chart so the events sit directly under the plot.

tsx
import { Chart } from "@aai-agency/og-components";
import { EventTimeline } from "@aai-agency/og-components";

// A compact lane that lines up beneath a chart. Pass the chart's
// visible X window as domain and match padding to its plot inset.
<Chart kind="line" series={production} height={280} />
<EventTimeline
  events={events}
  orientation="horizontal"
  domain={[windowStart, windowEnd]}
  padding={{ left: 56, right: 14 }}
  showLog={false}
/>

Props

PropTypeDefaultDescription
eventsWellEvent[]-Events to render. Each has id, date, type, title, and optional endDate, summary, description, value, attachments, meta.
orientation"vertical" | "horizontal""vertical"Vertical grouped history feed (default), or a compact lane that aligns beneath a chart.
titlestring-Heading rendered above the timeline.
maxHeightnumber460Max height of the scrollable vertical feed before it scrolls internally.
groupBy"year" | "month" | "none"span-basedSection granularity for the feed. Chosen from the event span when omitted.
showFiltersbooleantrueShow the lifecycle-group filter chips above the vertical feed.
renderDetail(event: WellEvent) => ReactNode-Inject custom section(s) into the detail dialog per event — an operations log, a sub-table, a chart.
onEventSelect(event: WellEvent | null) => void-Fires when a row or marker is clicked.
selectedEventIdstring | null-Controlled selection. Omit for uncontrolled (the component manages the open dialog itself).
formatDate(time: number) => string-Override date formatting in rows, headers, and the dialog.
domain[DateInput, DateInput]fit to eventsHorizontal only: the visible time window. Match it to the chart's X window.
padding{ left?: number; right?: number }{ left: 58, right: 8 }Horizontal only: plot-area insets, so the lane lines up under the chart's plot.
heightnumber76Horizontal only: lane height in pixels.
showLegend / showAxis / showLogbooleantrueHorizontal only: toggle the legend, the time axis, and the paired feed.
emptyMessagestring"No events"Shown when there are no plottable events.

Sample data

The package ships a full single-well lifecycle history — permits through workover — with per-event details, an AI-style summary, inline-previewable image attachments, a downloadable CSV, and a step log for the operations-log demo. Use it to prototype without synthesizing your own events:

tsx
import { sampleWellEvents } from "@aai-agency/og-components/sample-data";
import type { WellEvent } from "@aai-agency/og-components";

// WellEvent[] — a single well's lifecycle, ready to pass to <EventTimeline events={...} />