Skip to content

useActivityLog ​

Writes audit entries to the backend. useCrud already calls it on every create, update and delete.

Import ​

ts
import { activityEntityLabel, useActivityLog } from '@shared/composables/useActivityLog'

Signature ​

ts
function useActivityLog(): {
  log: (entry: Omit<ActivityEntry, 'id' | 'timestamp'>) => Promise<void>
}

function activityEntityLabel(entity: string): string

Returns ​

MemberTypeDescription
log(entry) => Promise<void>POSTs to /activity-log. The id and timestamp are the server's.

ActivityEntry ​

KeyTypeDescription
idstringAssigned by the backend.
action'create' | 'update' | 'delete' | stringA free string, so an app can log its own verbs.
entitystringThe resource. A stable identifier, not a label.
entityIdstring | numberWhich row.
detailsRecord<string, unknown>Whatever is worth keeping — usually the payload.
userIdstringWho did it.
timestampDateAssigned by the backend.

activityEntityLabel ​

Turns an entity string into something readable. It looks for activity-log.entities.{entity} in lower case and returns the translation if it exists, or the entity as logged if it does not.

That fallback matters because entity is a free string — a module name, a table name, whatever the app passed. Only the ones with a key translate; the rest render exactly as they were recorded.

Example ​

vue
<script setup lang="ts">
import { activityEntityLabel, useActivityLog } from '@shared/composables/useActivityLog'

const { log } = useActivityLog()

async function approve(id: number) {
  await fetch(`/api/expenses/${id}/approve`, { method: 'POST' })
  await log({
    action: 'approve',
    entity: 'expense',
    entityId: id,
    details: { approvedBy: 'finance' },
  })
}
</script>

<template>
  <div>
    <button type="button" @click="approve(41)">
      Approve
    </button>
    <p>{{ activityEntityLabel('expense') }}</p>
  </div>
</template>

Notes ​

  • entity must not change with the language. It is data in the audit trail, so it is 'warehouse' — never "Warehouse", never a translated word. activityEntityLabel is what turns it into something to read, at the point it is displayed.
  • log is a plain POST and it rejects on failure. useCrud calls it fire-and-forget, so a failed audit never breaks the operation that triggered it. Do the same if the entry is not more important than the action.
  • The activity-log page reads entries back through its own useCrud instance, and sets activityLog: false there so reading the log does not write to it.
  • The whole feature sits behind config.features.activityLog.