Hooks

useDocyrusContactActivity

Merge a Docyrus record's audit log, threaded comments, related events, tasks, and status updates into one timeline for <ContactActivityPanel />, plus comment and status mutations.

Client Only

Installation

pnpm dlx @docyrus/cli add @docyrus/hooks-use-docyrus-contact-activity
Required Packages(2 packages)
pnpm add @docyrus/api-client @tanstack/react-query

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. 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 <ContactActivityPanel />. The panel stays presentational; the hook owns all I/O.

Usage

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 <ContactActivityPanel {...activity.panelProps} />;
}

Options

useDocyrusContactActivity(options: UseDocyrusContactActivityOptions)

OptionTypeDefaultDescription
clientContactActivityClient—Authenticated REST client (RestApiClient satisfies it).
appSlugstring—App slug of the owning record.
dataSourceSlugstring—Data source slug of the owning record.
recordIdstring—Owning record id.
relationSlugstring—Back-relation slug on base/event + base/task pointing at this record (per-tenant, e.g. contact). Required to include the events / tasks sources.
eventRelationSlugstringrelationSlugOverride the event back-relation slug.
taskRelationSlugstringrelationSlugOverride the task back-relation slug.
sourcesContactActivitySourcesall trueToggle which sources merge (audit / comments / events / tasks / statusUpdates / files).
eventAppSlug / eventDataSourceSlugstringbase / eventEvents source override.
taskAppSlug / taskDataSourceSlugstringbase / taskTasks source override.
activityAppSlug / activityDataSourceSlugstringbase / activityStatus-activity source override.
usersChatUser[]—Consumer-supplied users (preferred). Falls back to /v1/users when omitted.
currentUserIdstring—Flags the viewer's own activities.
activityTypesActivityType[]—Restrict the panel's type-filter toggles.
eventTypeNamesEventTypeNameHintscall: ['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.
limitnumber100Per-source page size (the API caps at 100 for OAuth tokens).
enabledbooleantrue once recordId is presentGate every query.

Returns

UseDocyrusContactActivityResult

FieldTypeDescription
activitiesContactActivity[]Merged, date-sorted timeline.
usersChatUser[]Resolved users.
currentUserChatUser | undefinedResolved current user.
panelPropsContactActivityPanelDataSpread onto <ContactActivityPanel />.
isLoadingbooleanAny read source loading.
errorError | nullFirst read-source error.
isMutatingbooleanAny mutation pending.
mutationErrorError | nullFirst mutation error.
refetch() => Promise<unknown>Invalidate the record-scoped keys.
createComment(payload: CreatePostPayload) => Promise<void>Post a comment (threaded reply via parent_id).
deleteComment(commentId: string) => Promise<void>Delete a comment by id.
updateStatus(input: UpdateStatusInput) => Promise<void>Two-call status change (see below).
isUpdatingStatusbooleanStatus mutation pending.

Two-call status update

updateStatus performs the record PATCH then logs the status-update activity:

await activity.updateStatus({
  statusFieldSlug: 'status',
  value: enumId,
  secondaryValue,      // → __status_secondary  (only sent when provided)
  description,         // → __status_description
  followupDate         // → __status_followup_date
});

Companion fields (__<slug>_*) 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

TypeDescription
UseDocyrusContactActivityOptionsHook options.
UseDocyrusContactActivityResultHook return.
ContactActivityPanelDataThe panelProps bag.
ContactActivitySourcesPer-source toggles.
EventTypeNameHintsCall / meeting classification hints.
UpdateStatusInputupdateStatus payload.
ContactActivityClientMinimal REST surface the hook needs.

On this page