# ActivityChart

> A day-bucketed Recharts chart in Max's glass panel: each series is drawn as an area, a line or a bar, with a dot legend and a popover tooltip.

## Facts

- **Product**: Max
- **Family**: Analytics
- **Global**: `window.DigitalCrew.ActivityChart`
- **Source**: `max-agent: src/features/analytics/ui/activity-chart.tsx`
- **Import in the app**: `import { ActivityChart } from "@/features/analytics/ui/activity-chart";`
- **Live preview**: /design/components/activity-chart/preview.html
- **Page**: /design/components/activity-chart

## Guidelines

**Use it for** activity over a period on the analytics page, a campaign's performance tab and detail sheets.

**What you provide**: `title` (a 12px uppercase overline), `data` (one point per day, `date` as `YYYY-MM-DD` plus numeric keys; the default point type is `AnalyticsTimeseriesPointDto`), `series` (`{ key, label, color, kind: "area" | "line" | "bar" }`) and `isLoading`. Take the presets from `chart-series.ts`: `VOLUME_SERIES`, `ENGAGEMENT_SERIES`, `DEALS_SERIES`, with colours `C1`–`C4` = `var(--chart-1…4)`.

**Anatomy**: `glass-panel glass-hover border border-glass rounded-3xl px-6 py-6`. The plot is 280px tall. The grid is dashed `3 3`, horizontal only, drawn in `var(--border)` at 50% opacity. Axis ticks are 11px `muted-foreground` with no axis or tick lines. The X axis reads `MMM d` with a 32px minimum tick gap. The Y axis is 48px wide and shows integers only. Areas are monotone with a 2px stroke and 12% fill. Lines are 2px with no dots and a 4px active dot. Bars have `[4,4,0,0]` radii and a maximum width of 24px. The tooltip is `rounded-xl border-glass bg-popover shadow-md`.

**States**: while `isLoading`, a 280px [`Skeleton`](/design/components/skeleton.md) (`rounded-2xl`) replaces the chart. When no series has a value above 0, a dashed box reads "No activity in this period".

**Don't** hard-code hex colours; pass the `--chart-N` variables so the chart follows the theme. Don't expect stacking: areas overlap.

## API

```ts
/** A day-bucketed point: the date key plus numeric series values. */
export type ChartPoint = {
    date: string;
};
export interface ChartSeries<Point extends ChartPoint = AnalyticsTimeseriesPointDto> {
    key: Exclude<keyof Point, "date"> & string;
    label: string;
    /** CSS color (use the theme's --chart-N variables) */
    color: string;
    kind: "area" | "line" | "bar";
}
export declare function ActivityChart<Point extends ChartPoint = AnalyticsTimeseriesPointDto>({ title, data, series, isLoading, }: {
    title: string;
    data: Point[];
    series: ChartSeries<Point>[];
    isLoading: boolean;
}): JSX.Element;
```

## Example

```html
<div id="root" class="p-6"></div>
<script>
(function () {
  var h = React.createElement, DC = window.DigitalCrew, I = DC.Icons;
  // VOLUME_SERIES from src/features/analytics/ui/chart-series.ts
  var SERIES = [
    { key: "email_sent", label: "Emails sent", color: "var(--chart-1)", kind: "area" },
    { key: "linkedin_invitations_sent", label: "LinkedIn invitations", color: "var(--chart-2)", kind: "area" },
    { key: "linkedin_messages_sent", label: "LinkedIn messages", color: "var(--chart-3)", kind: "area" }
  ];
  // Sat 12 Sep to Fri 25 Sep 2026: quiet weekends, busy weekdays.
  var SENT = [18, 12, 212, 236, 248, 231, 204, 22, 15, 244, 262, 275, 258, 221];
  var INV = [4, 2, 58, 64, 61, 66, 52, 5, 3, 67, 72, 70, 74, 60];
  var MSG = [6, 3, 41, 47, 52, 44, 38, 7, 4, 49, 55, 58, 53, 46];
  // One AnalyticsTimeseriesPointDto per day (every field of the DTO is present).
  var DATA = SENT.map(function (sent, i) {
    return {
      date: new Date(Date.UTC(2026, 8, 12 + i)).toISOString().slice(0, 10),
      email_sent: sent,
      email_opened: Math.round(sent * 0.52),
      email_replied: Math.round(sent * 0.041),
      linkedin_invitations_sent: INV[i],
      linkedin_connections: Math.round(INV[i] * 0.38),
      linkedin_messages_sent: MSG[i],
      linkedin_replies: Math.round(MSG[i] * 0.12),
      whatsapp_messages_sent: 0,
      whatsapp_replies: 0,
      meetings_booked: i % 3 === 0 ? 1 : 2,
      deals_created: 0,
      deals_won: 0,
      prospect_lists_created: 0,
      signals_detected: 0
    };
  });
  function App() {
    return h(DC.ActivityChart, { title: "Outreach volume", data: DATA, series: SERIES, isLoading: false });
  }
  ReactDOM.createRoot(document.getElementById("root")).render(h(DC.TooltipProvider, { delayDuration: 80 }, h(App)));
})();
</script>
```

## More in Analytics

- [ChartContainer](/design/components/chart-container.md): The shell every Crew OS chart sits in: it wraps a Recharts 2 chart in a `ResponsiveContainer`, turns a config object into per-series colour variables and styles axes, grid, tooltip and legend with the theme tokens.
- [FunnelChart](/design/components/funnel-chart.md): A vertical column funnel with a summary panel.
- [InsightsCard](/design/components/insights-card.md): A meeting's numbers (sentiment, stat tiles, per-speaker talk share, an over-talk hint), all derived on the client from the transcript.
- [QualificationContent](/design/components/qualification-content.md): The body of a BANT qualification snapshot: a score, a verdict, a confirmed count, and one tile per criterion with its status, rationale and first piece of transcript evidence.
