@dcodegroup-au/vue-activity-log
v0.1.19
Published
A Vue 3 component for displaying activity logs with timeline, comments, and notifications.
Readme
Vue Activity Log
Vue 3 components for displaying activity logs from dcodegroup/activity-log: timeline view, table list, comments, emoji reactions, email/SMS previews, and delete confirmation modals.
Package: @dcodegroup-au/vue-activity-log
Peer backend: dcodegroup/activity-log
Requirements
| Dependency | Version |
|---|---|
| Vue | >=3.0.0 |
| axios | any |
| vue-i18n | ^9.0.0 |
| @heroicons/vue | ^2.0.0 |
| @dcodegroup/vue-mention | ^0.0.2 |
| mitt | ^3.0.0 (or any bus with $on / $off / $emit) |
| vue-markdown-render | ^2.1.1 |
Installation
npm install @dcodegroup-au/vue-activity-log
# or
pnpm add @dcodegroup-au/vue-activity-logInstall peer dependencies if they are not already in your app:
npm install vue axios vue-i18n @heroicons/vue @dcodegroup/vue-mention mitt vue-markdown-renderSetup
1. Register the plugin (recommended)
import { createApp } from 'vue'
import App from './App.vue'
import VueActivityLogPlugin from '@dcodegroup-au/vue-activity-log'
import '@dcodegroup-au/vue-activity-log/dist/style.css'
const app = createApp(App)
app.use(VueActivityLogPlugin)
app.mount('#app')2. Or import components locally
<script setup>
import {
VActivityLog,
ActivityLogList,
ActivityLogModal,
ActivityEmail,
ActivityLogDeleteComment,
} from '@dcodegroup-au/vue-activity-log'
import '@dcodegroup-au/vue-activity-log/dist/style.css'
</script>3. Provide an event bus
Most components inject: ['bus'] and call bus.$on / bus.$off / bus.$emit. Raw mitt uses on / off / emit, so wrap it:
import mitt from 'mitt'
import { createApp } from 'vue'
import App from './App.vue'
const emitter = mitt()
const bus = {
$on: (...args) => emitter.on(...args),
$off: (...args) => emitter.off(...args),
$emit: (...args) => emitter.emit(...args),
}
const app = createApp(App)
app.provide('bus', bus)
app.mount('#app')Or in a parent component:
<script setup>
import { provide } from 'vue'
import mitt from 'mitt'
const emitter = mitt()
provide('bus', {
$on: (...args) => emitter.on(...args),
$off: (...args) => emitter.off(...args),
$emit: (...args) => emitter.emit(...args),
})
</script>Mount <ActivityLogModal /> once near the app root so email previews and delete confirmations can open.
4. Configure vue-i18n
Components use $t(...) keys under activity-log.* and generic.*. Align messages with the Laravel package lang file, or add a minimal locale:
const messages = {
en: {
'activity-log': {
headings: {
title: 'Activity Log',
confirm_delete_comment: 'Confirm Delete Comment',
},
fields: {
updated_model: 'Updated',
to: 'To',
subject: 'Subject',
date: 'Date',
collapsed_view: 'Collapsed view',
my_activities: 'My activities',
system: 'System',
loading: 'Loading...',
no_result: 'No results',
},
buttons: {
download_phone_call: 'Download Phone Call',
comment: 'Comment',
save: 'Save',
preview_email: 'Preview Email',
preview_sms: 'Preview SMS',
delete: 'Delete',
edit: 'Edit',
cancel: 'Cancel',
resend: 'Resend',
resent: 'Resent',
},
placeholders: {
add_comment: 'Add your comment...',
search_description: 'Search by description',
},
search: {
placeholder: 'Search...',
},
words: {
edited: 'Edited',
loading: 'Loading ...',
views: 'views',
read_more: 'Read more',
read_less: 'Read less',
unknown: 'Someone',
},
phases: {
opened_on: 'Opened on',
email_has_not_been_opened: 'Email is unopened.',
},
},
generic: {
created_by: 'Created by',
buttons: {
cancel: 'Cancel',
confirm: 'Confirm',
delete: 'Delete',
},
},
},
}Components
| Component | Export | Purpose |
|---|---|---|
| VActivityLog | yes | Timeline with search, filters, comments, reactions, resend |
| ActivityLogList | yes | Compact table/list view |
| ActivityLogModal | yes | Host modal opened via the bus |
| ActivityEmail | yes | Email/SMS preview content for the modal |
| ActivityLogDeleteComment | yes | Delete-comment confirmation for the modal |
| VActivityReactions | internal | Emoji reactions UI used by VActivityLog |
VActivityLog — timeline view
<template>
<VActivityLog
model-class="Post"
model-id="123"
get-url="/activity-logs"
comment-url="/activity-logs/comments"
load-users-url="/activity-logs/filters/facets/created_by"
resend-url="/activity-logs/resend-communication"
:current-user="{ id: 1, full_name: 'John Doe' }"
:allow-comment="true"
:allow-attachments="true"
:allow-resend="true"
:auto-grow-input="true"
timezone="Asia/Bangkok"
@comment-added="onCommentAdded"
/>
</template>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| modelClass | String | — | Required. Model class name sent to the API |
| modelId | String | — | Required. Model id |
| getUrl | String | "/activity-logs" | Index endpoint |
| commentUrl | String | "/activity-logs/comments" | Create/update comment endpoint |
| loadUsersUrl | String | "/activity-logs/filters/facets/created_by" | Mention user facet endpoint |
| resendUrl | String | "/activity-logs/resent-communication" | Resend communication base URL |
| currentUser | Object | — | { id, full_name, ... } for comments/reactions |
| allowComment | Boolean | false | Show comment composer |
| allowAttachments | Boolean | false | Show file attachment controls in the comment composer and editor |
| attachmentAccept | String | images, PDF, Word | Value passed to the file input's accept attribute |
| maxAttachments | Number | 10 | Maximum files allowed per comment |
| attachFilesText | String | "Attach files" | Attachment button label |
| allowResend | Boolean | false | Show resend action on communications |
| hasReaction | Boolean | true | Show emoji reactions on activities |
| isWidgetView | Boolean | false | Compact/widget layout |
| defaultCollapView | Boolean | false | Start in collapsed view |
| refreshSelf | Boolean | false | Refetch after posting a comment |
| canMentionInComment | Boolean | true | Enable @ mentions |
| canMentionSpace | Boolean | true | Allow spaces in mentions |
| enterToComment | Boolean | false | Submit on Enter (otherwise use the button) |
| autoGrowInput | Boolean | false | Auto-expand comment textarea |
| isMarkdownContent | Boolean | false | Treat communication content as markdown |
| showFullComment | Boolean | false | Skip “read more” truncation |
| noActivityText | String | "No activity found" | Empty state text |
| timezone | String | — | Passed as query param |
| extra_models | String | — | Related models filter |
| filterEvent | String | "activityLogFilterChange" | Bus event for external filters |
| resendEvent | String | "activityLogResend" | Bus event name for resend |
| modalEvent | String | "openActivityLogModal" | Bus event to open the modal |
| activityEmailComponentName | String | "ActivityEmail" | Component name passed into the modal |
Events
| Event | Payload |
|---|---|
| commentAdded | { event, activities, modelId, modelClass } |
| attachmentError | Upload or comment request error |
Default slot
Rendered in the search/toolbar area (e.g. an export button).
Bus events used
| Event | Direction | Purpose |
|---|---|---|
| refreshActivityLog | listen / emit | Refresh this (or sibling) timeline instances |
| activityLogFilterChange | listen / emit | Apply external filter params |
| activityLogTermChanged | listen | Set filter[name] from external search |
| openActivityLogModal | emit | Open email preview or delete dialog |
Reactions
When hasReaction is true (default), activities show emoji reactions inline with the title. Set :has-reaction="false" to hide them. Clicking an emoji posts:
POST {getUrl}/{activityId}/reactions
Content-Type: application/json
{
"emoji": "👍",
"modelClass": "Post",
"modelId": "123",
"currentUser": { "id": 1, "full_name": "John Doe" }
}Expected response shape matches the index endpoint: { "data": [ /* activities */ ] }.
Each activity may include reactions, reaction_groups, or reactionGroups for display.
ActivityLogList — table / list view
<template>
<ActivityLogList
model-class="Post"
model-id="123"
get-url="/activity-logs"
current-user="John Doe"
/>
</template>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| modelClass | String | — | Required |
| modelId | String \| Number | — | Required |
| getUrl | String | "/activity-logs" | Index endpoint |
| allowComment | Boolean | false | Reserved for comment UI |
| refreshSelf | Boolean | false | Reserved refresh flag |
| currentUser | String | "Guest" | Display name (string, unlike timeline) |
| modalEvent | String | "openActivityLogModal" | Bus event for email preview |
| filterEvent | String | "activityLogFilterChange" | Bus event for external filters |
ActivityLogModal
Renders a centered overlay. Place once in the app shell:
<template>
<ActivityLogModal />
</template>Open it via the bus:
bus.$emit('openActivityLogModal', {
componentName: 'ActivityEmail', // or 'ActivityLogDeleteComment'
componentData: {
to: '[email protected]',
subject: 'Welcome',
content: '<p>Hello</p>',
date: '2024-06-16 10:30 AM',
isMarkdownContent: false,
},
// optional:
cancelTitle: 'Cancel',
confirmTitle: 'OK',
scrollable: true,
isAsyncCallback: false,
callback: () => {},
cancelCallback: () => {},
})Close with:
bus.$emit('closeActivityLogModal')ActivityEmail
Modal body for email/SMS preview.
| Prop | Type | Required | Default |
|---|---|---|---|
| to | String | yes | — |
| subject | String | yes | — |
| content | String | yes | — |
| date | String | no | — |
| isMarkdownContent | Boolean | no | false |
When isMarkdownContent is true and content looks like markdown, it renders via vue-markdown-render; otherwise HTML is rendered with v-html.
ActivityLogDeleteComment
| Prop | Type | Required |
|---|---|---|
| endpoint | String | yes — full DELETE URL for the comment |
On success it emits refreshActivityLog and closeActivityLogModal on the bus.
bus.$emit('openActivityLogModal', {
componentName: 'ActivityLogDeleteComment',
componentData: {
endpoint: `/activity-logs/comment/${commentId}`,
},
})Complete example
<template>
<div class="app">
<VActivityLog
model-class="Post"
model-id="123"
get-url="/activity-logs"
comment-url="/activity-logs/comments"
load-users-url="/activity-logs/filters/facets/created_by"
resend-url="/activity-logs/resend-communication"
:current-user="currentUser"
:allow-comment="true"
:allow-attachments="true"
:allow-resend="true"
:can-mention-in-comment="true"
:auto-grow-input="true"
:timezone="timezone"
@comment-added="onCommentAdded"
/>
<ActivityLogModal />
</div>
</template>
<script setup>
import { ref, provide } from 'vue'
import mitt from 'mitt'
import {
VActivityLog,
ActivityLogModal,
} from '@dcodegroup-au/vue-activity-log'
import '@dcodegroup-au/vue-activity-log/dist/style.css'
const emitter = mitt()
provide('bus', {
$on: (...args) => emitter.on(...args),
$off: (...args) => emitter.off(...args),
$emit: (...args) => emitter.emit(...args),
})
const currentUser = ref({ id: 1, full_name: 'John Doe' })
const timezone = ref('Asia/Bangkok')
const onCommentAdded = (data) => {
console.log('Comment added:', data.modelClass, data.modelId)
}
</script>API contract
Designed to work with dcodegroup/activity-log routes (defaults below). Adjust URLs via props if your route_path differs.
GET /activity-logs
Query: modelClass, modelId, optional timezone, extra_models, filter[term], filter[created_by], …
Response:
{
"data": [
{
"id": 1,
"user": "John Doe",
"title": "Created post",
"description": "Post description",
"type": "Create",
"color": "blue",
"icon": "PlusIcon",
"created_at_date": "2024-06-16 10:30 AM",
"is_edited": false,
"communication": {
"type": "Email",
"to": "[email protected]",
"subject": "Subject",
"content": "<p>Content</p>",
"date": "2024-06-16",
"reads_count": 2,
"read_at_date": "2024-06-17"
},
"meta": { "action": "Created" },
"delete_comment_endpoint": "/activity-logs/comment/1",
"reactions": [],
"reaction_groups": {}
}
]
}type values commonly used: Create, Update, Delete, Comment, Phone Call, plus notification/communication types from the backend.
POST /activity-logs/comments
{
"modelClass": "Post",
"modelId": "123",
"comment": "User's comment",
"currentUrl": "https://...",
"currentUser": "John Doe",
"timezone": "Asia/Bangkok",
"attachment_ids": [10, 11]
}Response: { "data": [ /* activities */ ] }
Files are never uploaded separately. When the user has picked new files, the
same request is sent as multipart/form-data instead of JSON, with the fields
above plus attachments[] (the raw files) and attachment_ids[] (IDs already
attached to the comment that should be kept). The backend stores the files and
links them to the activity log it creates, all in one round trip.
Activity responses can include:
{
"attachments": [
{
"id": 10,
"url": "/attachments/10",
"custom_properties": {
"original_filename": "screenshot.png"
}
}
]
}PATCH /activity-logs/comments/{id}
Same body as create; updates an existing comment. attachment_ids lists the
already-stored attachments the comment should keep, so the backend should sync
against it and detach anything missing.
When new files are attached during an edit the request is sent as
POST with _method=PATCH, because PHP does not parse multipart bodies on
PATCH requests.
DELETE /activity-logs/comment/{id}
Backend default path uses singular comment. Response: { "data": [ /* activities */ ] } (or empty; the modal also emits a refresh).
POST /activity-logs/resend-communication/{id}
Resends a communication log. Note: the Vue prop default is historically "/activity-logs/resent-communication" — pass resend-url explicitly to match the Laravel route (resend-communication).
POST /activity-logs/{id}/reactions
{
"emoji": "👍",
"modelClass": "Post",
"modelId": "123",
"currentUser": { "id": 1, "full_name": "John Doe" }
}Response: { "data": [ /* activities */ ] }
GET /activity-logs/filters/facets/created_by
Query: s, modelClass, modelId (and filter[admin]=1 when loading mention users)
Response: array of { "label": "John Doe", "value": 1 } (axios response.data is mapped directly in the mention loader).
Styling
Components ship Tailwind-oriented utility classes plus package CSS:
import '@dcodegroup-au/vue-activity-log/dist/style.css'Ensure your app’s Tailwind setup (or the host design system) provides compatible utility classes / theme tokens used by the components (e.g. btn-primary, tertiary-*).
Root markup uses the .vue-activity-log scope class. Timeline status helpers are BEM-style (content__status__time, content__status__description). Activity DB change chips (.activity__db-content) use break-all so long values wrap instead of overflowing.
Development
pnpm install
pnpm buildBuild output is written to dist/ (vue-activity-log.es.js, vue-activity-log.umd.js, style.css).
License
MIT
