diff --git a/src/app/api/v1/tenancies/[id]/renew/route.ts b/src/app/api/v1/tenancies/[id]/renew/route.ts new file mode 100644 index 00000000..5915d938 --- /dev/null +++ b/src/app/api/v1/tenancies/[id]/renew/route.ts @@ -0,0 +1,26 @@ +import { NextResponse } from 'next/server'; + +import { withAuth, withPermission, type RouteHandler } from '@/lib/api/helpers'; +import { parseBody } from '@/lib/api/route-helpers'; +import { errorResponse } from '@/lib/errors'; +import { renewTenancy } from '@/lib/services/berth-tenancies.service'; +import { assertTenanciesModuleEnabled } from '@/lib/services/tenancies-module.service'; +import { renewTenancySchema } from '@/lib/validators/tenancies'; + +const renewHandler: RouteHandler = async (req, ctx, params) => { + try { + await assertTenanciesModuleEnabled(ctx.portId); + const body = await parseBody(req, renewTenancySchema); + const result = await renewTenancy(params.id!, ctx.portId, body, { + userId: ctx.userId, + portId: ctx.portId, + ipAddress: ctx.ipAddress, + userAgent: ctx.userAgent, + }); + return NextResponse.json({ data: result }); + } catch (error) { + return errorResponse(error); + } +}; + +export const POST = withAuth(withPermission('tenancies', 'manage', renewHandler)); diff --git a/src/app/api/v1/tenancies/[id]/transfer/route.ts b/src/app/api/v1/tenancies/[id]/transfer/route.ts new file mode 100644 index 00000000..19eede6f --- /dev/null +++ b/src/app/api/v1/tenancies/[id]/transfer/route.ts @@ -0,0 +1,26 @@ +import { NextResponse } from 'next/server'; + +import { withAuth, withPermission, type RouteHandler } from '@/lib/api/helpers'; +import { parseBody } from '@/lib/api/route-helpers'; +import { errorResponse } from '@/lib/errors'; +import { transferTenancy } from '@/lib/services/berth-tenancies.service'; +import { assertTenanciesModuleEnabled } from '@/lib/services/tenancies-module.service'; +import { transferTenancySchema } from '@/lib/validators/tenancies'; + +const transferHandler: RouteHandler = async (req, ctx, params) => { + try { + await assertTenanciesModuleEnabled(ctx.portId); + const body = await parseBody(req, transferTenancySchema); + const result = await transferTenancy(params.id!, ctx.portId, body, { + userId: ctx.userId, + portId: ctx.portId, + ipAddress: ctx.ipAddress, + userAgent: ctx.userAgent, + }); + return NextResponse.json({ data: result }); + } catch (error) { + return errorResponse(error); + } +}; + +export const POST = withAuth(withPermission('tenancies', 'manage', transferHandler)); diff --git a/src/components/tenancies/tenancy-detail.tsx b/src/components/tenancies/tenancy-detail.tsx index 8ed232c2..63e687b6 100644 --- a/src/components/tenancies/tenancy-detail.tsx +++ b/src/components/tenancies/tenancy-detail.tsx @@ -4,7 +4,17 @@ import { useState } from 'react'; import Link from 'next/link'; import type { Route } from 'next'; import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { ArrowLeft, Bell, Download, FileSignature, Mail, Pencil, StopCircle } from 'lucide-react'; +import { + ArrowLeft, + ArrowRightLeft, + Bell, + Download, + FileSignature, + Mail, + Pencil, + RefreshCw, + StopCircle, +} from 'lucide-react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; @@ -25,6 +35,8 @@ import { apiFetch } from '@/lib/api/client'; import { toastError } from '@/lib/api/toast-error'; import { ClientLink, YachtLink, BerthLink } from '@/components/tenancies/tenancy-list'; import { TenancyEditDialog } from '@/components/tenancies/tenancy-edit-dialog'; +import { TenancyRenewDialog } from '@/components/tenancies/tenancy-renew-dialog'; +import { TenancyTransferDialog } from '@/components/tenancies/tenancy-transfer-dialog'; interface TenancyDoc { id: string; @@ -121,6 +133,8 @@ interface TenancyDetailProps { export function TenancyDetail({ tenancyId, portSlug }: TenancyDetailProps) { const [endDialogOpen, setEndDialogOpen] = useState(false); const [editDialogOpen, setEditDialogOpen] = useState(false); + const [renewDialogOpen, setRenewDialogOpen] = useState(false); + const [transferDialogOpen, setTransferDialogOpen] = useState(false); const tenancy = useQuery<{ data: TenancyData }>({ queryKey: ['tenancy', tenancyId], queryFn: () => apiFetch(`/api/v1/tenancies/${tenancyId}`), @@ -292,10 +306,20 @@ export function TenancyDetail({ tenancyId, portSlug }: TenancyDetailProps) { Edit {res.status === 'active' && ( - + <> + + + + )}