# 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. |