Untitled
unknown
plain_text
22 days ago
13 kB
2
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