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.
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:
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
pnpm add @aai-agency/og-componentsThe 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:
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.
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.
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.
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
| Prop | Type | Default | Description |
|---|---|---|---|
events | WellEvent[] | - | 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. |
title | string | - | Heading rendered above the timeline. |
maxHeight | number | 460 | Max height of the scrollable vertical feed before it scrolls internally. |
groupBy | "year" | "month" | "none" | span-based | Section granularity for the feed. Chosen from the event span when omitted. |
showFilters | boolean | true | Show 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. |
selectedEventId | string | 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 events | Horizontal 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. |
height | number | 76 | Horizontal only: lane height in pixels. |
showLegend / showAxis / showLog | boolean | true | Horizontal only: toggle the legend, the time axis, and the paired feed. |
emptyMessage | string | "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:
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={...} />