Skip to content

Commit

Permalink
Show attachment description as well as fix up some more things
Browse files Browse the repository at this point in the history
  • Loading branch information
denniskigen committed Feb 2, 2024
1 parent 9e3fb4c commit 7d6fed6
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 169 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import classNames from 'classnames';
import { SkeletonPlaceholder } from '@carbon/react';
import { type Attachment } from '@openmrs/esm-framework';
import AttachmentThumbnail from './attachment-thumbnail.component';
Expand Down Expand Up @@ -33,7 +32,7 @@ const AttachmentsGridOverview: React.FC<AttachmentsGridOverviewProps> = ({
{attachments.map((attachment, indx) => {
const imageProps = {
src: attachment.src,
title: attachment.title,
title: attachment.filename,
style: {},
onClick: () => {
openAttachment(attachment);
Expand All @@ -48,7 +47,7 @@ const AttachmentsGridOverview: React.FC<AttachmentsGridOverviewProps> = ({
return (
<div key={indx}>
<AttachmentThumbnail imageProps={imageProps} item={item} />
<p className={styles.title}>{attachment.title}</p>
<p className={styles.title}>{attachment.filename}</p>
<p className={styles.muted}>{attachment.dateTime}</p>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
@import '../root.scss';

.galleryContainer {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
align-items: center;
display: grid;
grid-template-columns:
repeat(3, 1fr);
grid-gap: spacing.$spacing-05;
padding: 0 spacing.$spacing-05 spacing.$spacing-05 spacing.$spacing-05;
padding: spacing.$spacing-05;
}

.attachmentThumbnailSkeleton {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, ContentSwitcher, Loading, Switch } from '@carbon/react';
import { List, Thumbnail_2, Add } from '@carbon/react/icons';
import { Button, ContentSwitcher, DataTableSkeleton, Loading, Switch } from '@carbon/react';
import { Add, List, Thumbnail_2 } from '@carbon/react/icons';
import {
type UploadedFile,
type Attachment,
createAttachment,
deleteAttachmentPermanently,
showModal,
showSnackbar,
useAttachments,
useLayoutType,
UserHasAccess,
createAttachment,
deleteAttachmentPermanently,
useAttachments,
type Attachment,
type UploadedFile,
} from '@openmrs/esm-framework';
import { CardHeader, EmptyState } from '@openmrs/esm-patient-common-lib';
import { createGalleryEntry } from '../utils';
import { useAllowedExtensions } from './use-allowed-extensions';
import AttachmentsGridOverview from './attachments-grid-overview.component';
import AttachmentsTableOverview from './attachments-table-overview.component';
import AttachmentPreview from './image-preview.component';
import styles from './attachments-overview.scss';
import { useAllowedExtensions } from './use-allowed-extensions';

const AttachmentsOverview: React.FC<{ patientUuid: string }> = ({ patientUuid }) => {
const { t } = useTranslation();
Expand All @@ -45,7 +45,7 @@ const AttachmentsOverview: React.FC<{ patientUuid: string }> = ({ patientUuid })
}
}, [error, t]);

const showCam = useCallback(() => {
const showAttachmentModal = useCallback(() => {
const close = showModal('capture-photo-modal', {
saveFile: (file: UploadedFile) => createAttachment(patientUuid, file),
allowedExtensions: allowedExtensions,
Expand All @@ -67,15 +67,15 @@ const AttachmentsOverview: React.FC<{ patientUuid: string }> = ({ patientUuid })

showSnackbar({
title: t('fileDeleted', 'File deleted'),
subtitle: `${attachment.title} ${t('successfullyDeleted', 'successfully deleted')}`,
subtitle: `${attachment.filename} ${t('successfullyDeleted', 'successfully deleted')}`,
kind: 'success',
isLowContrast: true,
});
})
.catch((error) => {
.catch(() => {
showSnackbar({
title: t('error', 'Error'),
subtitle: `${attachment.title} ${t('failedDeleting', "couldn't be deleted")}`,
subtitle: `${attachment.filename} ${t('failedDeleting', "couldn't be deleted")}`,
kind: 'error',
});
});
Expand Down Expand Up @@ -104,26 +104,30 @@ const AttachmentsOverview: React.FC<{ patientUuid: string }> = ({ patientUuid })
} else {
const anchor = document.createElement('a');
anchor.setAttribute('href', attachment.src);
anchor.setAttribute('download', attachment.title);
anchor.setAttribute('download', attachment.filename);
anchor.click();
}
},
[setAttachmentToPreview],
);

if (isLoading) {
return <DataTableSkeleton role="progressbar" />;
}

if (!attachments.length) {
return (
<EmptyState
displayText={t('attachmentsInLowerCase', 'attachments')}
headerTitle={t('attachmentsInProperFormat', 'Attachments')}
launchForm={showCam}
launchForm={showAttachmentModal}
/>
);
}

return (
<UserHasAccess privilege="View Attachments">
<div onDragOverCapture={showCam} className={styles.overview}>
<div onDragOverCapture={showAttachmentModal} className={styles.overview}>
<div id="container">
<CardHeader title={t('attachments', 'Attachments')}>
<div className={styles.validatingDataIcon}>{isValidating && <Loading withOverlay={false} small />}</div>
Expand All @@ -137,7 +141,7 @@ const AttachmentsOverview: React.FC<{ patientUuid: string }> = ({ patientUuid })
</Switch>
</ContentSwitcher>
<div className={styles.divider} />
<Button kind="ghost" renderIcon={Add} iconDescription="Add attachment" onClick={showCam}>
<Button kind="ghost" renderIcon={Add} iconDescription="Add attachment" onClick={showAttachmentModal}>
{t('add', 'Add')}
</Button>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import AttachmentsOverview from './attachments-overview.component';
import { useAttachments } from '@openmrs/esm-framework';

const mockedUseAttachments = jest.mocked(useAttachments);

it('renders a loading skeleton when attachments are loading', () => {
mockedUseAttachments.mockReturnValue({
data: [],
error: null,
isLoading: true,
isValidating: false,
mutate: jest.fn(),
});

renderAttachmentsOverview();

expect(screen.getByRole('progressbar')).toBeInTheDocument();
expect(screen.queryByRole('table')).not.toBeInTheDocument();
});

it('renders an empty state if attachments are not available', () => {
mockedUseAttachments.mockReturnValue({
data: [],
error: null,
isLoading: false,
isValidating: false,
mutate: jest.fn(),
});

renderAttachmentsOverview();

expect(screen.getByText(/There are no attachments to display for this patient/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /record attachments/i })).toBeInTheDocument();
});

function renderAttachmentsOverview() {
render(<AttachmentsOverview patientUuid="test-uuid" />);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import { useTranslation } from 'react-i18next';
import styles from './attachments-table-overview.scss';

interface AttachmentsTableOverviewProps {
isLoading: boolean;
attachments: Array<Attachment>;
isLoading: boolean;
deleteAttachment: (attachment: Attachment) => void;
openAttachment: (attachment: Attachment) => void;
}
Expand All @@ -40,7 +40,7 @@ const AttachmentsTableOverview: React.FC<AttachmentsTableOverviewProps> = ({
id: attachment.id,
fileName: (
<span role="button" tabIndex={0} className={styles.link} onClick={() => openAttachment(attachment)}>
{attachment.title}
{attachment.filename}
</span>
),
type: attachment.bytesContentFamily,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import { type Attachment, useLayoutType } from '@openmrs/esm-framework';
import styles from './image-preview.scss';

interface AttachmentPreviewProps {
closePreview: any;
attachmentToPreview: Attachment;
closePreview: () => void;
deleteAttachment: (attachment: Attachment) => void;
}

const AttachmentPreview: React.FC<AttachmentPreviewProps> = ({
closePreview,
attachmentToPreview,
closePreview,
deleteAttachment,
}) => {
const { t } = useTranslation();
Expand Down Expand Up @@ -49,7 +49,7 @@ const AttachmentPreview: React.FC<AttachmentPreviewProps> = ({
/>
<div className={styles.attachmentImage}>
{attachmentToPreview.bytesContentFamily === 'IMAGE' ? (
<img src={attachmentToPreview.src} alt={attachmentToPreview.title} />
<img src={attachmentToPreview.src} alt={attachmentToPreview.filename} />
) : attachmentToPreview.bytesContentFamily === 'PDF' ? (
<iframe title="PDFViewer" className={styles.pdfViewer} src={attachmentToPreview.src} />
) : null}
Expand All @@ -67,8 +67,9 @@ const AttachmentPreview: React.FC<AttachmentPreviewProps> = ({
</div>
</div>
<div className={styles.rightPanel}>
{attachmentToPreview?.title ? (
<p className={classNames(styles.bodyLong01, styles.imageDescription)}>{attachmentToPreview.title}</p>
<h4 className={styles.title}>{attachmentToPreview.filename}</h4>
{attachmentToPreview?.description ? (
<p className={classNames(styles.bodyLong01, styles.imageDescription)}>{attachmentToPreview.description}</p>
) : null}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,10 @@
.menuItem {
max-width: none;
}

.title {
@extend .productiveHeading02;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import React, { type SyntheticEvent, useCallback, useEffect, useState, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, TextArea, TextInput, ModalHeader, ModalBody, ModalFooter } from '@carbon/react';
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';
import styles from './file-review.scss';
import CameraMediaUploaderContext from './camera-media-uploader-context.resources';
import { DocumentPdf, DocumentUnknown } from '@carbon/react/icons';
import styles from './file-review.scss';

export interface FileReviewContainerProps {
onCompletion: () => void;
}

interface FilePreviewProps {
uploadedFile: UploadedFile;
collectDescription?: boolean;
onSaveFile: (dataUri: UploadedFile) => void;
clearData?(): void;
moveToNextFile: () => void;
}

const FileReviewContainer: React.FC<FileReviewContainerProps> = ({ onCompletion }) => {
const { filesToUpload, clearData, setFilesToUpload, closeModal, collectDescription } =
useContext(CameraMediaUploaderContext);
const { t } = useTranslation();
const [currentFile, setCurrentFile] = useState(1);
const { filesToUpload, clearData, setFilesToUpload, closeModal, collectDescription } =
useContext(CameraMediaUploaderContext);

const moveToNextFile = useCallback(() => {
if (currentFile < filesToUpload.length) {
Expand All @@ -26,9 +34,7 @@ const FileReviewContainer: React.FC<FileReviewContainerProps> = ({ onCompletion

const handleSave = useCallback(
(updatedFile: UploadedFile) => {
setFilesToUpload((filesToUpload) =>
filesToUpload.map((file, indx) => (indx === currentFile - 1 ? updatedFile : file)),
);
setFilesToUpload((filesToUpload) => filesToUpload.map((file, i) => (i === currentFile - 1 ? updatedFile : file)));
moveToNextFile();
},
[moveToNextFile, setFilesToUpload, currentFile],
Expand All @@ -51,18 +57,10 @@ const FileReviewContainer: React.FC<FileReviewContainerProps> = ({ onCompletion
);
};

interface FilePreviewProps {
uploadedFile: UploadedFile;
collectDescription?: boolean;
onSaveFile: (dataUri: UploadedFile) => void;
clearData?(): void;
moveToNextFile: () => void;
}

const FilePreview: React.FC<FilePreviewProps> = ({ uploadedFile, collectDescription, onSaveFile, clearData }) => {
const { t } = useTranslation();
const [fileName, setFileName] = useState('');
const [fileDescription, setFileDescription] = useState('');
const { t } = useTranslation();
const [emptyName, setEmptyName] = useState(false);

useEffect(() => {
Expand Down Expand Up @@ -112,7 +110,7 @@ const FilePreview: React.FC<FilePreviewProps> = ({ uploadedFile, collectDescript
);

return (
<form onSubmit={saveImageOrPdf}>
<Form onSubmit={saveImageOrPdf}>
<ModalBody className={styles.overview}>
{uploadedFile.fileType === 'image' ? (
<img src={uploadedFile.base64Content} alt="placeholder" />
Expand All @@ -126,33 +124,35 @@ const FilePreview: React.FC<FilePreviewProps> = ({ uploadedFile, collectDescript
</div>
)}
<div className={styles.imageDetails}>
<div className={styles.captionFrame}>
<TextInput
id="caption"
labelText={`${uploadedFile.fileType === 'image' ? t('image', 'Image') : t('file', 'File')} ${t(
'name',
'name',
)}`}
autoComplete="off"
placeholder={t('attachmentCaptionInstruction', 'Enter caption')}
onChange={updateFileName}
required
value={fileName}
invalid={emptyName}
autoFocus
invalidText={emptyName && t('fieldRequired', 'This field is required')}
/>
</div>
{collectDescription && (
<TextArea
id="description"
labelText={t('imageDescription', 'Image description')}
autoComplete="off"
placeholder={t('attachmentCaptionInstruction', 'Enter caption')}
value={fileDescription}
onChange={updateDescription}
/>
)}
<Stack gap={5}>
<div className={styles.captionFrame}>
<TextInput
id="caption"
labelText={`${uploadedFile.fileType === 'image' ? t('image', 'Image') : t('file', 'File')} ${t(
'name',
'name',
)}`}
autoComplete="off"
placeholder={t('attachmentCaptionInstruction', 'Enter caption')}
onChange={updateFileName}
required
value={fileName}
invalid={emptyName}
autoFocus
invalidText={emptyName && t('fieldRequired', 'This field is required')}
/>
</div>
{collectDescription && (
<TextArea
id="description"
labelText={t('imageDescription', 'Image description')}
autoComplete="off"
placeholder={t('attachmentCaptionInstruction', 'Enter caption')}
value={fileDescription}
onChange={updateDescription}
/>
)}
</Stack>
</div>
</ModalBody>
<ModalFooter>
Expand All @@ -165,7 +165,7 @@ const FilePreview: React.FC<FilePreviewProps> = ({ uploadedFile, collectDescript
</Button>
</UserHasAccess>
</ModalFooter>
</form>
</Form>
);
};

Expand Down
Loading

0 comments on commit 7d6fed6

Please sign in to comment.