Untitled
unknown
plain_text
8 months ago
13 kB
9
Indexable
import AddIcon from '@mui/icons-material/Add';
import {
Box,
Button,
FormControl,
FormLabel,
IconButton,
Input,
ModalClose,
Option,
Select,
Stack,
Typography,
} from '@mui/joy';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
// API and utility imports
import { RemoveCircleOutline } from '@mui/icons-material';
import { useParams } from 'react-router-dom';
import { fetchCharterers, registerCharterer } from '../../api/charterers';
import { ApiResponse } from '../../api/types';
import { linkChartererToVoyage } from '../../api/voyages';
import { ETSModal } from '../../components/Modals/ETSModal';
import { showSnackbar } from '../../components/Notifications/Snackbar';
import { useContractForm } from '../../hooks/useContractForm';
import { useContractMutation } from '../../hooks/useContractMutation';
import { Charterer } from '../../model/charterers';
import { ContractStatus, Currency } from '../../model/contracts';
import { Vessel, Voyage, VoyageLinkCharterer } from '../../model/vessel';
import AddNewContractContent from '../Contents/AddNewContractContent';
interface AddContractFormInput {
VoyageID: string;
VesselID: string;
VesselName: string;
Charterer: string;
ChartererId: string;
PortOfDeparture: string;
PortOfArrival: string;
Status: ContractStatus;
Currency: Currency;
Amount: number;
}
const VesselDetailsLinkChartererModal = ({
showModal,
onCloseModal,
voyage,
vessels,
}: {
showModal: boolean;
onCloseModal: () => void;
voyage: Voyage | null;
vessels: Vessel[] | undefined;
}) => {
const queryClient = useQueryClient();
const [isLoading, setIsLoading] = useState(false);
const [currentStep, setCurrentStep] = useState(0);
const { vesselId } = useParams();
const [showNewChartererInput, setShowNewChartererInput] = useState(false);
const [chartererId, setChartererId] = useState<string>('');
const { handleSubmit, reset, setValue, control } = useForm<
AddContractFormInput & { NewChartererName: string }
>({
criteriaMode: 'all',
reValidateMode: 'onChange',
mode: 'onChange',
defaultValues: {
VoyageID: '',
VesselID: '',
VesselName: '',
Charterer: '',
ChartererId: '',
PortOfDeparture: '',
PortOfArrival: '',
Status: ContractStatus.ACTUAL,
Currency: Currency.EUA,
Amount: 0,
NewChartererName: '',
},
});
const { data: charterersData, isLoading: isCharterersLoading } = useQuery({
queryKey: ['charterers'],
queryFn: fetchCharterers,
});
const contractForm = useContractForm(voyage);
useEffect(() => {
if (voyage) {
setValue('VesselName', voyage.VesselName);
setValue('PortOfDeparture', voyage.PortOfDeparture);
setValue('PortOfArrival', voyage.PortOfArrival);
setValue('VesselID', vesselId as string);
}
}, [voyage, setValue, vesselId]);
const resetModal = () => {
reset();
setCurrentStep(0);
setShowNewChartererInput(false);
onCloseModal();
};
const goBackStep = () => {
setCurrentStep(currentStep - 1);
};
// Use the contractMutation hook for handling contract creation
const { submitContract, isLoading: isContractLoading } = useContractMutation(chartererId, () => {
// On successful contract creation
queryClient.invalidateQueries({ queryKey: ['vessel', voyage?.VesselID] });
queryClient.invalidateQueries({ queryKey: ['voyages'] });
resetModal();
});
// Combined mutation for charterer creation and linking
const chartererMutation = useMutation({
mutationFn: async (data: { createChartererData: Charterer; linkData: VoyageLinkCharterer }) => {
setIsLoading(true);
let newChartererId = chartererId;
// First, create the charterer if it's a new one
if (showNewChartererInput) {
const response = await registerCharterer(data.createChartererData);
if (!response.success) {
showSnackbar('Failed to register charterer', 'error', 5000);
return;
}
newChartererId = '' + response.message;
data.linkData.VoyageCharterer = newChartererId;
setChartererId(newChartererId);
}
// Secondly, link the charterer
const response: ApiResponse = await linkChartererToVoyage(
voyage?.VoyageID || '',
voyage?.VesselID || '',
data.linkData,
);
setIsLoading(false);
if (!response.success) {
// error handling
switch (response.status) {
case 409:
showSnackbar('Charterer already linked to this voyage', 'warning', 5000);
return;
default:
throw new Error('Failed to link charterer to voyage');
}
}
// Return the charterer ID for the next step
return newChartererId;
},
onSuccess: (newChartererId) => {
// Move to the next step with the charterer ID set
if (newChartererId) {
setChartererId(newChartererId);
}
setCurrentStep(currentStep + 1);
},
onError: (error) => {
showSnackbar('Failed to register charterer. Please try again.', 'error', 5000, 'Error');
},
onSettled: () => {
setIsLoading(false);
},
});
const handleLinkCharterer = (formData: AddContractFormInput & { NewChartererName: string }) => {
if (!formData) {
showSnackbar('No data provided', 'warning', 3000);
return;
}
const createChartererData: Charterer = {
Name: formData.NewChartererName,
};
const linkData: VoyageLinkCharterer = {
VoyageCharterer: formData.ChartererId,
};
chartererMutation.mutate({
createChartererData,
linkData,
});
};
const onSubmit = (formData: AddContractFormInput & { NewChartererName: string }) => {
if (currentStep === 0) {
handleLinkCharterer(formData);
} else {
// For contract creation, get values from the contract form instead of the main form
const contractFormValues = contractForm.getValues();
submitContract({
VoyageID: voyage?.VoyageID ?? '',
VesselID: vesselId || '',
VesselName: formData.VesselName,
Charterer: chartererId,
PortOfDeparture: formData.PortOfDeparture,
PortOfArrival: formData.PortOfArrival,
Status: contractFormValues.Status || ContractStatus.ACTUAL,
Currency: contractFormValues.Currency || Currency.EUA,
Amount: parseFloat(contractFormValues.Amount?.toString() || '0'),
});
}
};
const hasCharterers = Array.isArray(charterersData) && charterersData.length > 0;
const renderLinkChartererStep = () => (
<>
<Box
id="modal-title"
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
gap: '24px',
mb: '24px',
}}
>
<Typography
level="hextralarge"
sx={(theme) => ({
color: theme.vars.palette.primary[700],
})}
>
Link Charterer
</Typography>
<ModalClose variant="plain" sx={{}} />
<Typography level="pmedium" sx={(theme) => ({ color: theme.vars.palette.neutral[500] })}>
Select or create a new charterer to link to this voyage.
</Typography>
</Box>
<Box
sx={() => ({
display: 'flex',
flexDirection: 'column',
gap: '24px',
width: '100%',
justifyContent: 'center',
})}
>
<FormControl>
<FormLabel>Charterer</FormLabel>
<Controller
name="Charterer"
control={control}
disabled={!hasCharterers || showNewChartererInput}
render={({ field }) => (
<Select
{...field}
placeholder="Select Charterer"
onChange={(event, newValue) => {
const selectedCharterer = charterersData?.find(
(charterer) => charterer.Name === newValue,
);
setValue('Charterer', selectedCharterer?.Name || '');
setValue('ChartererId', selectedCharterer?.ID || '');
setChartererId(selectedCharterer?.ID || '');
field.onChange(newValue);
}}
>
{isCharterersLoading ? (
<Option value="" disabled>
Loading...
</Option>
) : (
charterersData?.map((charterer) => (
<Option key={charterer.ID} value={charterer.Name}>
{charterer.Name}
</Option>
))
)}
</Select>
)}
/>
</FormControl>
<FormControl>
{!showNewChartererInput ? (
<Button
variant="plain"
startDecorator={<AddIcon />}
onClick={() => setShowNewChartererInput(true)}
sx={{ alignSelf: 'flex-start', pl: 0 }}
>
Create new charterer
</Button>
) : (
<>
<FormLabel>New Charterer Name</FormLabel>
<Controller
name="NewChartererName" // Use the new field
control={control}
rules={{ required: 'Charterer name is required' }} // Add validation rule
render={({ field, fieldState }) => (
<>
<Stack direction="row" gap="8px">
<Input
{...field}
placeholder="Enter charterer name"
onChange={(e) => {
setValue('NewChartererName', e.target.value); // Update the new field
setValue('ChartererId', ''); // Clear the ChartererId
}}
sx={{
width: '100%',
flexGrow: 1,
}}
error={!!fieldState.error} // Highlight input on error
/>
<IconButton variant="plain" onClick={() => setShowNewChartererInput(false)}>
<RemoveCircleOutline
sx={{
fontSize: '20px',
}}
/>
</IconButton>
</Stack>
{fieldState.error && ( // Display error message
<Typography color="danger" sx={{ mt: 1 }}>
{fieldState.error.message}
</Typography>
)}
</>
)}
/>
</>
)}
</FormControl>
<Box
sx={{
display: 'flex',
justifyContent: 'flex-end',
gap: '16px',
}}
>
<Button variant="outlined" onClick={resetModal}>
Cancel
</Button>
<Button variant="solid" onClick={handleSubmit(onSubmit)} loading={isLoading}>
Next: Add Contract
</Button>
</Box>
</Box>
</>
);
const renderAddNewContractStep = () => (
<AddNewContractContent
isEdit={false}
voyage={voyage}
vessels={vessels}
contract={null}
control={contractForm.control}
register={contractForm.register}
setValue={contractForm.setValue}
getValues={contractForm.getValues}
addContractLoading={isContractLoading}
onSubmit={handleSubmit(onSubmit)}
secondaryButtonAction={goBackStep}
secondaryButtonText={'Back'}
/>
);
const renderStep = () => {
switch (currentStep) {
case 0:
return renderLinkChartererStep();
case 1:
return renderAddNewContractStep();
default:
return <>Not found</>;
}
};
return (
<ETSModal showModal={showModal} handleClose={resetModal} minWidth="520px">
<form onSubmit={handleSubmit(onSubmit)}>{renderStep()}</form>
</ETSModal>
);
};
export default VesselDetailsLinkChartererModal;
Editor is loading...
Leave a Comment