From a1b77d8b001b7ccbdd6a2b3b6500c9ae19a1373d Mon Sep 17 00:00:00 2001 From: Hardik Date: Sat, 30 May 2026 04:14:03 +0530 Subject: [PATCH] feat(vessels): editable custom code when creating a vessel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Vessel dialog now shows an editable Code field pre-filled with the next auto-generated code (e.g. SITE-004) — user can change it freely - Edit Vessel dialog keeps the code read-only (changing codes on existing data would break PO references) - createVessel action: uses submitted code if provided, auto-generates if left blank, and validates uniqueness before saving Co-Authored-By: Claude Sonnet 4.6 --- App/app/(portal)/admin/vessels/actions.ts | 14 ++++++- App/app/(portal)/admin/vessels/page.tsx | 4 ++ .../(portal)/admin/vessels/vessel-form.tsx | 38 ++++++++++++++----- .../(portal)/admin/vessels/vessels-table.tsx | 4 +- 4 files changed, 48 insertions(+), 12 deletions(-) diff --git a/App/app/(portal)/admin/vessels/actions.ts b/App/app/(portal)/admin/vessels/actions.ts index 8b66426..e6078cc 100644 --- a/App/app/(portal)/admin/vessels/actions.ts +++ b/App/app/(portal)/admin/vessels/actions.ts @@ -11,6 +11,7 @@ type ActionResult = { ok: true } | { error: string }; const vesselSchema = z.object({ name: z.string().min(1, "Vessel name is required"), + code: z.string().optional(), siteId: z.string().optional(), }); @@ -22,12 +23,23 @@ export async function createVessel(formData: FormData): Promise { const parsed = vesselSchema.safeParse({ name: formData.get("name"), + code: (formData.get("code") as string).trim() || undefined, siteId: (formData.get("siteId") as string) || undefined, }); if (!parsed.success) return { error: parsed.error.errors[0]?.message ?? "Validation failed" }; const existingCodes = await db.vessel.findMany({ select: { code: true } }); - const code = nextId("SITE", existingCodes.map((v) => v.code)); + + let code: string; + if (parsed.data.code) { + // User supplied a custom code — validate uniqueness + const conflict = await db.vessel.findUnique({ where: { code: parsed.data.code } }); + if (conflict) return { error: `Code "${parsed.data.code}" is already in use by another vessel.` }; + code = parsed.data.code; + } else { + // Auto-generate next available code + code = nextId("SITE", existingCodes.map((v) => v.code)); + } await db.vessel.create({ data: { name: parsed.data.name, code, siteId: parsed.data.siteId ?? null } }); revalidatePath("/admin/vessels"); diff --git a/App/app/(portal)/admin/vessels/page.tsx b/App/app/(portal)/admin/vessels/page.tsx index f2da75f..a9ab70f 100644 --- a/App/app/(portal)/admin/vessels/page.tsx +++ b/App/app/(portal)/admin/vessels/page.tsx @@ -3,6 +3,7 @@ import { db } from "@/lib/db"; import { hasPermission } from "@/lib/permissions"; import { redirect } from "next/navigation"; import { VesselsTable } from "./vessels-table"; +import { nextId } from "@/lib/id-generators"; import type { Metadata } from "next"; export const metadata: Metadata = { title: "Vessel Management" }; @@ -21,8 +22,11 @@ export default async function AdminVesselsPage() { db.site.findMany({ where: { isActive: true }, orderBy: { name: "asc" }, select: { id: true, name: true } }), ]); + const suggestedCode = nextId("SITE", vessels.map((v) => v.code)); + return ( ({ id: v.id, code: v.code, diff --git a/App/app/(portal)/admin/vessels/vessel-form.tsx b/App/app/(portal)/admin/vessels/vessel-form.tsx index 9114182..2e0d62d 100644 --- a/App/app/(portal)/admin/vessels/vessel-form.tsx +++ b/App/app/(portal)/admin/vessels/vessel-form.tsx @@ -17,15 +17,35 @@ type VesselRow = { const INPUT = "w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20"; -function VesselFormFields({ vessel, sites }: { vessel?: VesselRow; sites: SiteOption[] }) { +function VesselFormFields({ + vessel, + sites, + suggestedCode, +}: { + vessel?: VesselRow; + sites: SiteOption[]; + suggestedCode?: string; +}) { return (
- {vessel && ( -
- -

{vessel.code}

-
- )} +
+ + {vessel ? ( + /* Editing: show code read-only — code changes on existing data would break references */ +

+ {vessel.code} +

+ ) : ( + /* Creating: editable, pre-filled with next available code */ + + )} +
@@ -45,7 +65,7 @@ function VesselFormFields({ vessel, sites }: { vessel?: VesselRow; sites: SiteOp ); } -export function AddVesselButton({ sites }: { sites: SiteOption[] }) { +export function AddVesselButton({ sites, suggestedCode }: { sites: SiteOption[]; suggestedCode?: string }) { const router = useRouter(); const [open, setOpen] = useState(false); const [pending, setPending] = useState(false); @@ -68,7 +88,7 @@ export function AddVesselButton({ sites }: { sites: SiteOption[] }) { setOpen(false)}>
- + {error &&

{error}

}