Log Activity Form
A presentational multi-tab "log activity" composer — comment, email, call, meeting, task, and status — with every tab rendered inline, wired to Docyrus via the useDocyrusLogActivity hook.
Installation
pnpm dlx @docyrus/cli add @docyrus/ui-log-activity-formpnpm add @tanstack/react-form @tanstack/react-query motion sonnernpx shadcn@latest add badge button calendar command input label popover scroll-area select separator textareaOverview
LogActivityForm is a presentational component (it owns no network I/O). A tab bar switches between six surfaces and each one renders its form inline — no dialog, no dropdown-triggered form. Every tab reuses the real Docyrus components / form-fields:
| Tab | Inline surface |
|---|---|
| Comment | CommentsPanel (threaded) |
EmailComposer (send-only) | |
| Status | inline StatusFormField (status list + sub-status / notes / follow-up date, no popover) |
| Task | subject + status + priority + assignee + due date + description form-fields |
| Call / Meeting | subject + event type + start / end + owner + description form-fields |
Wire it to a Docyrus record with useDocyrusLogActivity — the hook owns ALL I/O (option fetches, the per-tab create mutations, the two-call status update, and an embedded email composer) and returns a spreadable logActivityProps bag. This mirrors the EmailComposer / useDocyrusEmailComposer split.
Usage
import { LogActivityForm } from '@docyrus/ui/components/log-activity-form';
import { useDocyrusLogActivity } from '@docyrus/ui/library/hooks/use-docyrus-log-activity';
import { ContactActivityPanel, useDocyrusContactActivity } from '@docyrus/ui/components/contact-activity-panel';
function RecordActivity({ client, recordId }) {
// One hook feeds the log form (all I/O).
const { logActivityProps } = useDocyrusLogActivity({
client,
appSlug: 'base',
dataSourceSlug: 'organization',
recordId,
taskRelationSlug: 'organization', // per-tenant back-relation for task/event linking
statusFieldSlug: 'status',
callEventTypeName: 'Call', // resolve the call/meeting event type by name
meetingEventTypeName: 'Meeting'
});
// A sibling hook feeds the timeline. Both share the record-scoped query prefix,
// so logging on the form auto-refreshes the panel — no manual refetch wiring.
const { panelProps } = useDocyrusContactActivity({
client, appSlug: 'base', dataSourceSlug: 'organization', recordId,
taskRelationSlug: 'organization',
sources: { audit: true, comments: true, tasks: true, statusUpdates: true }
});
return (
<>
<LogActivityForm {...logActivityProps} />
<ContactActivityPanel {...panelProps} />
</>
);
}API Reference
Every prop is optional — a bare <LogActivityForm /> renders the tab shell with empty surfaces. In practice you spread logActivityProps (see the hook), which fills all of these.
Shell
| Prop | Type | Default | Description |
|---|---|---|---|
activityTypes | LoggableActivityType[] | all six | Which tabs to show (order preserved). |
activeType | LoggableActivityType | — | Controlled active tab. |
defaultActivityType | LoggableActivityType | first tab | Uncontrolled initial tab. |
onTypeChange | (type: LoggableActivityType) => void | — | Fired on tab change. |
disabled | boolean | false | Disable every surface. |
className | string | — | Root class. |
Comment tab
| Prop | Type | Description |
|---|---|---|
comments | DocyrusComment[] | Thread source. |
users | CommentUser[] | Users for @ mentions / avatars. |
currentUser | CommentUser | The authoring user. |
onCreateComment | (payload: LogActivityCommentPayload) => void | Promise<void> | Create / reply. |
onDeleteComment | (commentId: string) => void | Promise<void> | Delete. |
isCommentsLoading / isCreatingComment | boolean | Pending flags. |
Email tab
| Prop | Type | Description |
|---|---|---|
emailComposerProps | Partial<EmailComposerProps> | Spread onto the inline EmailComposer (from useDocyrusEmailComposer). |
onSendEmail | () => void | Promise<void> | Send handler (the tab wraps it with a toast). |
isSendingEmail | boolean | Pending flag. |
Status tab
| Prop | Type | Description |
|---|---|---|
statusField | IField | null | The record's status field (field-status / field-select). |
statusEnumOptions | EnumOption[] | Status options. |
statusInitialValues | Record<string, unknown> | Current status + companion values, keyed by field slug. |
onSubmitStatus | (input: LogStatusInput) => void | Promise<void> | Submit (two-call in the hook). |
isSubmittingStatus / isStatusLoading | boolean | Pending / loading flags. |
Task tab
| Prop | Type | Description |
|---|---|---|
taskStatusOptions / taskPriorityOptions | EnumOption[] | Task enum options. |
taskUsers | EnumOption[] | Assignee options ({ id, name, icon }). |
onSubmitTask | (input: LogTaskInput) => void | Promise<void> | Submit. |
isSubmittingTask | boolean | Pending flag. |
Call / Meeting tabs
| Prop | Type | Description |
|---|---|---|
eventTypes | EnumOption[] | Event type options. |
eventUsers | EnumOption[] | Owner options. |
callEventTypeId / meetingEventTypeId | string | Preset event-type id per tab. |
onSubmitEvent | (input: LogEventInput, kind: 'call' | 'meeting') => void | Promise<void> | Submit. |
isSubmittingEvent | boolean | Pending flag. |
Type Exports
| Type | Description |
|---|---|
LogActivityFormProps | Props for the presentational form. |
LoggableActivityType | "comment" | "email" | "call" | "meeting" | "task" | "status_update" |
LogActivityCommentPayload | Comment submit payload ({ content, parent_id?, mentions? }). |
LogTaskInput | Task submit payload. |
LogEventInput | Call / meeting submit payload. |
LogStatusInput | Status submit payload. |
Kanban
A drag-and-drop kanban board with sortable columns and items, column reordering, overlay previews, and final drop zones for archiving or completing tasks.
Map
Interactive map component built on Leaflet with SSR-safe lazy loading, theme-aware tiles, marker clustering, drawing tools, custom controls, and geolocation.