Skip to content

Commit

Permalink
Useful tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
denniskigen committed Oct 29, 2024
1 parent 31efe70 commit d948b4e
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@ import React, { useState, useCallback, useMemo, useEffect, useRef, useContext }
import { useTranslation } from 'react-i18next';
import { Tabs, Tab, TabList, TabPanels, TabPanel, ModalHeader, ModalBody, InlineNotification } from '@carbon/react';
import { type FetchResponse, type UploadedFile } from '@openmrs/esm-framework';
import { useAllowedFileExtensions } from '@openmrs/esm-patient-common-lib';
import CameraComponent from './camera.component';
import CameraMediaUploaderContext from './camera-media-uploader-context.resources';
import FileReviewContainer from './file-review.component';
import MediaUploaderComponent from './media-uploader.component';
import UploadStatusComponent from './upload-status.component';
import styles from './camera-media-uploader.scss';
import { useAllowedFileExtensions } from '@openmrs/esm-patient-common-lib';

interface CameraMediaUploaderModalProps {
allowedExtensions: Array<string> | null;
cameraOnly?: boolean;
closeModal: () => void;
collectDescription?: boolean;
Expand All @@ -26,7 +25,6 @@ interface CameraMediaUploadTabsProps {
}

const CameraMediaUploaderModal: React.FC<CameraMediaUploaderModalProps> = ({
allowedExtensions,
cameraOnly,
closeModal,
collectDescription,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import React, { type SyntheticEvent, useCallback, useState, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { Controller, useForm, type SubmitHandler } from 'react-hook-form';
import { Button, Form, ModalBody, ModalFooter, ModalHeader, Stack, TextArea, TextInput } from '@carbon/react';
import { DocumentPdf, DocumentUnknown } from '@carbon/react/icons';
import { type UploadedFile, UserHasAccess } from '@openmrs/esm-framework';
Expand All @@ -17,6 +20,7 @@ interface FilePreviewProps {
moveToNextFile: () => void;
onSaveFile: (dataUri: UploadedFile) => void;
title?: string;
// TODO: Constrain the file type to a more specific type that only allows image and pdf
uploadedFile: UploadedFile;
}

Expand Down Expand Up @@ -71,28 +75,48 @@ const FilePreview: React.FC<FilePreviewProps> = ({
}) => {
const { t } = useTranslation();
const { allowedExtensions } = useContext(CameraMediaUploaderContext);
const fileExtension = uploadedFile.fileName.match(/\.[^\\/.]+$/)?.[0] || '';
const [fileName, setFileName] = useState(uploadedFile.fileName.replace(/\.[^\\/.]+$/, ''));
const [fileDescription, setFileDescription] = useState(uploadedFile.fileDescription);
const [emptyName, setEmptyName] = useState(false);
const fileNameWithoutExtension = uploadedFile.fileName.trim().replace(/\.[^\\/.]+$/, '');

const schema = z.object({
fileName: z.string({
required_error: t('nameIsRequired', 'Name is required'),
}),
fileDescription: z.string().optional(),
});

const {
control,
handleSubmit,
formState: { errors },
} = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
defaultValues: {
fileName: fileNameWithoutExtension,
},
});

const saveImageOrPdf = useCallback(
(event: SyntheticEvent) => {
event.preventDefault();
const onSubmit: SubmitHandler<z.infer<typeof schema>> = (data) => {
const { fileName, fileDescription } = data;

const sanitizedFileName = allowedExtensions?.reduce((name, ext) => {
const regex = new RegExp(`\\.(${ext})+$`, 'i');
const sanitizedFileName =
allowedExtensions?.reduce((name, extension) => {
const regex = new RegExp(`\\.(${extension})+$`, 'i');
return name.replace(regex, '');
}, fileName) || fileName;

onSaveFile?.({
...uploadedFile,
fileName: `${sanitizedFileName}${fileExtension}`,
fileDescription,
});
},
[onSaveFile, fileName, fileExtension, allowedExtensions, fileDescription, uploadedFile],
);
onSaveFile?.({
...uploadedFile,
fileName: `${sanitizedFileName}${fileExtension}`,
fileDescription,
});
};

const getFileExtension = useCallback((filename: string): string => {
const validExtension = filename.match(/\.[0-9a-z]+$/i);
return validExtension ? validExtension[0].toLowerCase() : '';
}, []);

const fileExtension = getFileExtension(uploadedFile.fileName);

const cancelCapture = useCallback(
(event: SyntheticEvent) => {
Expand All @@ -102,34 +126,11 @@ const FilePreview: React.FC<FilePreviewProps> = ({
[clearData],
);

const updateFileName = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
event.preventDefault();

if (event.target.value === '') {
setEmptyName(true);
} else if (emptyName) {
setEmptyName(false);
}

setFileName(event.target.value);
},
[setEmptyName, setFileName, emptyName],
);

const updateDescription = useCallback(
(event: React.ChangeEvent<HTMLTextAreaElement>) => {
event.preventDefault();
setFileDescription(event.target.value);
},
[setFileDescription],
);

return (
<Form onSubmit={saveImageOrPdf}>
<Form onSubmit={handleSubmit(onSubmit)}>
<ModalBody className={styles.overview}>
{uploadedFile.fileType === 'image' ? (
<img src={uploadedFile.base64Content} alt="placeholder" />
<img src={uploadedFile.base64Content} alt={t('imagePlaceholder', 'Image placeholder')} />
) : uploadedFile.fileType === 'pdf' ? (
<div className={styles.filePlaceholder}>
<DocumentPdf size={16} />
Expand All @@ -142,30 +143,39 @@ const FilePreview: React.FC<FilePreviewProps> = ({
<div className={styles.imageDetails}>
<Stack gap={5}>
<div className={styles.captionFrame}>
<TextInput
autoComplete="off"
autoFocus
id="caption"
invalid={emptyName}
invalidText={emptyName && t('fieldRequired', 'This field is required')}
labelText={`${uploadedFile.fileType === 'image' ? t('image', 'Image') : t('file', 'File')} ${t(
'name',
'name',
)}`}
onChange={updateFileName}
placeholder={t('attachmentCaptionInstruction', 'Enter caption')}
required
value={fileName}
<Controller
control={control}
name="fileName"
render={({ field: { onChange, value } }) => (
<TextInput
autoFocus
id="caption"
invalid={!!errors.fileName}
invalidText={errors.fileName?.message}
labelText={`${uploadedFile.fileType === 'image' ? t('image', 'Image') : t('file', 'File')} ${t(
'name',
'name',
)}`}
onChange={onChange}
placeholder={t('enterAttachmentName', 'Enter attachment name')}
value={value}
/>
)}
/>
</div>
{collectDescription && (
<TextArea
autoComplete="off"
id="description"
labelText={t('imageDescription', 'Image description')}
onChange={updateDescription}
placeholder={t('attachmentCaptionInstruction', 'Enter caption')}
value={fileDescription}
<Controller
control={control}
name="fileDescription"
render={({ field: { onChange, value } }) => (
<TextArea
id="description"
labelText={t('imageDescription', 'Image description')}
onChange={onChange}
placeholder={t('enterAttachmentDescription', 'Enter attachment description')}
value={value}
/>
)}
/>
)}
</Stack>
Expand All @@ -176,7 +186,7 @@ const FilePreview: React.FC<FilePreviewProps> = ({
<Button kind="secondary" size="lg" onClick={cancelCapture}>
{t('cancel', 'Cancel')}
</Button>
<Button type="submit" size="lg" onClick={saveImageOrPdf} disabled={emptyName}>
<Button type="submit" size="lg">
{title || t('addAttachment', 'Add attachment')}
</Button>
</UserHasAccess>
Expand All @@ -185,4 +195,4 @@ const FilePreview: React.FC<FilePreviewProps> = ({
);
};

export default FileReviewContainer;
export default FileReviewContainer;
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,12 @@ const MediaUploaderComponent = () => {
<p className="cds--label-description">
{t('fileUploadSizeConstraints', 'Size limit is {{fileSize}}MB', {
fileSize: maxFileSize,
})}.
</p>
<p className='cds--label-description'>
{t('supportedFiletypes', 'Supported files are {{supportedFiles}}',{
})}
.{' '}
{t('supportedFiletypes', 'Supported files are {{supportedFiles}}', {
supportedFiles: allowedExtensions?.join(', '),
})}.
})}
.
</p>
<div className={styles.uploadFile}>
<FileUploaderDropContainer
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect, useContext } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, ButtonSet, FileUploaderItem, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
import { showSnackbar } from '@openmrs/esm-framework';
Expand Down Expand Up @@ -27,7 +27,7 @@ const UploadStatusComponent: React.FC<UploadStatusComponentProps> = ({ title })

useEffect(() => {
Promise.all(
filesToUpload.map((file, indx) =>
filesToUpload.map((file, index) =>
saveFile(file)
.then(() => {
showSnackbar({
Expand All @@ -37,8 +37,8 @@ const UploadStatusComponent: React.FC<UploadStatusComponentProps> = ({ title })
isLowContrast: true,
});
setFilesUploading((prevfilesToUpload) =>
prevfilesToUpload.map((file, ind) =>
ind === indx
prevfilesToUpload.map((file, prevFileIndex) =>
prevFileIndex === index
? {
...file,
status: 'complete',
Expand All @@ -47,16 +47,15 @@ const UploadStatusComponent: React.FC<UploadStatusComponentProps> = ({ title })
),
);
})
.catch((err) => {
.catch((error) => {
showSnackbar({
kind: 'error',
subtitle: err,
subtitle: error?.message,
title: `${t('uploading', 'Uploading')} ${file.fileName} ${t('failed', 'failed')}`,
});
}),
),
).then(() => {
true;
onCompletion?.();
});
}, [onCompletion, saveFile, filesToUpload, t, setFilesUploading]);
Expand Down
4 changes: 3 additions & 1 deletion packages/esm-patient-attachments-app/src/routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
"name": "capture-photo-widget",
"component": "capturePhotoWidget",
"slot": "capture-patient-photo-slot"
},
}
],
"modals": [
{
"name": "capture-photo-modal",
"component": "capturePhotoModal"
Expand Down
7 changes: 5 additions & 2 deletions packages/esm-patient-attachments-app/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"addAttachment": "Add Attachment",
"addAttachment_title": "Add Attachment",
"addMoreAttachments": "Add more attachments",
"attachmentCaptionInstruction": "Enter caption",
"attachments": "Attachments",
"Attachments": "Attachments",
"attachmentsInLowerCase": "attachments",
Expand All @@ -21,10 +20,11 @@
"deleteImage": "Delete image",
"deletePdf": "Delete PDF",
"edit": "Edit",
"enterAttachmentDescription": "Enter attachment description",
"enterAttachmentName": "Enter attachment name",
"error": "Error",
"failed": "failed",
"failedDeleting": "couldn't be deleted",
"fieldRequired": "This field is required",
"file": "File",
"fileDeleted": "File deleted",
"fileName": "File name",
Expand All @@ -35,11 +35,14 @@
"gridView": "Grid view",
"image": "Image",
"imageDescription": "Image description",
"imagePlaceholder": "Image placeholder",
"imagePreview": "Image preview",
"name": "name",
"nameIsRequired": "Name is required",
"noImageToDisplay": "No image to display",
"options": "Options",
"successfullyDeleted": "successfully deleted",
"supportedFiletypes": "Supported files are {{supportedFiles}}",
"tableView": "Table view",
"type": "Type",
"unsupportedFileType": "Unsupported file type",
Expand Down

0 comments on commit d948b4e

Please sign in to comment.