Overview
ChangeLog is the shared UI for audit/activity timelines. Scope every feed by pluginName + reference.prefix + reference.number — the same triple the backend ChangeLogService uses when writing entries.
- Import ChangeLog and hooks from @quan-erp/base-frontend
- reference.prefix — entity kind (e.g. SO, EMP, INV)
- reference.number — record id/code as string
- Backend must call ChangeLogService.addChangeLog for entries to appear
1. ChangeLog component
Drop the component on a detail page (or tab). It loads and renders the feed for that reference.
- Signature: ({ pluginName, reference: { prefix, number } }) => ReactNode
- pluginName must match the value used when writing logs on the backend
- Always String(id) for reference.number
1import { ChangeLog } from "@quan-erp/base-frontend";
2import metadata from "../../../module.metadata.json" with { type: "json" };
3
4export function SalesOrderDetail({ id }: { id: number }) {
5 return (
6 <ChangeLog
7 pluginName={metadata.name}
8 reference={{ prefix: "SO", number: String(id) }}
9 />
10 );
11}2. Query & mutation hooks
Use these when you need a custom feed UI instead of the stock ChangeLog component.
- useChangeLogQuery(query, option?) — paginated list; query needs pluginName, referencePrefix, referenceNumber; optional parentId
- useInfiniteChangeLog(pluginName, referencePrefix, referenceNumber, limit?) — infinite scroll feed
- useReactChangeLogQuery() — add a reaction { reaction, reactBy, changeLogId }
- useRemoveReactChangeLogQuery() — remove reaction by change-log id
- clearChangeLogCache({ pluginName, reference, logId? }) — invalidate cached entries after writes
1import {
2 useChangeLogQuery,
3 useReactChangeLogQuery,
4 clearChangeLogCache,
5} from "@quan-erp/base-frontend";
6
7const { data: logs = [] } = useChangeLogQuery({
8 pluginName: metadata.name,
9 referencePrefix: "SO",
10 referenceNumber: String(orderId),
11 currentPage: 1,
12 pageSize: 20,
13});
14
15const react = useReactChangeLogQuery();
16await react.mutateAsync({
17 reaction: "👍",
18 reactBy: currentUserId,
19 changeLogId: logs[0].id,
20});
21
22clearChangeLogCache({
23 pluginName: metadata.name,
24 reference: { prefix: "SO", number: String(orderId) },
25});3. Result shape
Entries returned by the hooks follow GetChangeLogDto.
1interface GetChangeLogDto {
2 id: number;
3 message: string;
4 createdBy: UserDto;
5 isCreatedBySystem: boolean;
6 pluginName: string;
7 referencePrefix: string;
8 referenceNumber: string;
9 createDate: string;
10 reactions: ChangeLogReaction[];
11}
12
13interface ChangeLogReaction {
14 id: number;
15 reaction: string;
16 reactBy: UserDto;
17}4. Backend pairing
The UI only shows what ChangeLogService recorded. On create/update/status changes, call addChangeLog with the same pluginName / referencePrefix / referenceNumber the page passes to <ChangeLog />.
- See Documentation → Backend → Builtin Services → ChangeLogService
- Mismatch in prefix/number/pluginName = empty feed
- After server-side writes from another client, clearChangeLogCache if you keep a long-lived view open
Critical rules
- Import from @quan-erp/base-frontend — not shared-ui
- Keep frontend reference and backend reference fields identical
- reference.number is always a string
- Prefer <ChangeLog /> for standard timelines; use hooks only for custom layouts
- Do not invent a plugin-local activity table when ChangeLogService already covers the use case
Checklist
- Backend addChangeLog on the relevant mutations
- Detail page renders <ChangeLog pluginName reference />
- prefix/number match backend writes
- Optional: reactions via useReactChangeLogQuery
- Verify feed after create/update in the ERP UI
Common failures
- Empty feed — backend never wrote logs, or pluginName/prefix/number mismatch
- Import error — ChangeLog is not on @quan-erp/shared-ui
- Stale list — forgot clearChangeLogCache after external write
- Reaction fails — missing reactBy / changeLogId or permission
- Wrong record’s history — number not String(id) or wrong prefix