I will toss you some code and at the and formulate what i want:
addOffshoreForm.tsx :
import {
ButtonContent,
DestructiveButton,
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
Icon,
If,
Input,
Toaster,
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import type * as z from "zod";
import React, { useEffect, useState } from "react";
import { toast } from "sonner";
import wretch from "wretch";
import type { VesselInfo } from "@/components/forms/offshoreInfoForm";
import {
fieldsNameHelper,
formSchema,
numberFields,
toTitleCase,
} from "@/components/forms/offshoreInfoForm";
import type { PostgrestError } from "@supabase/supabase-js";
interface AddOffshoreFormProps {
fields: string[];
vesselType: string;
vesselSubType: string;
vesselMmsi: string;
returnMmsi: (mmsi: any) => void;
}
export const AddOffshoreForm = ({
fields,
vesselType,
vesselSubType,
vesselMmsi,
returnMmsi,
}: AddOffshoreFormProps) => {
const excludeFields: string[] = [
"id",
"vessel_id",
"postion_date",
"eta_iso",
"destination",
"longitude",
"latitude",
"callsign",
];
const disabledFields: string[] = ["type", "subType"];
const [selectedType, setSelectedType] = useState<string>();
const [selectedSubType, setSelectedSubType] = useState<string>(vesselSubType);
const [mmsi, setMmsi] = useState<string>(vesselMmsi.toString());
// update selectedType and selectedSubType when vesselType and vesselSubType change
useEffect(() => {
setSelectedType(vesselType);
setSelectedSubType(vesselSubType);
setMmsi(vesselMmsi);
}, [vesselType, vesselSubType, vesselMmsi]);
fields = fields?.sort().filter((item) => !excludeFields.includes(item));
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
type: selectedType,
subType: selectedSubType,
mmsi: mmsi,
},
});
async function onSubmit(values: z.infer<typeof formSchema>) {
const addVessel: VesselInfo = Object.entries(values).reduce(
(acc: VesselInfo, [key, value]) => {
const vesselInfoKey: string = fieldsNameHelper[key].name;
if (key === "subType") {
acc[vesselInfoKey] = selectedSubType?.toLowerCase() || "";
} else if (key === "type") {
acc[vesselInfoKey] = selectedType?.toLowerCase() || "";
} else if (key === "mmsi") {
acc[vesselInfoKey] = mmsi;
} else {
acc[vesselInfoKey] = (value || "").toString().toLowerCase() || "";
}
return acc;
},
{},
);
try {
const response = await wretch("/api/form/insertOffshoreVessel")
.post(addVessel)
.res();
if (response.ok) {
toast.success("Success!", {
description: "Vessel details inserted.",
});
returnMmsi(Number(mmsi));
} else {
toast.error("Error submitting form");
}
} catch (error) {
toast.error("Something went wrong", {
description: (error as PostgrestError | null)?.message,
});
}
}
const reset = form.reset;
useEffect(() => {
reset({}, { keepValues: false });
reset(
{
type: vesselType,
subType: selectedSubType || vesselSubType,
mmsi: mmsi,
},
{ keepValues: false },
);
}, [reset]);
return (
<div className={"mt-4"}>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
{Object.keys(formSchema.shape).map((key, index) => (
<>
<If condition={fields?.includes(fieldsNameHelper[key].name)}>
<If condition={key !== "buildYear"}>
<FormField
key={key + index}
control={form.control}
name={key as any}
render={({ field }) => (
<FormItem className={"flex flex-row items-center"}>
<FormLabel className={"w-64 text-lg font-light"}>
{toTitleCase(fieldsNameHelper[key].name)}
</FormLabel>
<div className={"mr-2"}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="text-black hover:text-black/50 dark:text-white dark:hover:text-white/50">
<Icon name="unknown" style="h-5 w-5" />
</TooltipTrigger>
<TooltipContent>
<p>{fieldsNameHelper[key].description}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl className={"w-full"}>
<Input
className={"text-md font-light"}
required={false}
{...field}
type={
numberFields.includes(key) ? "number" : "text"
}
value={field.value ?? ""}
disabled={disabledFields.includes(key)}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</If>
<If condition={key === "buildYear"}>
<FormField
control={form.control}
name="buildYear"
render={({ field }) => (
<FormItem className={"flex flex-row items-center"}>
<FormLabel className={"w-64 text-lg font-light"}>
Build year
</FormLabel>
<div className={"mr-2"}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="text-black hover:text-black/50 dark:text-white dark:hover:text-white/50">
<Icon name="unknown" style="h-5 w-5" />
</TooltipTrigger>
<TooltipContent>
<p>{fieldsNameHelper[key].description}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl className={"w-full"}>
<Input
{...field}
className={"text-md font-light"}
type="number"
min={1900}
max={2100}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</If>
</If>
</>
))}
<div className="flex flex-row justify-end">
<DestructiveButton type="submit" className="mt-4">
<ButtonContent>Add vessel</ButtonContent>
</DestructiveButton>
</div>
<Toaster />
</form>
</Form>
</div>
);
};
offshoreInfoForm.tsx :
import {
ButtonContent,
DestructiveButton,
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
Icon,
If,
Input,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
Toaster,
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
import React, { useEffect, useState } from "react";
import { toast } from "sonner";
import type { PostgrestError } from "@supabase/supabase-js";
import wretch from "wretch";
export interface VesselInfo {
[key: string]: string | undefined;
}
interface AddOffshoreFormProps {
vesselInfo: VesselInfo;
vesselMmsi: number;
}
export const formSchema = z.object({
beneficialOwner: z.string().optional(),
bollardPull: z.string().optional(),
bsW: z.string().optional(),
bucketCapacity: z.string().optional(),
buildYear: z.string().optional(),
cableTanks: z.string().optional(),
callsign: z.string().optional(),
class: z.string().optional(),
clearDeckArea: z.string().optional(),
commercialOwner: z.string().optional(),
craneSize: z.string().optional(),
cranes: z.string().optional(),
crudeProcessingCapacity: z.string().optional(),
cutterPower: z.string().optional(),
deckArea: z.string().optional(),
deckLoadCapacity: z.string().optional(),
designType: z.string().optional(),
destination: z.string().optional(),
dredgeDepth: z.string().optional(),
dredgePumpPower: z.string().optional(),
dynamicPositioningClass: z.string().optional(),
engineTypePower: z.string().optional(),
etaIso: z.string().optional(),
excavatorPower: z.string().optional(),
exportableCrude: z.string().optional(),
fleetName: z.string().optional(),
fleetOperator: z.string().optional(),
fluidCapacity: z.string().optional(),
fuelCapacity: z.string().optional(),
gas: z.string().optional(),
grossTonnage: z.string().optional(),
hS: z.string().optional(),
heliDeck: z.string().optional(),
hopperCapacity: z.string().optional(),
id: z.string().optional(),
imo: z.string().optional(),
jetPumps: z.string().optional(),
latitude: z.string().optional(),
legs: z.string().optional(),
longitude: z.string().optional(),
mainEnginesTypePower: z.string().optional(),
maxDredgeWidth: z.string().optional(),
maxFlow: z.string().optional(),
maxSpeed: z.string().optional(),
mmsi: z.string().optional(),
moonPool: z.string().optional(),
noOfBerths: z.string().optional(),
oilContentWaterDischarged: z.string().optional(),
pdfDownloaded: z.string().optional(),
pipeDiameter: z.string().optional(),
positionTime: z.string().optional(),
producedWater: z.string().optional(),
pumpingDistance: z.string().optional(),
rov: z.string().optional(),
rvp: z.string().optional(),
slopTanks: z.string().optional(),
specialisedCarriageCapability: z.string().optional(),
speedConsumption: z.string().optional(),
subType: z.string().optional(),
totalPowerInstalled: z.string().optional(),
towingCapacity: z.string().optional(),
tugType: z.string().optional(),
type: z.string().optional(),
vesselClass: z.string().optional(),
vesselName: z.string().optional(),
vesselType: z.string().optional(),
vesselId: z.string().optional(),
waterInjectionCapacity: z.string().optional(),
});
type FieldsNameHelper = {
[key: string]: { name: string; description: string };
};
export const fieldsNameHelper: FieldsNameHelper = {
beneficialOwner: {
name: "beneficial owner",
description: "The beneficial owner of the vessel",
},
bollardPull: {
name: "bollard pull",
description: "The force exerted by the vessel while tethered",
},
bsW: {
name: "bs&w",
description: "Basic sediment and water in oil measurement",
},
bucketCapacity: {
name: "bucket capacity",
description: "Maximum carrying capacity of the bucket",
},
buildYear: {
name: "build year",
description: "Year the vessel was constructed",
},
cableTanks: {
name: "cable tanks",
description: "Storage tanks for cables on the vessel",
},
callsign: {
name: "callsign",
description: "The identifying signal letters or numbers of the vessel",
},
class: {
name: "class",
description: "Classification society to which the vessel is registered",
},
clearDeckArea: {
name: "clear deck area",
description: "Total area available for use on the deck",
},
commercialOwner: {
name: "commercial owner",
description: "The commercial owner of the vessel",
},
craneSize: {
name: "crane size",
description: "The size and lifting capacity of the crane on the vessel",
},
cranes: {
name: "cranes",
description: "The number and type of cranes installed on the vessel",
},
crudeProcessingCapacity: {
name: "crude processing capacity",
description: "Capacity to process crude oil onboard",
},
cutterPower: {
name: "cutter power",
description: "Power output of the cutting equipment on the vessel",
},
deckArea: {
name: "deck area",
description: "Total area of the vessel's deck",
},
deckLoadCapacity: {
name: "deck load capacity",
description: "Maximum load capacity of the vessel’s deck",
},
designType: {
name: "design/type",
description: "The design and type classification of the vessel",
},
destination: {
name: "destination",
description: "The intended destination of the vessel",
},
dredgeDepth: {
name: "dredge depth",
description: "Maximum depth at which the vessel can dredge",
},
dredgePumpPower: {
name: "dredge pump power",
description: "Power of the dredging pump",
},
dynamicPositioningClass: {
name: "dynamic positioning class",
description: "Classification of the vessel's dynamic positioning system",
},
engineTypePower: {
name: "engine type & power",
description: "Type and power output of the vessel’s engines",
},
etaIso: {
name: "eta_iso",
description: "Estimated time of arrival following ISO standards",
},
excavatorPower: {
name: "excavator power",
description: "Power output of the excavator on the vessel",
},
exportableCrude: {
name: "exportable crude",
description: "Volume of crude oil that can be exported by the vessel",
},
fleetName: {
name: "fleet name",
description: "Name of the fleet the vessel belongs to",
},
fleetOperator: {
name: "fleet operator",
description: "Operator managing the fleet of vessels",
},
fluidCapacity: {
name: "fluid capacity",
description: "Total capacity for fluid storage on the vessel",
},
fuelCapacity: {
name: "fuel capacity",
description: "Fuel storage capacity of the vessel",
},
gas: {
name: "gas",
description:
"Capabilities or systems related to gas handling on the vessel",
},
grossTonnage: {
name: "gross tonnage",
description: "Overall internal volume of the vessel",
},
hS: {
name: "h2s",
description: "Presence and handling of hydrogen sulfide on the vessel",
},
heliDeck: {
name: "heli-deck",
description: "Helicopter landing area available on the vessel",
},
hopperCapacity: {
name: "hopper capacity",
description: "Storage capacity of the hopper on the dredging vessel",
},
id: { name: "id", description: "Unique identifier of the vessel" },
imo: {
name: "imo",
description:
"International Maritime Organization number assigned to the vessel",
},
jetPumps: {
name: "jet pumps",
description: "Types and capacities of jet pumps on the vessel",
},
latitude: {
name: "latitude",
description: "Current latitude position of the vessel",
},
legs: {
name: "legs",
description:
"Structural supports used in vessels, particularly in offshore applications",
},
longitude: {
name: "longitude",
description: "Current longitude position of the vessel",
},
mainEnginesTypePower: {
name: "main engines type & power",
description: "Type and power of the main engines on the vessel",
},
maxDredgeWidth: {
name: "max dredge width",
description: "Maximum width the vessel can dredge",
},
maxFlow: {
name: "max flow",
description: "Maximum flow rate achievable by the vessel’s systems",
},
maxSpeed: {
name: "max speed (buckets/min)",
description:
"Maximum operational speed of the vessel in buckets per minute",
},
mmsi: {
name: "mmsi",
description:
"Maritime Mobile Service Identity used for vessel tracking and safety",
},
moonPool: {
name: "moon pool",
description:
"An opening in the bottom or side of the vessel for underwater operations",
},
noOfBerths: {
name: "no of berths",
description: "Number of sleeping berths available on the vessel",
},
oilContentWaterDischarged: {
name: "oil content water discharged",
description: "Measure of oil content in discharged water",
},
pdfDownloaded: {
name: "pdf downloaded (y/n)",
description:
"Indicates if the vessel’s documentation has been downloaded as PDF",
},
pipeDiameter: {
name: "pipe diameter",
description: "Diameter of pipes used onboard the vessel",
},
positionTime: {
name: "position_time",
description: "Timestamp for the vessel's position",
},
producedWater: {
name: "produced water",
description: "Water produced as a byproduct of the vessel’s operations",
},
pumpingDistance: {
name: "pumping distance",
description: "Maximum distance over which the vessel can pump materials",
},
rov: {
name: "rov",
description: "Remotely operated vehicle capabilities of the vessel",
},
rvp: {
name: "rvp",
description:
"Reid vapor pressure measurements related to the vessel’s operations",
},
slopTanks: {
name: "slop tanks",
description:
"Tanks on the vessel used for temporary storage of oily waste water",
},
specialisedCarriageCapability: {
name: "specialised carriage capability",
description:
"Specific carriage capabilities of the vessel for specialized cargoes",
},
speedConsumption: {
name: "speed/consumption",
description: "The vessel’s speed relative to fuel consumption",
},
subType: {
name: "sub_type",
description: "Sub-type classification of the vessel",
},
totalPowerInstalled: {
name: "total power installed",
description: "Total power output of all installed machinery",
},
towingCapacity: {
name: "towing capacity",
description: "Capacity of the vessel to tow other vessels or structures",
},
tugType: {
name: "tug type",
description: "Classification of the tug based on its design and use",
},
type: {
name: "type",
description: "General type classification of the vessel",
},
vesselClass: {
name: "vessel class",
description: "Class designation for regulatory and operational purposes",
},
vesselName: {
name: "vessel name",
description: "Official name of the vessel",
},
vesselType: {
name: "vessel type",
description: "Specific type classification based on structure and function",
},
vesselId: {
name: "vessel_id",
description: "Unique identification number of the vessel",
},
waterInjectionCapacity: {
name: "water injection capacity",
description: "Capacity for injecting water into surrounding sea or seabed",
},
};
export const numberFields: string[] = [
"bollardPull",
"buildYear",
"clearDeckArea",
"grossTonnage",
"noOfBerths",
"dynamicPositioningClass",
"fuelCapacity",
"fluidCapacity",
"deckArea",
"crudeProcessingCapacity",
"deckLoadCapacity",
"waterInjectionCapacity",
"speedConsumption",
"totalPowerInstalled",
"dredgeDepth",
"maxDredgeWidth",
"cuttingPower",
"pumpingDistance",
"hopperCapacity",
"bucketCapacity",
"maxSpeed",
"mmsi",
"imo",
];
export const uppercaseFields: string[] = [
"eta_iso",
"id",
"imo",
"mmsi",
"h2s",
"bs&w",
"rov",
"rvp",
];
export function toTitleCase(str: string): string {
if (uppercaseFields.includes(str)) {
return str.toUpperCase().replace("_", " ");
} else {
str = str.replace("_", " ");
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}
}
export const OffshoreInfoForm = ({
vesselInfo,
vesselMmsi,
}: AddOffshoreFormProps) => {
const disabledFields: string[] = [
"destination",
"etaIso",
"id",
"imo",
"latitude",
"longitude",
"mmsi",
"positionTime",
"vesselId",
];
const [selectedType, setSelectedType] = useState<string | null>(
vesselInfo["type"] ?? null,
);
const defaultFieldsValues = Object.fromEntries(
Object.keys(fieldsNameHelper).map((key) => [
key,
vesselInfo[fieldsNameHelper[key].name as string] ?? "",
]),
);
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: defaultFieldsValues,
});
const reset = form.reset;
useEffect(() => {
reset({}, { keepValues: false });
reset(defaultFieldsValues, { keepValues: false });
}, [reset, defaultFieldsValues]);
async function onSubmit(values: z.infer<typeof formSchema>) {
const updatedVesselInfo: VesselInfo = Object.entries(values).reduce(
(acc: VesselInfo, [key, value]) => {
const vesselInfoKey: string = fieldsNameHelper[key].name;
acc[vesselInfoKey] = (value || "").toString().toLowerCase();
return acc;
},
{},
);
try {
const response = await wretch("/api/form/offshoreVessel")
.post(updatedVesselInfo)
.res();
if (response.ok) {
await wretch(`/api/form/offshoreVessel?mmsi=${vesselMmsi}`)
.get()
.json();
toast.success("Success!", {
description: "Vessel details updated.",
});
} else {
toast.error("Error submitting form");
}
} catch (error) {
toast.error("Something went wrong", {
description: (error as PostgrestError | null)?.message,
});
}
}
return (
<div className="mt-6 flex w-full flex-col gap-2 p-2 sm:w-1/2">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
{Object.keys(formSchema.shape).map((key, index) => (
<>
<If
condition={Object.keys(vesselInfo).includes(
fieldsNameHelper[key].name,
)}
>
<If
condition={
key !== "type" && key !== "subType" && key !== "buildYear"
}
>
<FormField
key={key + index}
control={form.control}
name={key as any}
render={({ field }) => (
<FormItem className={"flex flex-row items-center"}>
<FormLabel className={"w-64 text-lg font-light"}>
{toTitleCase(fieldsNameHelper[key].name)}
</FormLabel>
<div className={"mr-2"}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="text-black hover:text-black/50 dark:text-white dark:hover:text-white/50">
<Icon name="unknown" style="h-5 w-5" />
</TooltipTrigger>
<TooltipContent>
<p>{fieldsNameHelper[key].description}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl className={"w-full"}>
<Input
{...field}
className={"text-md font-light"}
type={
numberFields.includes(key) ? "number" : "text"
}
value={field.value ?? ""}
disabled={disabledFields.includes(key)}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</If>
<If condition={key === "type"}>
<FormField
control={form.control}
name="type"
render={({ field }) => (
<FormItem className={"flex flex-row items-center"}>
<FormLabel className={"w-64 text-lg font-light"}>
Type
</FormLabel>
<div className={"mr-2"}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="text-black hover:text-black/50 dark:text-white dark:hover:text-white/50">
<Icon name="unknown" style="h-5 w-5" />
</TooltipTrigger>
<TooltipContent>
<p>{fieldsNameHelper[key].description}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<Select
onValueChange={(value: any) => {
field.onChange(value);
setSelectedType(value);
}}
value={field.value ?? ""}
>
<FormControl className={"text-md w-full font-light"}>
<SelectTrigger>
<SelectValue placeholder="Select a type" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value={"offshore support vessel"}>
Offshore Support Vessel
</SelectItem>
<SelectItem
value={"oil exploration and drilling vessel"}
>
Oil Exploration And Drilling Vessel
</SelectItem>
<SelectItem value={"offshore production vessel"}>
Offshore Production Vessel
</SelectItem>
<SelectItem value={"offshore construction vessel"}>
Offshore Construction Vessel
</SelectItem>
</SelectContent>
</Select>
</FormItem>
)}
/>
</If>
<If condition={key === "subType"}>
<FormField
control={form.control}
name="subType"
render={({ field }) => (
<FormItem className={"flex flex-row items-center"}>
<FormLabel className={"w-64 text-lg font-light"}>
Sub type
</FormLabel>
<div className={"mr-2"}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="text-black hover:text-black/50 dark:text-white dark:hover:text-white/50">
<Icon name="unknown" style="h-5 w-5" />
</TooltipTrigger>
<TooltipContent>
<p>{fieldsNameHelper[key].description}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<Select
onValueChange={field.onChange}
value={field.value ?? ""}
>
<FormControl className={"text-md w-full font-light"}>
<SelectTrigger>
<SelectValue placeholder="Select a sub type" />
</SelectTrigger>
</FormControl>
{selectedType &&
selectedType === "offshore support vessel" && (
<SelectContent>
<SelectItem value={"mpsv"}>MPSV</SelectItem>
<SelectItem value={"ahts"}>AHTS</SelectItem>
<SelectItem value={"tug"}>Tug</SelectItem>
<SelectItem value={"psv"}>PSV</SelectItem>
</SelectContent>
)}
{selectedType &&
selectedType === "offshore production vessel" && (
<SelectContent>
<SelectItem value={"fpso"}>FPSO</SelectItem>
</SelectContent>
)}
{selectedType &&
selectedType === "offshore construction vessel" && (
<SelectContent>
<SelectItem value={"dredger"}>
Dredger
</SelectItem>
<SelectItem value={"clv"}>CLV</SelectItem>
<SelectItem value={"wiv"}>WIV</SelectItem>
</SelectContent>
)}
</Select>
</FormItem>
)}
/>
</If>
<If condition={key === "buildYear"}>
<FormField
control={form.control}
name="buildYear"
render={({ field }) => (
<FormItem className={"flex flex-row items-center"}>
<FormLabel className={"w-64 text-lg font-light"}>
Build year
</FormLabel>
<div className={"mr-2"}>
<TooltipProvider>
<Tooltip>
<TooltipTrigger className="text-black hover:text-black/50 dark:text-white dark:hover:text-white/50">
<Icon name="unknown" style="h-5 w-5" />
</TooltipTrigger>
<TooltipContent>
<p>{fieldsNameHelper[key].description}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<FormControl className={"w-full"}>
<Input
{...field}
className={"text-md font-light"}
type="number"
defaultValue={vesselInfo["build year"]}
min={1900}
max={2100}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</If>
</If>
</>
))}
<If
condition={vesselInfo && Object.keys(formSchema.shape).length > 0}
>
<div className="flex flex-row justify-end">
<DestructiveButton type="submit" className="mt-4">
<ButtonContent>Update</ButtonContent>
</DestructiveButton>
</div>
</If>
<Toaster />
</form>
</Form>
</div>
);
};
insertOffshoreVessel.ts :
import type {NextApiRequest, NextApiResponse} from "next";
import wretch from "wretch";
import {env} from "@/env/client.mjs";
/**
* @swagger
* /api/form/addLinearReport:
* post:
* summary: Create a bug report in linear
* description: Within the support team.
* parameters:
* - in: body
* name: report
* required: true
* type: object
* description: The report object
* produces:
* - application/json
* responses:
* 200:
* description: Success!
* 400:
* description: No id present as a query parameter.
* 401:
* description: The user does not have an active session or is not authenticated.
* 405:
* description: Method not allowed.
* 500:
* description: Error!
* tags:
* - deprecated
*/
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const {method} = req;
switch (method) {
case "POST":
try {
const response = await wretch(`${env.NEXT_PUBLIC_UI_FLASK_URL}insert_vessel`)
.post(req.body)
.json();
return res.status(200).json(response);
} catch (error) {
console.error("Error origin: POST /api/form/insertOffshoreVessel", error);
return res.status(500).json(error);
}
default:
res.setHeader("Allow", ["POST"]);
return res
.status(405)
.send(`Method ${method ?? "Undefined"} Not Allowed`);
}
}
import type {NextApiRequest, NextApiResponse} from "next";
import wretch from "wretch";
import {env} from "@/env/client.mjs";
/**
* @swagger
* /api/form/addLinearReport:
* post:
* summary: Create a bug report in linear
* description: Within the support team.
* parameters:
* - in: body
* name: report
* required: true
* type: object
* description: The report object
* produces:
* - application/json
* responses:
* 200:
* description: Success!
* 400:
* description: No id present as a query parameter.
* 401:
* description: The user does not have an active session or is not authenticated.
* 405:
* description: Method not allowed.
* 500:
* description: Error!
* tags:
* - deprecated
*/
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const {method} = req;
switch (method) {
case "GET":
try {
const mmsi = req.query.mmsi;
const subType = req.query.sub_type;
let response;
if (subType) {
response = await wretch(`${env.NEXT_PUBLIC_UI_FLASK_URL}attributes_by_sub_type?sub_type=${subType}`)
.get()
.json();
} else {
response = await wretch(`${env.NEXT_PUBLIC_UI_FLASK_URL}get_mmsi?mmsi=${mmsi}`)
.get()
.json();
}
return res.status(200).json(response);
} catch (error) {
console.error("Error origin: GET /api/form/offshoreVessel | ", error);
return res.status(500).json(error);
}
case "POST":
try {
const response = await wretch(`${env.NEXT_PUBLIC_UI_FLASK_URL}update_vessel`)
.post(req.body)
.json();
return res.status(200).json(response);
} catch (error) {
console.error("Error origin: POST /api/form/offshoreVessel | ", error);
return res.status(500).json(error);
}
case "INSERT_POST":
try {
const response = await wretch(`${env.NEXT_PUBLIC_UI_FLASK_URL}insert_vessel`)
.post(req.body)
.json();
return res.status(200).json(response);
} catch (error) {
console.error("Error origin: POST /api/form/offshoreVessel", error);
return res.status(500).json(error);
}
default:
res.setHeader("Allow", ["POST", "GET"]);
return res
.status(405)
.send(`Method ${method ?? "Undefined"} Not Allowed`);
}
}
offshoreVessel.ts :
import type {NextApiRequest, NextApiResponse} from "next";
import wretch from "wretch";
import {env} from "@/env/client.mjs";
/**
* @swagger
* /api/form/addLinearReport:
* post:
* summary: Create a bug report in linear
* description: Within the support team.
* parameters:
* - in: body
* name: report
* required: true
* type: object
* description: The report object
* produces:
* - application/json
* responses:
* 200:
* description: Success!
* 400:
* description: No id present as a query parameter.
* 401:
* description: The user does not have an active session or is not authenticated.
* 405:
* description: Method not allowed.
* 500:
* description: Error!
* tags:
* - deprecated
*/
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const {method} = req;
switch (method) {
case "GET":
try {
const mmsi = req.query.mmsi;
const subType = req.query.sub_type;
let response;
if (subType) {
response = await wretch(`${env.NEXT_PUBLIC_UI_FLASK_URL}attributes_by_sub_type?sub_type=${subType}`)
.get()
.json();
} else {
response = await wretch(`${env.NEXT_PUBLIC_UI_FLASK_URL}get_mmsi?mmsi=${mmsi}`)
.get()
.json();
}
return res.status(200).json(response);
} catch (error) {
console.error("Error origin: GET /api/form/offshoreVessel | ", error);
return res.status(500).json(error);
}
case "POST":
try {
const response = await wretch(`${env.NEXT_PUBLIC_UI_FLASK_URL}update_vessel`)
.post(req.body)
.json();
return res.status(200).json(response);
} catch (error) {
console.error("Error origin: POST /api/form/offshoreVessel | ", error);
return res.status(500).json(error);
}
case "INSERT_POST":
try {
const response = await wretch(`${env.NEXT_PUBLIC_UI_FLASK_URL}insert_vessel`)
.post(req.body)
.json();
return res.status(200).json(response);
} catch (error) {
console.error("Error origin: POST /api/form/offshoreVessel", error);
return res.status(500).json(error);
}
default:
res.setHeader("Allow", ["POST", "GET"]);
return res
.status(405)
.send(`Method ${method ?? "Undefined"} Not Allowed`);
}
}
offshoreVesselInfo.tsx :
import type { NextPage } from "next";
import {
BackHomeButton,
CommandPalletteButton,
If,
Input,
MinimalPage,
PageHeading,
Select,
SelectContent,
SelectItem,
SelectTrigger,
} from "ui";
import { BugReportButton, CommandInterface, Navigation } from "@/components";
import type { ChangeEvent } from "react";
import { useEffect, useState } from "react";
import wretch from "wretch";
import { AddOffshoreForm } from "@/components/forms/addOffshoreForm";
import { OffshoreInfoForm } from "@/components/forms/offshoreInfoForm";
interface VesselInfo {
[key: string]: string | undefined;
}
const OffshoreVesselInfo: NextPage = () => {
const [vesselInfo, setVesselInfo] = useState<VesselInfo | null>();
const [mmsi, setMmsi] = useState<number | null>();
const [vesselSubType, setVesselSubType] = useState<string>("");
const [offshoreType, setOffshoreType] = useState<string>("");
const [newOffshoreForm, setNewOffshoreForm] = useState<any>();
// get vessel info data from the api endpoint
useEffect(() => {
const fetchData = async () => {
if (mmsi) {
setVesselInfo(null);
try {
const response = await wretch(`/api/form/offshoreVessel?mmsi=${mmsi}`)
.get()
.res();
if (response.ok) {
const data: VesselInfo | null = await response.json();
setVesselInfo(data);
} else {
console.error("Error:", response.statusText);
}
} catch (error) {
console.error("Error:", error);
}
} else {
setVesselInfo(null);
}
};
fetchData();
}, [mmsi]);
useEffect(() => {
const fetchData = async () => {
if (vesselSubType) {
try {
const response = await wretch(
`/api/form/offshoreVessel?sub_type=${vesselSubType}`,
)
.get()
.res();
if (response.ok) {
const data: any = await response.json();
setNewOffshoreForm(data);
} else {
console.error("Error:", response.statusText);
}
} catch (error) {
console.error("Error:", error);
}
} else {
setNewOffshoreForm(null);
}
};
fetchData();
}, [vesselSubType]);
function capitalizeFirstLetter(string: string): string {
return string.replace(/\b\w/g, function (l) {
return l.toUpperCase();
});
}
useEffect(() => {
if (!mmsi) {
setVesselSubType("");
setOffshoreType("");
}
}, [mmsi]);
function getVesselAfterInsert(mmsi: number) {
setMmsi(mmsi);
const fetchData = async () => {
if (mmsi) {
try {
const response = await wretch(`/api/form/offshoreVessel?mmsi=${mmsi}`)
.get()
.res();
if (response.ok) {
const data: VesselInfo | null = await response.json();
setVesselInfo(data);
} else {
console.error("Error:", response.statusText);
}
} catch (error) {
console.error("Error:", error);
}
} else {
setVesselInfo(null);
}
};
fetchData();
}
return (
<MinimalPage
pageTitle={"Offshore Vessel Info | Email Interface"}
pageDescription={"Spot Ship Email Interface | Offshore Vessel Info"}
commandPrompt
>
<div className="flex w-full flex-row justify-between pl-1 pt-1">
<div>
<BackHomeButton />
</div>
<Navigation />
<div className="flex flex-row gap-4">
<BugReportButton />
<CommandPalletteButton />
<CommandInterface />
</div>
</div>
<PageHeading text="Offshore Vessel Info" />
<div className="mt-6 flex w-full flex-col gap-2 p-2 sm:w-1/2">
<div className={"flex flex-row items-center"}>
<div
className={"w-28 text-2xl font-normal text-black dark:text-white"}
>
MMSI
</div>
<Input
type={"number"}
className={"text-md"}
maxLength={9}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setMmsi(parseFloat(event.target.value) || null);
}}
/>
</div>
<div className={"font-sans text-sm text-black dark:text-white"}>
International Maritime Mobile Service Identity (MMSI) from 0 to
9-digit identifier.
</div>
</div>
<If condition={!!(mmsi && vesselInfo?.["message"])}>
<div className={"mt-2 flex w-full flex-col gap-2 p-2 sm:w-1/2"}>
<div
className={"mb-6 text-center text-lg text-black dark:text-white"}
>
No information found for MMSI: {mmsi}. Add new offshore vessel
</div>
<div>
<div className={"grid grid-cols-2 gap-4"}>
<Select
onValueChange={(type: any) => {
setVesselSubType("");
if (!offshoreType || offshoreType !== type) {
setOffshoreType(type);
} else if (offshoreType === type) {
setOffshoreType("");
}
}}
>
<SelectTrigger>
{capitalizeFirstLetter(offshoreType) || "Select vessel type"}
</SelectTrigger>
<SelectContent>
<SelectItem value={"offshore support vessel"}>
Offshore Support Vessel
</SelectItem>
<SelectItem value={"oil exploration and drilling vessel"}>
Oil Exploration And Drilling Vessel
</SelectItem>
<SelectItem value={"offshore production vessel"}>
Offshore Production Vessel
</SelectItem>
<SelectItem value={"offshore construction vessel"}>
Offshore Construction Vessel
</SelectItem>
</SelectContent>
</Select>
<If condition={offshoreType === "offshore support vessel"}>
<div>
<Select
onValueChange={(value: any) => {
setVesselSubType(value);
}}
>
<SelectTrigger>
{vesselSubType.toUpperCase() || "Select vessel sub type"}
</SelectTrigger>
<SelectContent>
<SelectItem value={"mpsv"}>MPSV</SelectItem>
<SelectItem value={"ahts"}>AHTS</SelectItem>
<SelectItem value={"tug"}>Tug</SelectItem>
<SelectItem value={"psv"}>PSV</SelectItem>
</SelectContent>
</Select>
</div>
</If>
<If condition={offshoreType === "offshore production vessel"}>
<div>
<Select
onValueChange={(value: any) => {
setVesselSubType(value);
}}
>
<SelectTrigger>
{vesselSubType.toUpperCase() || "Select vessel sub type"}
</SelectTrigger>
<SelectContent>
<SelectItem value={"fpso"}>FPSO</SelectItem>
</SelectContent>
</Select>
</div>
</If>
<If condition={offshoreType === "offshore construction vessel"}>
<div>
<Select
onValueChange={(value: any) => {
setVesselSubType(value);
}}
>
<SelectTrigger>
{vesselSubType.toUpperCase() || "Select vessel sub type"}
</SelectTrigger>
<SelectContent>
<SelectItem value={"dredger"}>Dredger</SelectItem>
<SelectItem value={"clv"}>CLV</SelectItem>
<SelectItem value={"wiv"}>WIV</SelectItem>
</SelectContent>
</Select>
</div>
</If>
</div>
<If condition={!!newOffshoreForm}>
<div className={"mt-6"}>
<AddOffshoreForm
fields={newOffshoreForm}
vesselType={offshoreType}
vesselSubType={vesselSubType}
vesselMmsi={mmsi ? mmsi.toString() : ""}
returnMmsi={(mmsi: number) => {
getVesselAfterInsert(mmsi);
setOffshoreType("");
setVesselSubType("");
}}
/>
</div>
</If>
</div>
</div>
</If>
<If
condition={
!!(
mmsi &&
vesselInfo &&
!vesselInfo?.["message"] &&
typeof vesselInfo === "object"
)
}
>
<OffshoreInfoForm
vesselInfo={vesselInfo as VesselInfo}
vesselMmsi={mmsi as number}
/>
</If>
</MinimalPage>
);
};
export default OffshoreVesselInfo;
and now, I want to create similar structure for regular vessel. We have less in this part :
export const fieldsNameHelper: FieldsNameHelper = {
beneficialOwner: {
name: "beneficial owner",
description: "The beneficial owner of the vessel",
},
bollardPull: {
name: "bollard pull",
description: "The force exerted by the vessel while tethered",
},
bsW: {
name: "bs&w",
description: "Basic sediment and water in oil measurement",
},
bucketCapacity: {
name: "bucket capacity",
description: "Maximum carrying capacity of the bucket",
},
buildYear: {
name: "build year",
description: "Year the vessel was constructed",
},
cableTanks: {
name: "cable tanks",
description: "Storage tanks for cables on the vessel",
},
callsign: {
name: "callsign",
description: "The identifying signal letters or numbers of the vessel",
},
class: {
name: "class",
description: "Classification society to which the vessel is registered",
},
clearDeckArea: {
name: "clear deck area",
description: "Total area available for use on the deck",
},
commercialOwner: {
name: "commercial owner",
description: "The commercial owner of the vessel",
},
craneSize: {
name: "crane size",
description: "The size and lifting capacity of the crane on the vessel",
},
cranes: {
name: "cranes",
description: "The number and type of cranes installed on the vessel",
},
crudeProcessingCapacity: {
name: "crude processing capacity",
description: "Capacity to process crude oil onboard",
},
cutterPower: {
name: "cutter power",
description: "Power output of the cutting equipment on the vessel",
},
deckArea: {
name: "deck area",
description: "Total area of the vessel's deck",
},
deckLoadCapacity: {
name: "deck load capacity",
description: "Maximum load capacity of the vessel’s deck",
},
designType: {
name: "design/type",
description: "The design and type classification of the vessel",
},
destination: {
name: "destination",
description: "The intended destination of the vessel",
},
dredgeDepth: {
name: "dredge depth",
description: "Maximum depth at which the vessel can dredge",
},
dredgePumpPower: {
name: "dredge pump power",
description: "Power of the dredging pump",
},
dynamicPositioningClass: {
name: "dynamic positioning class",
description: "Classification of the vessel's dynamic positioning system",
},
engineTypePower: {
name: "engine type & power",
description: "Type and power output of the vessel’s engines",
},
etaIso: {
name: "eta_iso",
description: "Estimated time of arrival following ISO standards",
},
excavatorPower: {
name: "excavator power",
description: "Power output of the excavator on the vessel",
},
exportableCrude: {
name: "exportable crude",
description: "Volume of crude oil that can be exported by the vessel",
},
fleetName: {
name: "fleet name",
description: "Name of the fleet the vessel belongs to",
},
fleetOperator: {
name: "fleet operator",
description: "Operator managing the fleet of vessels",
},
fluidCapacity: {
name: "fluid capacity",
description: "Total capacity for fluid storage on the vessel",
},
fuelCapacity: {
name: "fuel capacity",
description: "Fuel storage capacity of the vessel",
},
gas: {
name: "gas",
description:
"Capabilities or systems related to gas handling on the vessel",
},
grossTonnage: {
name: "gross tonnage",
description: "Overall internal volume of the vessel",
},
hS: {
name: "h2s",
description: "Presence and handling of hydrogen sulfide on the vessel",
},
heliDeck: {
name: "heli-deck",
description: "Helicopter landing area available on the vessel",
},
hopperCapacity: {
name: "hopper capacity",
description: "Storage capacity of the hopper on the dredging vessel",
},
id: { name: "id", description: "Unique identifier of the vessel" },
imo: {
name: "imo",
description:
"International Maritime Organization number assigned to the vessel",
},
jetPumps: {
name: "jet pumps",
description: "Types and capacities of jet pumps on the vessel",
},
latitude: {
name: "latitude",
description: "Current latitude position of the vessel",
},
legs: {
name: "legs",
description:
"Structural supports used in vessels, particularly in offshore applications",
},
longitude: {
name: "longitude",
description: "Current longitude position of the vessel",
},
mainEnginesTypePower: {
name: "main engines type & power",
description: "Type and power of the main engines on the vessel",
},
maxDredgeWidth: {
name: "max dredge width",
description: "Maximum width the vessel can dredge",
},
maxFlow: {
name: "max flow",
description: "Maximum flow rate achievable by the vessel’s systems",
},
maxSpeed: {
name: "max speed (buckets/min)",
description:
"Maximum operational speed of the vessel in buckets per minute",
},
mmsi: {
name: "mmsi",
description:
"Maritime Mobile Service Identity used for vessel tracking and safety",
},
moonPool: {
name: "moon pool",
description:
"An opening in the bottom or side of the vessel for underwater operations",
},
noOfBerths: {
name: "no of berths",
description: "Number of sleeping berths available on the vessel",
},
oilContentWaterDischarged: {
name: "oil content water discharged",
description: "Measure of oil content in discharged water",
},
pdfDownloaded: {
name: "pdf downloaded (y/n)",
description:
"Indicates if the vessel’s documentation has been downloaded as PDF",
},
pipeDiameter: {
name: "pipe diameter",
description: "Diameter of pipes used onboard the vessel",
},
positionTime: {
name: "position_time",
description: "Timestamp for the vessel's position",
},
producedWater: {
name: "produced water",
description: "Water produced as a byproduct of the vessel’s operations",
},
pumpingDistance: {
name: "pumping distance",
description: "Maximum distance over which the vessel can pump materials",
},
rov: {
name: "rov",
description: "Remotely operated vehicle capabilities of the vessel",
},
rvp: {
name: "rvp",
description:
"Reid vapor pressure measurements related to the vessel’s operations",
},
slopTanks: {
name: "slop tanks",
description:
"Tanks on the vessel used for temporary storage of oily waste water",
},
specialisedCarriageCapability: {
name: "specialised carriage capability",
description:
"Specific carriage capabilities of the vessel for specialized cargoes",
},
speedConsumption: {
name: "speed/consumption",
description: "The vessel’s speed relative to fuel consumption",
},
subType: {
name: "sub_type",
description: "Sub-type classification of the vessel",
},
totalPowerInstalled: {
name: "total power installed",
description: "Total power output of all installed machinery",
},
towingCapacity: {
name: "towing capacity",
description: "Capacity of the vessel to tow other vessels or structures",
},
tugType: {
name: "tug type",
description: "Classification of the tug based on its design and use",
},
type: {
name: "type",
description: "General type classification of the vessel",
},
vesselClass: {
name: "vessel class",
description: "Class designation for regulatory and operational purposes",
},
vesselName: {
name: "vessel name",
description: "Official name of the vessel",
},
vesselType: {
name: "vessel type",
description: "Specific type classification based on structure and function",
},
vesselId: {
name: "vessel_id",
description: "Unique identification number of the vessel",
},
waterInjectionCapacity: {
name: "water injection capacity",
description: "Capacity for injecting water into surrounding sea or seabed",
},
};
and everywhere we have fields or name like this above. Those are things that we will be updating in our db so similar to whats above:
imo: {
name: "IMO",
description: "International Maritime Organization identifier",
},
cargo_type: {
name: "Cargo Type",
description: "The type of cargo the vessel carries",
},
cargo_sub_type: {
name: "Cargo Sub Type",
description: "The subtype of cargo the vessel carries",
},
mmsi: { name: "MMSI", description: "Maritime Mobile Service Identity" },
vessel_name: { name: "Vessel Name", description: "The name of the vessel" },
year_of_build: {
name: "Year of Build",
description: "The year the vessel was built",
},
flag: { name: "Flag", description: "The flag country code" },
grt: { name: "GRT", description: "Gross Registered Tonnage" },
dwt: { name: "DWT", description: "Dead Weight Tonnage" },
overall_length: {
name: "Overall Length",
description: "The overall length of the vessel",
},
beam: { name: "Beam", description: "The beam of the vessel" },
maximum_draft: {
name: "Maximum Draft",
description:
"The deepest point of the vessel below the waterline when fully loaded",
},
};
+ also