Untitled

 avatar
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