diff --git a/src/app/api/v1/berths/[id]/activity/route.ts b/src/app/api/v1/berths/[id]/activity/route.ts
new file mode 100644
index 0000000..09beead
--- /dev/null
+++ b/src/app/api/v1/berths/[id]/activity/route.ts
@@ -0,0 +1,31 @@
+import { NextResponse } from 'next/server';
+import { eq, and } from 'drizzle-orm';
+
+import { withAuth, withPermission } from '@/lib/api/helpers';
+import { db } from '@/lib/db';
+import { berths } from '@/lib/db/schema/berths';
+import { loadEntityActivity } from '@/lib/services/entity-activity.service';
+import { errorResponse, NotFoundError } from '@/lib/errors';
+
+export const GET = withAuth(
+ withPermission('berths', 'view', async (_req, ctx, params) => {
+ try {
+ const id = params.id;
+ if (!id) throw new NotFoundError('berth');
+ const exists = await db
+ .select({ id: berths.id })
+ .from(berths)
+ .where(and(eq(berths.id, id), eq(berths.portId, ctx.portId)))
+ .limit(1);
+ if (exists.length === 0) throw new NotFoundError('berth');
+ const data = await loadEntityActivity({
+ portId: ctx.portId,
+ entityType: 'berth',
+ entityId: id,
+ });
+ return NextResponse.json({ data });
+ } catch (error) {
+ return errorResponse(error);
+ }
+ }),
+);
diff --git a/src/app/api/v1/clients/[id]/activity/route.ts b/src/app/api/v1/clients/[id]/activity/route.ts
new file mode 100644
index 0000000..fe38276
--- /dev/null
+++ b/src/app/api/v1/clients/[id]/activity/route.ts
@@ -0,0 +1,31 @@
+import { NextResponse } from 'next/server';
+import { eq, and } from 'drizzle-orm';
+
+import { withAuth, withPermission } from '@/lib/api/helpers';
+import { db } from '@/lib/db';
+import { clients } from '@/lib/db/schema/clients';
+import { loadEntityActivity } from '@/lib/services/entity-activity.service';
+import { errorResponse, NotFoundError } from '@/lib/errors';
+
+export const GET = withAuth(
+ withPermission('clients', 'view', async (_req, ctx, params) => {
+ try {
+ const id = params.id;
+ if (!id) throw new NotFoundError('client');
+ const exists = await db
+ .select({ id: clients.id })
+ .from(clients)
+ .where(and(eq(clients.id, id), eq(clients.portId, ctx.portId)))
+ .limit(1);
+ if (exists.length === 0) throw new NotFoundError('client');
+ const data = await loadEntityActivity({
+ portId: ctx.portId,
+ entityType: 'client',
+ entityId: id,
+ });
+ return NextResponse.json({ data });
+ } catch (error) {
+ return errorResponse(error);
+ }
+ }),
+);
diff --git a/src/app/api/v1/companies/[id]/activity/route.ts b/src/app/api/v1/companies/[id]/activity/route.ts
new file mode 100644
index 0000000..182de99
--- /dev/null
+++ b/src/app/api/v1/companies/[id]/activity/route.ts
@@ -0,0 +1,31 @@
+import { NextResponse } from 'next/server';
+import { eq, and } from 'drizzle-orm';
+
+import { withAuth, withPermission } from '@/lib/api/helpers';
+import { db } from '@/lib/db';
+import { companies } from '@/lib/db/schema/companies';
+import { loadEntityActivity } from '@/lib/services/entity-activity.service';
+import { errorResponse, NotFoundError } from '@/lib/errors';
+
+export const GET = withAuth(
+ withPermission('companies', 'view', async (_req, ctx, params) => {
+ try {
+ const id = params.id;
+ if (!id) throw new NotFoundError('company');
+ const exists = await db
+ .select({ id: companies.id })
+ .from(companies)
+ .where(and(eq(companies.id, id), eq(companies.portId, ctx.portId)))
+ .limit(1);
+ if (exists.length === 0) throw new NotFoundError('company');
+ const data = await loadEntityActivity({
+ portId: ctx.portId,
+ entityType: 'company',
+ entityId: id,
+ });
+ return NextResponse.json({ data });
+ } catch (error) {
+ return errorResponse(error);
+ }
+ }),
+);
diff --git a/src/app/api/v1/interests/[id]/activity/route.ts b/src/app/api/v1/interests/[id]/activity/route.ts
new file mode 100644
index 0000000..8180d88
--- /dev/null
+++ b/src/app/api/v1/interests/[id]/activity/route.ts
@@ -0,0 +1,31 @@
+import { NextResponse } from 'next/server';
+import { eq, and } from 'drizzle-orm';
+
+import { withAuth, withPermission } from '@/lib/api/helpers';
+import { db } from '@/lib/db';
+import { interests } from '@/lib/db/schema/interests';
+import { loadEntityActivity } from '@/lib/services/entity-activity.service';
+import { errorResponse, NotFoundError } from '@/lib/errors';
+
+export const GET = withAuth(
+ withPermission('interests', 'view', async (_req, ctx, params) => {
+ try {
+ const id = params.id;
+ if (!id) throw new NotFoundError('interest');
+ const exists = await db
+ .select({ id: interests.id })
+ .from(interests)
+ .where(and(eq(interests.id, id), eq(interests.portId, ctx.portId)))
+ .limit(1);
+ if (exists.length === 0) throw new NotFoundError('interest');
+ const data = await loadEntityActivity({
+ portId: ctx.portId,
+ entityType: 'interest',
+ entityId: id,
+ });
+ return NextResponse.json({ data });
+ } catch (error) {
+ return errorResponse(error);
+ }
+ }),
+);
diff --git a/src/app/api/v1/yachts/[id]/activity/route.ts b/src/app/api/v1/yachts/[id]/activity/route.ts
new file mode 100644
index 0000000..1253bc1
--- /dev/null
+++ b/src/app/api/v1/yachts/[id]/activity/route.ts
@@ -0,0 +1,31 @@
+import { NextResponse } from 'next/server';
+import { eq, and } from 'drizzle-orm';
+
+import { withAuth, withPermission } from '@/lib/api/helpers';
+import { db } from '@/lib/db';
+import { yachts } from '@/lib/db/schema/yachts';
+import { loadEntityActivity } from '@/lib/services/entity-activity.service';
+import { errorResponse, NotFoundError } from '@/lib/errors';
+
+export const GET = withAuth(
+ withPermission('yachts', 'view', async (_req, ctx, params) => {
+ try {
+ const id = params.id;
+ if (!id) throw new NotFoundError('yacht');
+ const exists = await db
+ .select({ id: yachts.id })
+ .from(yachts)
+ .where(and(eq(yachts.id, id), eq(yachts.portId, ctx.portId)))
+ .limit(1);
+ if (exists.length === 0) throw new NotFoundError('yacht');
+ const data = await loadEntityActivity({
+ portId: ctx.portId,
+ entityType: 'yacht',
+ entityId: id,
+ });
+ return NextResponse.json({ data });
+ } catch (error) {
+ return errorResponse(error);
+ }
+ }),
+);
diff --git a/src/components/berths/berth-tabs.tsx b/src/components/berths/berth-tabs.tsx
index 89e6e26..f057419 100644
--- a/src/components/berths/berth-tabs.tsx
+++ b/src/components/berths/berth-tabs.tsx
@@ -3,6 +3,7 @@
import { type DetailTab } from '@/components/shared/detail-layout';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { TagBadge } from '@/components/shared/tag-badge';
+import { EntityActivityFeed } from '@/components/shared/entity-activity-feed';
import { BerthReservationsTab } from './berth-reservations-tab';
import { BerthInterestsTab } from './berth-interests-tab';
import { BerthInterestPulse } from './berth-interest-pulse';
@@ -250,7 +251,12 @@ export function buildBerthTabs(berth: BerthData): DetailTab[] {
{
id: 'activity',
label: 'Activity',
- content:
Activity log coming soon.
-Loading…
; if (interests.length === 0) { - returnNo interests linked to this yacht.
; + return ( +No interests linked to this yacht
++ Interests for this yacht will appear here once a sales rep links them. Add an interest + from the Interests tab on the owner’s client page. +
+