# useDocyrusContactActivity
URL: /docs/web/hooks/use-docyrus-contact-activity
Merge a Docyrus record's audit log, threaded comments, related events, tasks, and status updates into one timeline for , plus comment and status mutations.
## Installation
```bash
pnpm dlx @docyrus/cli add @docyrus/hooks-use-docyrus-contact-activity
```
**Dependencies:**
- [@docyrus/api-client](https://www.npmjs.com/package/@docyrus/api-client)
- [@tanstack/react-query](https://tanstack.com/query/latest)
The hook needs an authenticated `RestApiClient` from `@docyrus/api-client` and a `QueryClientProvider`
above your component tree. Read endpoints require the relevant `DS.Read.*` scopes; comment writes and
status updates require `DS.ReadWrite.*` on the record's data source.
## Overview
`useDocyrusContactActivity` is the Docyrus-backed engine behind [`ContactActivityPanel`](/docs/web/components/contact-activity-panel).
It runs up to **six** React Query sources scoped to one record and merges them into a single
date-sorted `ContactActivity[]` feed:
- **Audit** — `GET …/items/:id/activities`: CRUD + file trace. Comment operations (`INSERT_COMMENT` / `UPDATE_COMMENT` / `DELETE_COMMENT`) are **filtered out** here because they arrive threaded from the comments source (hybrid, dedup-safe).
- **Comments** — `GET …/items/:id/comments`: the flat comment list, rebuilt into a `parent_id` tree (arbitrary depth). Top-level comments become timeline entries; `onLoadReplies` returns all descendants.
- **Events** — related `base/event` rows (needs `relationSlug` / `eventRelationSlug`), classified `call` vs `meeting` from the `calendar` event-type name.
- **Tasks** — related `base/task` rows (needs `relationSlug` / `taskRelationSlug`).
- **Status updates** — `base/activity` rows where `status_update_record_id = recordId`, surfaced as first-class `status_update` entries.
- **Files** — `GET …/items/:id/files`: documents attached to the record, surfaced as `file` entries carrying a real `PostAttachment` (name, size, MIME, signed URL). With this source on, the audit log's bare `UPLOAD_FILE` traces are dropped — they describe the same upload with less information.
A status change goes through the two-call `updateStatus` flow — it PATCHes the record (which emits a
generic audit `UPDATE`) **and** logs a `status_update` activity. The merge suppresses the audit
`record_update` row that coincides with a status update (short time window) so a single change surfaces
once, not twice.
The primary output is `panelProps` — spread it straight onto ``. The panel
stays presentational; the hook owns all I/O.
## Usage
```tsx
import { useDocyrusContactActivity } from '@docyrus/ui/library/hooks/use-docyrus-contact-activity';
import { ContactActivityPanel } from '@docyrus/ui/components/contact-activity-panel';
function RecordTimeline({ client, recordId }) {
const activity = useDocyrusContactActivity({
client,
appSlug: 'base',
dataSourceSlug: 'contact',
recordId,
// Per-tenant back-relation slug on base/event + base/task — required for those sources.
relationSlug: 'contact',
currentUserId: myUserId
});
return ;
}
```
## Options
`useDocyrusContactActivity(options: UseDocyrusContactActivityOptions)`
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `client` | `ContactActivityClient` | — | Authenticated REST client (`RestApiClient` satisfies it). |
| `appSlug` | `string` | — | App slug of the owning record. |
| `dataSourceSlug` | `string` | — | Data source slug of the owning record. |
| `recordId` | `string` | — | Owning record id. |
| `relationSlug` | `string` | — | Back-relation slug on `base/event` + `base/task` pointing at this record (per-tenant, e.g. `contact`). Required to include the events / tasks sources. |
| `eventRelationSlug` | `string` | `relationSlug` | Override the event back-relation slug. |
| `taskRelationSlug` | `string` | `relationSlug` | Override the task back-relation slug. |
| `sources` | `ContactActivitySources` | all `true` | Toggle which sources merge (`audit` / `comments` / `events` / `tasks` / `statusUpdates` / `files`). |
| `eventAppSlug` / `eventDataSourceSlug` | `string` | `base` / `event` | Events source override. |
| `taskAppSlug` / `taskDataSourceSlug` | `string` | `base` / `task` | Tasks source override. |
| `activityAppSlug` / `activityDataSourceSlug` | `string` | `base` / `activity` | Status-activity source override. |
| `users` | `ChatUser[]` | — | Consumer-supplied users (preferred). Falls back to `/v1/users` when omitted. |
| `currentUserId` | `string` | — | Flags the viewer's own activities. |
| `activityTypes` | `ActivityType[]` | — | Restrict the panel's type-filter toggles. |
| `eventTypeNames` | `EventTypeNameHints` | call: `['call','phone']`, meeting: `['meeting','visit','demo']` | Case-insensitive name hints for call vs meeting classification. |
| `classifyEvent` | `(event) => 'call' \| 'meeting'` | — | Custom classifier (wins over `eventTypeNames`). |
| `relatedColumns` | `{ events?: string[]; tasks?: string[] }` | — | Extra relation columns fetched on the event / task sources and surfaced as related chips. Relation slugs are per-tenant and an unknown column fails the whole request, so nothing is requested unless you name it. |
| `limit` | `number` | `100` | Per-source page size (the API caps at 100 for OAuth tokens). |
| `enabled` | `boolean` | `true` once `recordId` is present | Gate every query. |
## Returns
`UseDocyrusContactActivityResult`
| Field | Type | Description |
|-------|------|-------------|
| `activities` | `ContactActivity[]` | Merged, date-sorted timeline. |
| `users` | `ChatUser[]` | Resolved users. |
| `currentUser` | `ChatUser \| undefined` | Resolved current user. |
| `panelProps` | `ContactActivityPanelData` | Spread onto ``. |
| `isLoading` | `boolean` | Any read source loading. |
| `error` | `Error \| null` | First read-source error. |
| `isMutating` | `boolean` | Any mutation pending. |
| `mutationError` | `Error \| null` | First mutation error. |
| `refetch` | `() => Promise` | Invalidate the record-scoped keys. |
| `createComment` | `(payload: CreatePostPayload) => Promise` | Post a comment (threaded reply via `parent_id`). |
| `deleteComment` | `(commentId: string) => Promise` | Delete a comment by id. |
| `updateStatus` | `(input: UpdateStatusInput) => Promise` | Two-call status change (see below). |
| `isUpdatingStatus` | `boolean` | Status mutation pending. |
## Two-call status update
`updateStatus` performs the record PATCH **then** logs the status-update activity:
```tsx
await activity.updateStatus({
statusFieldSlug: 'status',
value: enumId,
secondaryValue, // → __status_secondary (only sent when provided)
description, // → __status_description
followupDate // → __status_followup_date
});
```
Companion fields (`___*`) are only sent when provided, so plain `field-select` status fields
(which have no companion columns) don't 400. If the record PATCH succeeds but logging fails, the promise
rejects with a **partial-failure** error (`error.partial === true`) — the record *did* change, and the
timeline still invalidates (`onSettled`) so the change is reflected. Surface this to the user so a retry
isn't mistaken for a no-op.
## Notes
- **Users are shared.** All Docyrus activity hooks fetch `/v1/users` under one query key (`['docyrus-users', limit]`) and the same canonical shape, so React Query dedupes to a single request across this hook, `useDocyrusCreateTask`, and `useDocyrusEventCreate`.
- **Rate-limit aware.** Read sources carry a short `staleTime` so tab switches / refocus don't refire the 5-source fan-out against the tight API rate limit.
- **Files are on by default.** The documents source adds one request per record; pass `sources={{ files: false }}` to keep the previous behaviour (uploads then stay as bare audit traces).
- **Related chips.** A comment's `assigned_to` becomes a related chip automatically (resolved against the users list). Everything else is opt-in through `relatedColumns` — the hook never guesses a tenant's relation slugs.
- **Per-tenant relations.** On some tenants `base/event ↔ contact` is many-to-many with no scalar column — pass the correct scalar back-relation via `eventRelationSlug` / `taskRelationSlug`, or omit them to leave those sources off.
## Type Exports
| Type | Description |
|------|-------------|
| `UseDocyrusContactActivityOptions` | Hook options. |
| `UseDocyrusContactActivityResult` | Hook return. |
| `ContactActivityPanelData` | The `panelProps` bag. |
| `ContactActivitySources` | Per-source toggles. |
| `EventTypeNameHints` | Call / meeting classification hints. |
| `UpdateStatusInput` | `updateStatus` payload. |
| `ContactActivityClient` | Minimal REST surface the hook needs. |