bcgov/citz-imb-staff-purchasing-reimbursement

View on GitHub
app/src/pages/IndividualRequest.tsx

Summary

Maintainability
C
1 day
Test Coverage
import { useCallback, useEffect, useState, useContext } from 'react';
import { RequestStates } from '../helpers/convertState';
import { useNavigate, useParams } from 'react-router-dom';
import axios from 'axios';
import Constants from '../constants/Constants';
import { ReimbursementRequest } from '../interfaces/ReimbursementRequest';
import { useAuthService } from '../keycloak';
import { IFile } from '../interfaces/IFile';
import { Purchase } from '../interfaces/Purchase';
import { Approval } from '../interfaces/Approval';
import RequestForm from '../components/custom/forms/RequestForm';
import BackButton from '../components/bcgov/BackButton';
import { marginBlock } from '../constants/styles';
import { ErrorContext, errorStyles } from '../components/custom/notifications/ErrorWrapper';
import { lastVisitedPage } from '../helpers/navigator';

/**
 * @description A page showing an individual reimbursement requests and all its fields.
 * @returns A React element
 */
const IndividualRequest = () => {
  // TODO: Consolidate these states into a single state
  const [reimbursementRequest, setReimbursementRequest] = useState<
    ReimbursementRequest | undefined
  >(undefined);
  const [approvalFiles, setApprovalFiles] = useState<Array<IFile>>([]);
  const [approvals, setApprovals] = useState<Array<Approval>>([]);
  const [purchaseFiles, setPurchaseFiles] = useState<Array<IFile>>([]);
  const [purchases, setPurchases] = useState<Array<Purchase>>([]);
  const { id } = useParams();
  const { state: authState } = useAuthService();
  const navigate = useNavigate();

  // Page permissions
  const isAdmin = authState.userInfo.client_roles?.includes('admin') || false;
  const [locked, setLocked] = useState<boolean>(false);
  const [showRecord, setShowRecord] = useState<boolean>(true);

  // Error notification
  const { setErrorState } = useContext(ErrorContext);

  // Fired when page is loaded.
  useEffect(() => {
    getReimbursementRequest();
  }, []);

  const tempFile: IFile = {
    file: '',
    name: '',
    size: 0,
    date: new Date(Date.now()).toISOString(),
    deleted: false,
    downloaded: false,
    removed: false,
    source: 'temp',
  };

  // Retrieves a single request's info
  const getReimbursementRequest = useCallback(async () => {
    try {
      const axiosReqConfig = {
        url: `${Constants.BACKEND_URL}/api/requests/${id}`,
        method: `get`,
        headers: {
          Authorization: `Bearer ${authState.accessToken}`,
        },
      };
      const response = await axios(axiosReqConfig);
      if (response.status === 200) {
        // Populate values with existing record
        const reimbursementRequest: ReimbursementRequest = response.data;

        // These arrays need to be as pre-populated, otherwise slice has unexpected behaviour with 0-length lists.
        const purchaseFileArray: Array<IFile> = Array<IFile>(
          reimbursementRequest.purchases.length,
        ).fill(tempFile);
        const approvalFileArray: Array<IFile> = Array<IFile>(
          reimbursementRequest.approvals?.length || 0,
        ).fill(tempFile);

        if (reimbursementRequest.purchases.length > 0) {
          reimbursementRequest.purchases.forEach((purchase, index) => {
            if (purchase.fileObj) {
              purchaseFileArray.splice(index, 0, purchase.fileObj);
            }
          });
        }

        if (reimbursementRequest.approvals) {
          reimbursementRequest.approvals.forEach((approval, index) => {
            if (approval.fileObj) {
              approvalFileArray.splice(index, 0, approval.fileObj);
            }
          });
        }

        // Set new states
        setReimbursementRequest(reimbursementRequest);
        setPurchases(reimbursementRequest.purchases);
        setPurchaseFiles(purchaseFileArray);

        if (reimbursementRequest.approvals) {
          setApprovals(reimbursementRequest.approvals);
          setApprovalFiles(approvalFileArray);
        }

        // Determine locked status of fields. If not admin and the state of request is not INCOMPLETE, lock the fields.
        if (!isAdmin && reimbursementRequest.state !== RequestStates.INCOMPLETE) {
          setLocked(true);
        }
      }
    } catch (e: unknown) {
      if (axios.isAxiosError(e)) {
        const { status } = e.response!;
        switch (status) {
          case 401:
            console.warn('User is unauthenticated. Redirecting to login.');
            setErrorState({
              text: 'User is unauthenticated. Redirecting to login.',
              open: true,
            });
            window.location.reload();
            break;
          case 403:
            console.warn('User is not authorized to view record.');
            setErrorState({
              text: 'User is not authorized to view record.',
              open: true,
            });
            // Request was not a success. User didn't get a record back, so don't show the form.
            setShowRecord(false);
            break;
          case 404:
            // Record doesn't exist
            navigate(lastVisitedPage());
            break;
          default:
            console.warn(e);
            break;
        }
      } else {
        console.warn(e);
      }
    }
  }, []);

  // Fired when the record is updated (i.e. User selects UPDATE.)
  const handleUpdate = async () => {
    // Apply purchaseFiles to purchases
    const combinedPurchases = [...purchases];
    combinedPurchases.forEach((purchase, index) => {
      combinedPurchases[index].fileObj = purchaseFiles[index];
    });

    // Apply approvalFiles to approvals
    const combinedApprovals = [...approvals];
    combinedApprovals.forEach((approval, index) => {
      combinedApprovals[index].fileObj = approvalFiles[index];
    });

    try {
      const axiosReqConfig = {
        url: `${Constants.BACKEND_URL}/api/requests/${id}`,
        method: `patch`,
        headers: {
          Authorization: `Bearer ${authState.accessToken}`,
        },
        data: {
          ...reimbursementRequest,
          purchases: combinedPurchases,
          approvals: combinedApprovals.filter((approval) => approval.fileObj),
          isAdmin,
        },
      };
      const response = await axios(axiosReqConfig);
      if (response.status === 200) {
        sessionStorage.removeItem('target-page');
        setErrorState({
          text: 'Update Successful',
          open: true,
          style: errorStyles.success,
        });
        // Return to previous page
        navigate(lastVisitedPage());
      }
    } catch (e) {
      console.warn('Record could not be updated.');
      if (axios.isAxiosError(e) && e.code === '401') {
        setErrorState({
          text: 'User is unauthenticated. Redirecting to login.',
          open: true,
        });
        window.location.reload();
      } else {
        setErrorState({
          text: 'Update was unsuccessful.',
          open: true,
          style: errorStyles.error,
        });
      }
    }
  };

  return showRecord ? (
    <RequestForm
      {...{
        locked,
        isAdmin,
        handleUpdate,
        reimbursementRequest,
        setReimbursementRequest,
        purchases,
        setPurchases,
        purchaseFiles,
        setPurchaseFiles,
        approvals,
        setApprovals,
        approvalFiles,
        setApprovalFiles,
      }}
    />
  ) : (
    <>
      <h1>You do not have access to this record.</h1>
      <p style={marginBlock}>
        If you think you are seeing this by mistake, contact your administrator.
      </p>
      <BackButton />
    </>
  );
};

export default IndividualRequest;