{detail.followups.length === 0 ? (
-
No follow-up history yet.
+
+ No follow-ups yet.
+
) : (
detail.followups.map((followup) => (
-
-
-
- {followup.followupStatusLabel ?? followup.followupStatus}
+
+
{followup.followupStatusLabel ?? followup.followupStatus}
+
+ {formatDate(followup.followupDate)}
+
+
{followup.note || '-'}
+ {followup.nextFollowupDate ? (
+
+ Next: {formatDate(followup.nextFollowupDate)}
-
{formatDate(followup.followupDate)}
-
- {followup.note ?
{followup.note}
: null}
-
- {followup.createdByName ?? followup.createdBy}
-
+ ) : null}
))
)}
-
-
-
+
+
+
);
}
diff --git a/src/features/crm/leads/server/service.ts b/src/features/crm/leads/server/service.ts
index e6518e5..0d8e3ba 100644
--- a/src/features/crm/leads/server/service.ts
+++ b/src/features/crm/leads/server/service.ts
@@ -258,7 +258,10 @@ async function listLeadActivity(
entityId: log.entityId,
createdAt: log.createdAt,
userId: log.userId,
- actorName: actorMap.get(log.userId) ?? null
+ actorName: actorMap.get(log.userId) ?? null,
+ beforeData: log.beforeData,
+ afterData: log.afterData,
+ requestId: log.requestId
}));
}
@@ -1077,4 +1080,3 @@ export function buildLeadAccessContext(input: {
}) {
return buildCrmSecurityContext(input);
}
-
diff --git a/src/features/crm/leads/types.ts b/src/features/crm/leads/types.ts
index 4dec438..ace991b 100644
--- a/src/features/crm/leads/types.ts
+++ b/src/features/crm/leads/types.ts
@@ -47,15 +47,7 @@ export interface LeadLinkedOpportunitySummary {
updatedAt: string;
}
-export interface LeadActivityRecord {
- id: string;
- action: string;
- entityType: string;
- entityId: string;
- createdAt: string;
- userId: string;
- actorName: string | null;
-}
+export type LeadActivityRecord = CrmAuditLogRecord;
export interface LeadReferenceData {
awarenesses: LeadOption[];
@@ -225,4 +217,5 @@ export interface LeadMutationSuccessResponse {
lead?: LeadDetail;
followup?: LeadFollowupSummary;
}
+import type { CrmAuditLogRecord } from '@/features/crm/audit-log/types';
diff --git a/src/features/crm/opportunities/api/types.ts b/src/features/crm/opportunities/api/types.ts
index aec162d..bd29c48 100644
--- a/src/features/crm/opportunities/api/types.ts
+++ b/src/features/crm/opportunities/api/types.ts
@@ -166,15 +166,7 @@ export interface OpportunityFollowupRecord {
updatedBy: string;
}
-export interface OpportunityActivityRecord {
- id: string;
- action: string;
- entityType: string;
- entityId: string;
- createdAt: string;
- userId: string;
- actorName: string | null;
-}
+export type OpportunityActivityRecord = CrmAuditLogRecord;
export interface OpportunityReferenceData {
branches: OpportunityBranchOption[];
@@ -337,3 +329,4 @@ export interface MutationSuccessResponse {
success: boolean;
message: string;
}
+import type { CrmAuditLogRecord } from '@/features/crm/audit-log/types';
diff --git a/src/features/crm/opportunities/components/opportunity-detail.tsx b/src/features/crm/opportunities/components/opportunity-detail.tsx
index 2414b67..21ccc49 100644
--- a/src/features/crm/opportunities/components/opportunity-detail.tsx
+++ b/src/features/crm/opportunities/components/opportunity-detail.tsx
@@ -8,6 +8,7 @@ import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import { AuditLogTab } from '@/features/crm/components/audit-log-tab';
import { formatDate, formatDateTime } from '@/lib/date-format';
import { formatNumber } from '@/lib/number-format';
import type {
@@ -284,7 +285,7 @@ export function OpportunityDetail({
Follow-up Timeline
Linked Quotations
- Activity
+ Audit Log
@@ -331,26 +332,11 @@ export function OpportunityDetail({
-
-
- Activity
-
-
- {data.activity.length === 0 ? (
- No activity yet.
- ) : (
- data.activity.map((activity) => (
-
-
{activity.action}
-
- {activity.actorName ?? activity.userId} -{' '}
- {formatDateTime(activity.createdAt)}
-
-
- ))
- )}
-
-
+
diff --git a/src/features/crm/opportunities/server/service.ts b/src/features/crm/opportunities/server/service.ts
index 00156c6..98d11eb 100644
--- a/src/features/crm/opportunities/server/service.ts
+++ b/src/features/crm/opportunities/server/service.ts
@@ -1136,7 +1136,10 @@ export async function getOpportunityActivity(
entityId: log.entityId,
createdAt: log.createdAt,
userId: log.userId,
- actorName: actorMap.get(log.userId) ?? null
+ actorName: actorMap.get(log.userId) ?? null,
+ beforeData: log.beforeData,
+ afterData: log.afterData,
+ requestId: log.requestId
}));
}
diff --git a/src/features/crm/quotations/api/types.ts b/src/features/crm/quotations/api/types.ts
index eca6ab4..1727277 100644
--- a/src/features/crm/quotations/api/types.ts
+++ b/src/features/crm/quotations/api/types.ts
@@ -257,15 +257,7 @@ export interface QuotationAttachmentRecord {
deletedAt: string | null;
}
-export interface QuotationActivityRecord {
- id: string;
- action: string;
- entityType: string;
- entityId: string;
- createdAt: string;
- userId: string;
- actorName: string | null;
-}
+export type QuotationActivityRecord = CrmAuditLogRecord;
export interface QuotationRelationItem {
id: string;
@@ -481,3 +473,4 @@ export interface QuotationCustomerPackageGenerateResponse {
pageCount: number;
} | null;
}
+import type { CrmAuditLogRecord } from '@/features/crm/audit-log/types';
diff --git a/src/features/crm/quotations/components/quotation-detail.tsx b/src/features/crm/quotations/components/quotation-detail.tsx
index 8e1e24f..735e336 100644
--- a/src/features/crm/quotations/components/quotation-detail.tsx
+++ b/src/features/crm/quotations/components/quotation-detail.tsx
@@ -27,6 +27,7 @@ import {
SelectValue
} from '@/components/ui/select';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import { AuditLogTab } from '@/features/crm/components/audit-log-tab';
import { formatDate, formatDateTime } from '@/lib/date-format';
import { formatNumber } from '@/lib/number-format';
import {
@@ -1231,7 +1232,7 @@ const canReviseByStatus = ['accepted', 'rejected', 'sent', 'approved'].includes(