Skip to content

Commit

Permalink
Merge pull request #489 from masslight/aykhan/fix-pdf-support
Browse files Browse the repository at this point in the history
Enable pdf file type for file uploads
  • Loading branch information
AykhanAhmadli authored Oct 16, 2024
2 parents d56ce14 + 8d58937 commit 5886bf0
Show file tree
Hide file tree
Showing 16 changed files with 292 additions and 138 deletions.
51 changes: 48 additions & 3 deletions packages/ottehr-components/lib/components/form/FileUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,25 @@ import { BoldPurpleInputLabel } from './BoldPurpleInputLabel';
import UploadComponent from './UploadComponent';
import CardComponent from './CardComponent';
import { safelyCaptureException } from '../../helpers';
import NonImageCardComponent from './NonImageCardComponent';

interface FileUploadProps {
name: string;
label: string;
defaultValue?: string;
fileUploadType?: string;
options: FileUploadOptions;
}

const FileUpload: FC<FileUploadProps> = ({ name, label, defaultValue, options }) => {
const FileUpload: FC<FileUploadProps> = ({ name, label, defaultValue, options, fileUploadType }) => {
const { description, onUpload, uploadFile, uploadFailed, resetUploadFailed, onClear, fileType } = options;
const theme = useTheme();

const { setValue } = useFormContext();
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [fileUrl, setFileUrl] = useState<string | undefined>(defaultValue);
const [fileName, setFileName] = useState<string | undefined>(undefined);

// If default value exists get a presignedUrl from the defaultValue and assign it to previewUrl
useEffect(() => {
Expand All @@ -45,8 +48,16 @@ const FileUpload: FC<FileUploadProps> = ({ name, label, defaultValue, options })
// unless you set its value because CardComponent does not render an <input />
if (fileUrl && previewUrl !== 'Not Found' && previewUrl !== null && !loading) {
setValue(name, defaultValue);
let fileTypeExtension = '';
switch (fileUploadType) {
case 'application/pdf':
fileTypeExtension = 'pdf';
break;
}
// TODO I hate this but I don't think we upload the existing file name
setFileName(`[Previously uploaded file].${fileTypeExtension}`);
}
}, [defaultValue, fileUrl, loading, name, previewUrl, setValue]);
}, [defaultValue, fileUploadType, fileUrl, loading, name, previewUrl, setValue]);

const handleFileUpload = (event: ChangeEvent<HTMLInputElement>): string | null => {
try {
Expand All @@ -61,6 +72,7 @@ const FileUpload: FC<FileUploadProps> = ({ name, label, defaultValue, options })
uploadFile(fileType, tempURL);
onUpload((prev) => ({ ...prev, [fileType]: { ...prev[fileType], fileData: file } }));
resetUploadFailed();
setFileName(file.name);
return file.name;
} else {
console.log('No files selected');
Expand All @@ -69,6 +81,7 @@ const FileUpload: FC<FileUploadProps> = ({ name, label, defaultValue, options })
} catch (error) {
console.error('Error occurred during file upload:', error);
safelyCaptureException(error);
setFileName(undefined);
return null;
}
};
Expand All @@ -81,12 +94,24 @@ const FileUpload: FC<FileUploadProps> = ({ name, label, defaultValue, options })
name={name}
uploadDescription="Failed to upload card. Please try again"
handleFileUpload={handleFileUpload}
fileUploadType={fileUploadType}
/>
);
}

// If no default value, check for a temporary previewUrl
if (!fileUrl && previewUrl) {
if (fileUploadType) {
return (
<NonImageCardComponent
name={name}
fileName={fileName}
setFileUrl={setFileUrl}
setPreviewUrl={setPreviewUrl}
onClear={onClear}
/>
);
}
return (
<CardComponent
name={name}
Expand All @@ -108,10 +133,23 @@ const FileUpload: FC<FileUploadProps> = ({ name, label, defaultValue, options })
name={name}
uploadDescription={`Failed to retrieve card. Please try again. ${description}`}
handleFileUpload={handleFileUpload}
fileUploadType={fileUploadType}
/>
);
}
if (previewUrl !== 'Not Found' && previewUrl !== null && !loading) {
if (fileUploadType) {
return (
<NonImageCardComponent
name={name}
fileName={fileName}
fileUrl={fileUrl}
setFileUrl={setFileUrl}
setPreviewUrl={setPreviewUrl}
onClear={onClear}
/>
);
}
// Show the image if the fetch succeeds
return (
<CardComponent
Expand Down Expand Up @@ -144,7 +182,14 @@ const FileUpload: FC<FileUploadProps> = ({ name, label, defaultValue, options })
}

// Otherwise prompt to upload a file
return <UploadComponent name={name} uploadDescription={description} handleFileUpload={handleFileUpload} />;
return (
<UploadComponent
name={name}
uploadDescription={description}
handleFileUpload={handleFileUpload}
fileUploadType={fileUploadType}
/>
);
};

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
import { Box, IconButton, Link } from '@mui/material';
import { FC, useContext } from 'react';
import { useFormContext } from 'react-hook-form';
import { IntakeThemeContext } from '../../contexts';

interface NonImageCardComponentProps {
name: string;
fileName?: string;
fileUrl?: string;
setPreviewUrl: (previewUrl: string | null) => void;
setFileUrl: (fileUrl: string | undefined) => void;
onClear: () => void;
}

const NonImageCardComponent: FC<NonImageCardComponentProps> = ({
name,
fileName,
fileUrl,
setPreviewUrl,
setFileUrl,
onClear,
}): JSX.Element => {
const { setValue } = useFormContext();
const { otherColors } = useContext(IntakeThemeContext);

return (
<Box
sx={{
px: 2,
py: 0.25,
backgroundColor: otherColors.toolTipGrey,
borderRadius: 2,
alignItems: 'center',
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
}}
>
<Link href={fileUrl} target="_blank">
{fileName}
</Link>
<IconButton
aria-label="remove-attachment"
onClick={() => {
setValue(name, '');
setPreviewUrl(null);
setFileUrl(undefined);
onClear();
}}
sx={{
color: otherColors.clearImage,
px: 0,
'&:hover': { backgroundColor: 'transparent' },
}}
>
<DeleteForeverIcon />
</IconButton>
</Box>
);
};

export default NonImageCardComponent;
132 changes: 80 additions & 52 deletions packages/ottehr-components/lib/components/form/UploadComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,88 @@ interface UploadComponentProps {
name: string;
uploadDescription: string;
handleFileUpload: (event: ChangeEvent<HTMLInputElement>) => void;
fileUploadType?: string;
}

const UploadComponent: FC<UploadComponentProps> = ({ name, uploadDescription, handleFileUpload }): JSX.Element => {
const UploadComponent: FC<UploadComponentProps> = ({
name,
uploadDescription,
handleFileUpload,
fileUploadType,
}): JSX.Element => {
const theme = useTheme();
const { control } = useFormContext();
const { otherColors } = useContext(IntakeThemeContext);
const inputRef = React.useRef<HTMLInputElement | null>(null);

// HTMLDivElement is here because I used a fragment to wrap uploadAndController.
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement | HTMLButtonElement>): void => {
try {
if (['Enter', 'Space'].includes(event.code)) {
inputRef.current?.click();
}
} catch (error) {
console.error(error);
}
};
const handleOnClick = (): void => {
try {
if (inputRef?.current) {
inputRef?.current?.click();
} else {
throw new Error('inputRef is not defined');
}
} catch (error) {
console.error(error);
}
};

const uploadAndController = (
<>
Upload
<Controller
name={name}
control={control}
render={({ field: { value, onChange, ...field } }) => {
return (
<input
{...field}
ref={inputRef}
value={value?.filename}
type="file"
accept={fileUploadType ?? 'image/png, image/jpeg, image/jpg'}
hidden
disabled={false}
onChange={(e) => onChange(handleFileUpload(e))}
/>
);
}}
/>
</>
);
const buttonAndOrControllerComponent = fileUploadType ? (
uploadAndController
) : (
<Button
id={name}
// component="label"
aria-labelledby={`${name}-label`}
aria-describedby={`${name}-description`}
variant="contained"
sx={{ textTransform: 'none', mt: fileUploadType ? -1 : 2 }}
onKeyDown={handleKeyDown}
onClick={handleOnClick}
>
{uploadAndController}
</Button>
);

return (
<Box
onKeyDown={fileUploadType ? handleKeyDown : undefined}
onClick={fileUploadType ? handleOnClick : undefined}
sx={{
height: 260,
height: fileUploadType ? 40 : 260,
border: `1px dashed ${theme.palette.primary.main}`,
borderRadius: 2,
display: 'flex',
Expand All @@ -29,64 +99,22 @@ const UploadComponent: FC<UploadComponentProps> = ({ name, uploadDescription, ha
alignItems: 'center',
textAlign: 'center',
justifyContent: 'center',
gap: 1,
gap: fileUploadType ? undefined : 1,
px: 4,
mb: 2,
mb: fileUploadType ? undefined : 2,
':hover': fileUploadType
? {
cursor: 'pointer',
}
: undefined,
}}
>
<Container style={{ width: '60%', margin: 0, padding: 0 }}>
<Typography id={`${name}-description`}>
<Markdown components={{ p: DescriptionRenderer }}>{uploadDescription}</Markdown>
</Typography>
</Container>
<Button
id={name}
// component="label"
aria-labelledby={`${name}-label`}
aria-describedby={`${name}-description`}
variant="contained"
sx={{ textTransform: 'none', mt: 2 }}
onKeyDown={(event) => {
try {
if (['Enter', 'Space'].includes(event.code)) {
inputRef.current?.click();
}
} catch (error) {
console.error(error);
}
}}
onClick={() => {
try {
if (inputRef?.current) {
inputRef?.current?.click();
} else {
throw new Error('inputRef is not defined');
}
} catch (error) {
console.error(error);
}
}}
>
Upload
<Controller
name={name}
control={control}
render={({ field: { value, onChange, ...field } }) => {
return (
<input
{...field}
ref={inputRef}
value={value?.filename}
type="file"
accept="image/png, image/jpeg, image/jpg"
hidden
disabled={false}
onChange={(e) => onChange(handleFileUpload(e))}
/>
);
}}
/>
</Button>
{buttonAndOrControllerComponent}
</Box>
);
};
Expand Down
24 changes: 13 additions & 11 deletions packages/ottehr-components/lib/components/form/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
export { default as CardComponent } from './CardComponent';
export { default as ControlButtons } from './ControlButtons';
export { default as ControlledCheckBox } from './ControlledCheckBox';
export { default as DateInput } from './DateInput';
export { default as FileUpload } from './FileUpload';
export { default as FormInput } from './FormInput';
export { default as FreeMultiSelectInput } from './FreeMultiSelectInput';
export { default as NonImageCardComponent } from './NonImageCardComponent';
export { default as InputMask } from './InputMask';
export { default as ControlButtons } from './ControlButtons';
export { default as SelectInput } from './SelectInput';
export { default as RenderLabelFromSelect } from './RenderLabelFromSelect';
export { default as RadioInput } from './RadioInput';
export { default as FreeMultiSelectInput } from './FreeMultiSelectInput';
export { default as DateInput } from './DateInput';
export { default as RadioListInput } from './RadioListInput';
export { default as ControlledCheckBox } from './ControlledCheckBox';
export { default as FileUpload } from './FileUpload';
export { default as RenderLabelFromSelect } from './RenderLabelFromSelect';
export { default as SelectInput } from './SelectInput';
export { default as UploadComponent } from './UploadComponent';
export { default as CardComponent } from './CardComponent';
export { default as YearInput } from './YearInput';
export * from './BoldPurpleInputLabel';
export * from './InputHelperText';
export * from './LinkRenderer';
export * from './DescriptionRenderer';
export * from './LightToolTip';
export * from './FormList';
export * from './InputHelperText';
export * from './LightToolTip';
export * from './LinkRenderer';
export * from './VisuallyHiddenInput';
export * from './VirtualizedListboxComponent';
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ export const getFormInputField = (
label={formInput.label || 'No label'}
defaultValue={formInput.defaultValue as string}
options={formInput.fileOptions}
fileUploadType={formInput.fileUploadType}
/>
);
case 'Photos':
Expand Down
Loading

0 comments on commit 5886bf0

Please sign in to comment.