Untitled

 avatar
unknown
plain_text
17 days ago
12 kB
5
Indexable
import Tab from '@mui/material/Tab';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { TabContext, TabList, TabPanel, TabPanelProps } from '@mui/lab';
import { styled, useTheme } from '@mui/material/styles';
import ItemTextViewer from './ItemTextViewer';
import ItemNativeViewer from './ItemNativeViewer';
import FooterSpacer from '../common/FooterSpacer';
import ItemPdfViewer from './ItemPdfViewer';
import ItemApi from '../../api/ItemApi';
import { handleError } from '../../utils/errorUtils';
import CONSTANTS from '../common/constants';
import FaintInformational from '../common/FaintInformational';
import Informational from '../common/Informational';
import { Box } from '@mui/material';
import { useItemsContext } from '../item/context/ItemsContext';
import { getViewerTypeForItemDefinitionId } from './ItemViewerUtils';
import { useCasesAppContext } from '../case/CasesAppContext';
import { useResizeObserver } from '../../hooks';
import { useMutation, useQuery } from '@tanstack/react-query';

export type ItemArtifact = {
  id: number;
  itemId: number;
  artifactType: string;
  extension: string;
  path: string;
};

type ItemViewerProps = {
  itemId: number;
  itemDefinitionId: number;
  isReOrientatedHorizontally: boolean;
};

const ItemViewer: React.FC<ItemViewerProps> = ({
  itemId,
  itemDefinitionId,
  isReOrientatedHorizontally,
}) => {
  const { caseId } = useCasesAppContext();
  const { isPending, data, error } = useItemArtifacts(itemId, caseId);
  if (error) {
    handleError(
      error,
      'Failed to retrieve item artifacts types, defaulting to Original',
    );
  }

  const itemArtifacts = (data ?? []) as ItemArtifact[];
  const hasArtifacts = itemArtifacts.length > 0;
  const hasMarkupArtifact = itemArtifacts.some(
    ({ artifactType }) => artifactType === CONSTANTS.ARTIFACT_TYPE.MARKUP,
  );
  const artifactsToTabs = useMemo(
    () =>
      itemArtifacts.map((artifact) =>
        artifact.artifactType === CONSTANTS.ARTIFACT_TYPE.WEB_NATIVE
          ? CONSTANTS.ITEM_VIEWER_TAB_TYPE.ORIGINAL
          : (artifact.artifactType as TabType),
      ),
    [itemArtifacts],
  );

  const { itemDefinitions, itemKinds } = useItemsContext();
  const [containerRef, scrollButtons] = useScrollButtonsOnResize();

  const [preferredTab, setPreferredTab] = useState<TabType | null>(null);
  const handleTabChange = (_: React.SyntheticEvent, newValue: TabType) => {
    setPreferredTab(newValue);
  };
  const activeTab = useMemo(() => {
    // Allow users to create Markup on the fly if it doesn't exits
    if (
      (preferredTab && artifactsToTabs.includes(preferredTab)) ||
      preferredTab === CONSTANTS.ITEM_VIEWER_TAB_TYPE.MARKUP
    )
      return preferredTab;
    if (artifactsToTabs.includes(CONSTANTS.ITEM_VIEWER_TAB_TYPE.PDF))
      return CONSTANTS.ITEM_VIEWER_TAB_TYPE.PDF;
    if (artifactsToTabs.includes(CONSTANTS.ITEM_VIEWER_TAB_TYPE.ORIGINAL))
      return CONSTANTS.ITEM_VIEWER_TAB_TYPE.ORIGINAL;
    if (artifactsToTabs.includes(CONSTANTS.ITEM_VIEWER_TAB_TYPE.TEXT))
      return CONSTANTS.ITEM_VIEWER_TAB_TYPE.TEXT;

    // if (artifactsToTabs.includes(CONSTANTS.ITEM_VIEWER_TAB_TYPE.MARKUP))
    //   return CONSTANTS.ITEM_VIEWER_TAB_TYPE.MARKUP;

    return CONSTANTS.ITEM_VIEWER_TAB_TYPE.PDF;
  }, [preferredTab, itemArtifacts]);
  const isNativePdfViewer = useMemo(() => {
    const viewers = getViewerTypeForItemDefinitionId(
      itemDefinitionId,
      itemDefinitions,
      itemKinds,
    );
    return viewers.Native === CONSTANTS.ITEM_KIND_DOCUMENT;
  }, [itemId, itemDefinitionId, itemArtifacts]);
  const isPdfTab = activeTab === CONSTANTS.ITEM_VIEWER_TAB_TYPE.PDF;
  const isMarkupTab = activeTab === CONSTANTS.ITEM_VIEWER_TAB_TYPE.MARKUP;
  const isOriginalTab =
    activeTab === CONSTANTS.ITEM_VIEWER_TAB_TYPE.ORIGINAL && isNativePdfViewer;

  let loading = isPending;

  if (isMarkupTab && !hasMarkupArtifact) {
    const {
      isPending: isCreateMarkupArtifact,
      data: markupArtifact,
      error,
    } = useCreateItemMarkupArtifact(itemId, caseId, !isPending);

    if (error) {
      handleError(error, 'Failed to create Markup Artifact');
    } else {
      itemArtifacts.push(markupArtifact);
    }

    loading = isCreateMarkupArtifact;
  }

  const showItemPdfViewerTab = useMemo(() => {
    if (!itemId) return false;
    if (loading) return false;
    if (!hasArtifacts) return false;

    return isPdfTab || isMarkupTab || isOriginalTab;
  }, [itemId, loading, hasArtifacts, isPdfTab, isMarkupTab, isOriginalTab]);

  const pdfViewerArtifactType = useMemo(() => {
    if (isPdfTab) return CONSTANTS.ARTIFACT_TYPE.PDF;
    if (isMarkupTab) return CONSTANTS.ARTIFACT_TYPE.MARKUP;

    return CONSTANTS.ARTIFACT_TYPE.NATIVE;
  }, [isPdfTab, isMarkupTab, activeTab]);

  return (
    <div
      id="viewer-outline"
      ref={containerRef}
      style={{
        alignContent: 'center',
        display: 'flex',
        flexDirection: 'column',
        height: '100%',
        width: '100%',
        borderColor: 'divider',
        overflow: 'hidden',
      }}
    >
      <TabContext value={activeTab}>
        <ItemViewerTabList
          itemId={itemId}
          scrollButtons={scrollButtons}
          artifactsToTabs={artifactsToTabs}
          onChange={handleTabChange}
        />

        <ItemViewerTabPanelTabs
          itemId={itemId}
          itemDefinitionId={itemDefinitionId}
          isHorizontal={isReOrientatedHorizontally}
          loading={loading}
          hasArtifacts={hasArtifacts}
          isNativePdfViewer={isNativePdfViewer}
          itemArtifacts={itemArtifacts}
        />
      </TabContext>

      <ItemViewerPdfTabPanel
        show={showItemPdfViewerTab}
        itemId={itemId}
        isHorizontal={isReOrientatedHorizontally}
        loading={loading}
        itemArtifacts={itemArtifacts}
        pdfViewerArtifactType={pdfViewerArtifactType}
        editable={isMarkupTab}
      />

      {!isReOrientatedHorizontally ? <FooterSpacer /> : null}
    </div>
  );
};
export default ItemViewer;

type ScrollButton = boolean | 'auto' | undefined;
const useScrollButtonsOnResize = (): [
  React.RefObject<HTMLDivElement>,
  ScrollButton,
] => {
  const ref = useRef<HTMLDivElement>(null);
  const [scrollButtons, setScrollButtons] = useState<ScrollButton>(false);

  useResizeObserver(ref, () => {
    if (ref.current) {
      const actualWidth = ref.current.offsetWidth;
      if (actualWidth < 300) {
        setScrollButtons('auto');
      } else {
        setScrollButtons(false);
      }
    }
  });

  return [ref, scrollButtons];
};

const TabHeader = styled(Tab)({
  minHeight: '30px',
  height: '30px',
});

type TabContainerProps = TabPanelProps & {
  isHorizontal: boolean;
};
const ItemViewerTabPanelContainer = styled(TabPanel, {
  shouldForwardProp: (prop) => prop !== 'isHorizontal',
})<TabContainerProps>(({ isHorizontal }) => ({
  height: isHorizontal ? '100%' : `calc(100% -  99px)`,
  width: '100%',
  overflow: 'hidden',
  padding: 0,
}));

type TabType = 'Original' | 'Pdf' | 'Text';
const ItemViewerTabList: React.FC<{
  itemId: number;
  scrollButtons: ScrollButton;
  artifactsToTabs: string[];
  onChange: (event: React.SyntheticEvent, value: TabType) => void;
}> = ({ itemId, scrollButtons, artifactsToTabs, onChange }) => {
  const theme = useTheme();

  return (
    <TabList
      onChange={onChange}
      sx={{
        width: 'auto',
        minHeight: '28px',
        height: '28px',
        background: theme.palette.gridHeaderRowBackGroundColor.main,
        borderBottom: `1px solid ${theme.palette.divider}`,
      }}
      variant="scrollable"
      scrollButtons={scrollButtons}
      aria-label="documber viewer tabs"
    >
      <TabHeader
        label={CONSTANTS.ITEM_VIEWER_TAB_TYPE.ORIGINAL}
        value={CONSTANTS.ITEM_VIEWER_TAB_TYPE.ORIGINAL}
        disabled={
          !artifactsToTabs.includes(CONSTANTS.ITEM_VIEWER_TAB_TYPE.ORIGINAL)
        }
      />
      <TabHeader
        label={CONSTANTS.ITEM_VIEWER_TAB_TYPE.PDF}
        value={CONSTANTS.ITEM_VIEWER_TAB_TYPE.PDF}
        disabled={!artifactsToTabs.includes(CONSTANTS.ITEM_VIEWER_TAB_TYPE.PDF)}
      />
      <TabHeader
        key={CONSTANTS.ITEM_VIEWER_TAB_TYPE.TEXT}
        label={CONSTANTS.ITEM_VIEWER_TAB_TYPE.TEXT}
        value={CONSTANTS.ITEM_VIEWER_TAB_TYPE.TEXT}
        disabled={
          !artifactsToTabs.includes(CONSTANTS.ITEM_VIEWER_TAB_TYPE.TEXT)
        }
      />
      <TabHeader
        key={CONSTANTS.ITEM_VIEWER_TAB_TYPE.MARKUP}
        label={CONSTANTS.ITEM_VIEWER_TAB_TYPE.MARKUP}
        value={CONSTANTS.ITEM_VIEWER_TAB_TYPE.MARKUP}
        disabled={!itemId}
      />
    </TabList>
  );
};

const ItemViewerTabPanelTabs: React.FC<{
  itemId: number;
  isHorizontal: boolean;
  itemDefinitionId: number;
  loading: boolean;
  hasArtifacts: boolean;
  isNativePdfViewer: boolean;
  itemArtifacts: ItemArtifact[];
}> = ({
  itemId,
  isHorizontal,
  itemDefinitionId,
  loading,
  hasArtifacts,
  isNativePdfViewer,
  itemArtifacts,
}) => {
  if (!itemId) return <FaintInformational message="Please select a Document" />;

  if (loading) return <div>Loading...</div>;

  if (!hasArtifacts)
    return (
      <Informational
        message="Document does not have any artifacts"
        serverity="info"
      />
    );

  return (
    <>
      <ItemViewerTextTabPanel itemId={itemId} isHorizontal={isHorizontal} />

      {!isNativePdfViewer && (
        <ItemViewerOriginalTabPanel
          itemId={itemId}
          itemDefinitionId={itemDefinitionId}
          isHorizontal={isHorizontal}
          itemArtifacts={itemArtifacts}
        />
      )}
    </>
  );
};

const ItemViewerOriginalTabPanel: React.FC<{
  itemId: number;
  isHorizontal: boolean;
  itemDefinitionId: number;
  itemArtifacts: ItemArtifact[];
}> = ({ itemId, itemDefinitionId, isHorizontal, itemArtifacts }) => {
  const ref = useRef<HTMLDivElement>(null);
  const theme = useTheme();

  return (
    <ItemViewerTabPanelContainer
      isHorizontal={isHorizontal}
      value={CONSTANTS.ITEM_VIEWER_TAB_TYPE.ORIGINAL}
      ref={ref}
      sx={{ backgroundColor: theme.palette.background.default }}
    >
      <ItemNativeViewer
        itemId={itemId}
        itemDefinitionId={itemDefinitionId}
        containerRef={ref}
        itemArtifacts={itemArtifacts ?? []}
      />
    </ItemViewerTabPanelContainer>
  );
};

const ItemViewerTextTabPanel: React.FC<{
  itemId: number;
  isHorizontal: boolean;
}> = ({ itemId, isHorizontal }) => {
  return (
    <ItemViewerTabPanelContainer
      isHorizontal={isHorizontal}
      value={CONSTANTS.ITEM_VIEWER_TAB_TYPE.TEXT}
    >
      <ItemTextViewer itemId={itemId} />
    </ItemViewerTabPanelContainer>
  );
};

const ItemViewerPdfTabPanel: React.FC<{
  itemId: number;
  isHorizontal: boolean;
  show: boolean;
  loading: boolean;
  itemArtifacts: ItemArtifact[];
  pdfViewerArtifactType: string;
  editable: boolean;
}> = ({
  itemId,
  show,
  isHorizontal,
  loading,
  pdfViewerArtifactType,
  itemArtifacts,
  editable,
}) => {
  return (
    <Box
      sx={{
        display: show ? 'block' : 'none',
        height: isHorizontal ? '100%' : `calc(100% -  99px)`,
        width: '100%',
        overflow: 'hidden',
        padding: 0,
      }}
    >
      <ItemPdfViewer
        itemId={itemId}
        artifactType={pdfViewerArtifactType}
        itemArtifacts={itemArtifacts}
        editable={editable}
        loading={loading}
      />
    </Box>
  );
};

const useItemArtifacts = (itemId: number, caseId: number) => {
  return useQuery({
    queryKey: ['ItemApi.getItemArtifacts', itemId, caseId],
    queryFn: async ({ signal }) =>
      await ItemApi.getItemArtifacts(itemId, caseId, { signal }),
  });
};

const useCreateItemMarkupArtifact = (
  itemId: number,
  caseId: number,
  enabled: boolean,
) => {
  return useQuery({
    queryKey: ['ItemApi.createItemMarkup', itemId, caseId],
    queryFn: async ({ signal }) =>
      await ItemApi.createItemMarkup(itemId, caseId, { signal }),
    enabled,
  });
};
Leave a Comment