feat(berths): inline spec-PDF preview, manual-pin badge, maintenance module toggle, under-offer popover
Post-cutover UAT batch #3: - #62 Spec tab renders the current berth spec PDF inline (lazy PdfViewer, toggleable, default-open) + explicit download. Interest Documents tab already previews/downloads linked deal docs inline (verified). - #57 Surface berths.status_override_mode through the interest-berths API; linked-berth rows show an amber "Pin overrides pitch" badge + corrected consequence copy when a berth is specifically-pitched but manually pinned (the soft-pin wins on the public map). - #63 New maintenance-module gate (maintenance_module_enabled, default on): registry + admin Settings toggle, maintenance-module.service, port-provider useMaintenanceModuleEnabled, layout wiring, buildBerthTabs hides the Maintenance tab when off, and both maintenance log routes assert the gate. - #66 BerthOccupancyChip: >1 competing interest opens a popover listing every deal (name + stage + in-EOI/primary + link); single stays a direct link. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ import { getPortBrandingConfig } from '@/lib/services/port-config';
|
||||
import { isTenanciesModuleEnabled } from '@/lib/services/tenancies-module.service';
|
||||
import { isExpensesModuleEnabled } from '@/lib/services/expenses-module.service';
|
||||
import { isResidentialModuleEnabled } from '@/lib/services/residential-module.service';
|
||||
import { isMaintenanceModuleEnabled } from '@/lib/services/maintenance-module.service';
|
||||
|
||||
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
|
||||
const headerList = await headers();
|
||||
@@ -127,12 +128,29 @@ export default async function DashboardLayout({ children }: { children: React.Re
|
||||
const residentialModuleByPort: Record<string, boolean> =
|
||||
Object.fromEntries(residentialModuleEntries);
|
||||
|
||||
// Per-port maintenance-module gate. Defaults to enabled (registry
|
||||
// default) so existing ports keep the berth Maintenance tab on deploy.
|
||||
// Resolved server-side so the tab SSRs in/out without flicker.
|
||||
const maintenanceModuleEntries = await Promise.all(
|
||||
ports.map(async (p) => {
|
||||
try {
|
||||
return [p.id, await isMaintenanceModuleEnabled(p.id)] as const;
|
||||
} catch {
|
||||
// Conservative default on lookup failure: keep the feature visible.
|
||||
return [p.id, true] as const;
|
||||
}
|
||||
}),
|
||||
);
|
||||
const maintenanceModuleByPort: Record<string, boolean> =
|
||||
Object.fromEntries(maintenanceModuleEntries);
|
||||
|
||||
return (
|
||||
<QueryProvider>
|
||||
<PortProvider
|
||||
ports={ports}
|
||||
defaultPortId={ports[0]?.id ?? null}
|
||||
tenanciesModuleByPort={tenanciesModuleByPort}
|
||||
maintenanceModuleByPort={maintenanceModuleByPort}
|
||||
>
|
||||
<PermissionsProvider>
|
||||
<SocketProvider>
|
||||
|
||||
@@ -4,12 +4,14 @@ import { withAuth, withPermission } from '@/lib/api/helpers';
|
||||
import { parseBody } from '@/lib/api/route-helpers';
|
||||
import { updateMaintenanceLogSchema } from '@/lib/validators/berths';
|
||||
import { updateMaintenanceLog, deleteMaintenanceLog } from '@/lib/services/berths.service';
|
||||
import { assertMaintenanceModuleEnabled } from '@/lib/services/maintenance-module.service';
|
||||
import { errorResponse } from '@/lib/errors';
|
||||
|
||||
// PATCH /api/v1/berths/[id]/maintenance/[logId]
|
||||
export const PATCH = withAuth(
|
||||
withPermission('berths', 'edit', async (req, ctx, params) => {
|
||||
try {
|
||||
await assertMaintenanceModuleEnabled(ctx.portId);
|
||||
const body = await parseBody(req, updateMaintenanceLogSchema);
|
||||
const log = await updateMaintenanceLog(params.id!, params.logId!, ctx.portId, body, {
|
||||
userId: ctx.userId,
|
||||
@@ -28,6 +30,7 @@ export const PATCH = withAuth(
|
||||
export const DELETE = withAuth(
|
||||
withPermission('berths', 'edit', async (_req, ctx, params) => {
|
||||
try {
|
||||
await assertMaintenanceModuleEnabled(ctx.portId);
|
||||
await deleteMaintenanceLog(params.id!, params.logId!, ctx.portId, {
|
||||
userId: ctx.userId,
|
||||
portId: ctx.portId,
|
||||
|
||||
@@ -4,12 +4,14 @@ import { withAuth, withPermission } from '@/lib/api/helpers';
|
||||
import { parseBody } from '@/lib/api/route-helpers';
|
||||
import { addMaintenanceLogSchema } from '@/lib/validators/berths';
|
||||
import { getMaintenanceLogs, addMaintenanceLog } from '@/lib/services/berths.service';
|
||||
import { assertMaintenanceModuleEnabled } from '@/lib/services/maintenance-module.service';
|
||||
import { errorResponse } from '@/lib/errors';
|
||||
|
||||
// GET /api/v1/berths/[id]/maintenance
|
||||
export const GET = withAuth(
|
||||
withPermission('berths', 'view', async (req, ctx, params) => {
|
||||
try {
|
||||
await assertMaintenanceModuleEnabled(ctx.portId);
|
||||
const logs = await getMaintenanceLogs(params.id!, ctx.portId);
|
||||
return NextResponse.json({ data: logs });
|
||||
} catch (error) {
|
||||
@@ -22,6 +24,7 @@ export const GET = withAuth(
|
||||
export const POST = withAuth(
|
||||
withPermission('berths', 'edit', async (req, ctx, params) => {
|
||||
try {
|
||||
await assertMaintenanceModuleEnabled(ctx.portId);
|
||||
const body = await parseBody(req, addMaintenanceLogSchema);
|
||||
const log = await addMaintenanceLog(params.id!, ctx.portId, body, {
|
||||
userId: ctx.userId,
|
||||
|
||||
Reference in New Issue
Block a user