Skip to content

Commit

Permalink
Merge branch '354-generic-resource-list' of github.com:beda-software/…
Browse files Browse the repository at this point in the history
…fhir-emr into update-app-layout
  • Loading branch information
vesnushka committed Dec 10, 2024
2 parents 27d933b + 5de9dab commit 0b4a65e
Show file tree
Hide file tree
Showing 32 changed files with 1,774 additions and 149 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ auth:
refresh_token: true
secret_required: false
access_token_expiration: 36000
type: smart-on-fhir
type: smart-on-fhir-practitioner
smart:
launch_uri: https://www.smartforms.io/launch
name: SmartForms
Expand Down
15 changes: 15 additions & 0 deletions resources/seeds/Client/a57d90e3-5f69-4b92-aa2e-2992180863c2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
auth:
authorization_code:
redirect_uri: https://portal-xrp.digitalhealth.gov.au/FormsPortal/authorisation
refresh_token: true
secret_required: false
access_token_expiration: 36000
type: smart-on-fhir-practitioner
smart:
name: ADHA CHAP Form
launch_uri: https://portal-xrp.digitalhealth.gov.au/FormsPortal/launch
description: ADHA CHAP Form
grant_types:
- authorization_code
id: a57d90e3-5f69-4b92-aa2e-2992180863c2
resourceType: Client
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ auth:
refresh_token: true
secret_required: false
access_token_expiration: 36000
type: smart-on-fhir
type: smart-on-fhir-practitioner
smart:
name: Clinical guidelines
launch_uri: https://beda.caresofa.com/
Expand Down
19 changes: 19 additions & 0 deletions src/components/AudioRecorder/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// eslint-disable-next-line
import { useAudioRecorder as useAudioRecorderControl } from "react-audio-voice-recorder";

export interface RecorderControls {
startRecording: () => void;
stopRecording: () => void;
togglePauseResume: () => void;
recordingBlob?: Blob;
isRecording: boolean;
isPaused: boolean;
recordingTime: number;
mediaRecorder?: MediaRecorder;
}

export function useAudioRecorder() {
const recorderControls: RecorderControls = useAudioRecorderControl();

return { recorderControls };
}
73 changes: 73 additions & 0 deletions src/components/AudioRecorder/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Trans } from '@lingui/macro';
// eslint-disable-next-line
import { AudioRecorder as AudioRecorderControl } from 'react-audio-voice-recorder';

import { RecorderControls } from './hooks';
import { S } from './styles';
import { uuid4 } from '@beda.software/fhir-react';
import { Upload, type UploadFile } from 'antd';
import React from 'react';
import { RcFile } from 'antd/lib/upload/interface';

interface AudioRecorderProps {
onChange: (url: RcFile) => Promise<void>;
recorderControls: RecorderControls;
}

export function AudioRecorder(props: AudioRecorderProps) {
const { recorderControls, onChange } = props;

const onRecordingComplete = async (blob: Blob) => {
const uuid = uuid4();
const audioFile = new File([blob], `${uuid}.webm`, { type: blob.type }) as RcFile;
audioFile.uid = uuid;
onChange(audioFile);
};

return (
<S.Scriber>
<S.Title $danger>
<Trans>Capture in progress</Trans>
</S.Title>
<AudioRecorderControl
showVisualizer
onRecordingComplete={onRecordingComplete}
audioTrackConstraints={{
noiseSuppression: true,
echoCancellation: true,
}}
recorderControls={{
...recorderControls,
}}
/>
</S.Scriber>
);
}

interface AudioPlayerProps {
files: UploadFile[];
onRemove?: (file: UploadFile) => void;
}

export function AudioPlayer(props: AudioPlayerProps) {
const { files, onRemove } = props;

return (
<S.Scriber>
<S.Title>
<Trans>Listen to the audio</Trans>
</S.Title>
{files.map((file) => (
<React.Fragment key={file.uid}>
<S.Audio controls src={file.url} />
<Upload
listType="text"
showUploadList={{ showRemoveIcon: !!onRemove }}
fileList={[file]}
onRemove={() => onRemove?.(file)}
/>
</React.Fragment>
))}
</S.Scriber>
);
}
50 changes: 50 additions & 0 deletions src/components/AudioRecorder/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import styled, { css } from 'styled-components';

import { Text } from 'src/components/Typography';

export const S = {
Scriber: styled.div`
display: flex;
flex-direction: column;
gap: 8px 0;
.audio-recorder {
width: 100%;
box-shadow: none;
border-radius: 30px;
background-color: ${({ theme }) => theme.neutralPalette.gray_2};
padding: 3px 6px 3px 18px;
}
.audio-recorder-timer,
.audio-recorder-status {
font-family: inherit;
color: ${({ theme }) => theme.neutralPalette.gray_12};
}
.audio-recorder-mic {
display: none;
}
.audio-recorder-timer {
margin-left: 0;
}
.audio-recorder-options {
filter: ${({ theme }) => (theme.mode === 'dark' ? `invert(100%)` : `invert(0%)`)};
}
`,
Title: styled(Text)<{ $danger?: boolean }>`
font-weight: 700;
${({ $danger }) =>
$danger &&
css`
color: ${({ theme }) => theme.antdTheme?.red5};
`}
`,
Audio: styled.audio`
height: 52px;
width: 100%;
`,
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import { QuestionReference } from './readonly-widgets/reference';
import { AnxietyScore, DepressionScore } from './readonly-widgets/score';
import { QuestionText, TextWithInput } from './readonly-widgets/string';
import { TimeRangePickerControl } from './readonly-widgets/TimeRangePickerControl';
import { UploadFileControlReadOnly } from './widgets/UploadFileControl';
import { UploadFile } from './readonly-widgets/UploadFile';
import { AudioAttachment } from './readonly-widgets/AudioAttachment';

interface Props extends Partial<QRFContextData> {
formData: QuestionnaireResponseFormData;
Expand Down Expand Up @@ -66,14 +67,15 @@ export function ReadonlyQuestionnaireResponseForm(props: Props) {
reference: QuestionReference,
display: Display,
boolean: QuestionBoolean,
attachment: UploadFileControlReadOnly,
attachment: UploadFile,
...questionItemComponents,
}}
itemControlQuestionItemComponents={{
'inline-choice': QuestionChoice,
'anxiety-score': AnxietyScore,
'depression-score': DepressionScore,
'input-inside-text': TextWithInput,
'audio-recorder-uploader': AudioAttachment,
...itemControlQuestionItemComponents,
}}
>
Expand Down
2 changes: 2 additions & 0 deletions src/components/BaseQuestionnaireResponseForm/controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { QuestionReference } from './widgets/reference';
import { ReferenceRadioButton } from './widgets/ReferenceRadioButton';
import { UploadFileControl } from './widgets/UploadFileControl';
import { TextWithMacroFill } from '../TextWithMacroFill';
import { AudioRecorderUploader } from './widgets/AudioRecorderUploader';

export const itemComponents: QuestionItemComponentMapping = {
text: QuestionText,
Expand Down Expand Up @@ -66,6 +67,7 @@ export const itemControlComponents: ItemControlQuestionItemComponentMapping = {
'check-box': InlineChoice,
'input-inside-text': QuestionInputInsideText,
'markdown-editor': MDEditorControl,
'audio-recorder-uploader': AudioRecorderUploader,
};

export const groupControlComponents: ItemControlGroupItemComponentMapping = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Upload } from 'antd';
import { QuestionItemProps } from 'sdc-qrf';
import classNames from 'classnames';

import s from './ReadonlyWidgets.module.scss';
import { S } from './ReadonlyWidgets.styles';
import { useUploader } from '../widgets/UploadFileControl/hooks';
import React from 'react';

export function AudioAttachment(props: QuestionItemProps) {
const { questionItem } = props;
const { text, hidden } = questionItem;
const { fileList } = useUploader(props);

if (hidden) {
return null;
}

return (
<S.Question className={classNames(s.question, s.column, 'form__question')}>
<span className={s.questionText}>{text}</span>
{fileList.length ? (
fileList.map((file) => (
<React.Fragment key={file.url}>
<S.Audio controls src={file.url} />
<Upload listType="text" showUploadList={{ showRemoveIcon: false }} fileList={fileList} />
</React.Fragment>
))
) : (
<span>-</span>
)}
</S.Question>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ export const S = {
border-top: 1px solid ${({ theme }) => theme.neutralPalette.gray_4};
}
`,
Audio: styled.audio`
height: 52px;
width: 100%;
`,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Upload } from 'antd';
import { QuestionItemProps } from 'sdc-qrf';
import classNames from 'classnames';

import s from './ReadonlyWidgets.module.scss';
import { S } from './ReadonlyWidgets.styles';
import { useUploader } from '../widgets/UploadFileControl/hooks';

export function UploadFile(props: QuestionItemProps) {
const { questionItem } = props;
const { text, hidden } = questionItem;
const { fileList } = useUploader(props);

if (hidden) {
return null;
}

return (
<S.Question className={classNames(s.question, s.column, 'form__question')}>
<span className={s.questionText}>{text}</span>
{fileList.length ? (
<Upload listType="picture" showUploadList={{ showRemoveIcon: false }} fileList={fileList} />
) : (
<span>-</span>
)}
</S.Question>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { AudioOutlined } from '@ant-design/icons';
import { Trans } from '@lingui/macro';
import { Form, UploadFile } from 'antd';
import { useCallback, useState } from 'react';
import { QuestionItemProps } from 'sdc-qrf';

import { AudioPlayer as AudioPlayerControl, AudioRecorder as AudioRecorderControl } from 'src/components/AudioRecorder';
import { useAudioRecorder } from 'src/components/AudioRecorder/hooks';

import { useUploader } from '../UploadFileControl/hooks';
import { RcFile } from 'antd/lib/upload/interface';
import { isSuccess } from '@beda.software/remote-data';
import { S } from './styles';
import { UploadFileControl } from '../UploadFileControl';

export function AudioRecorderUploader(props: QuestionItemProps) {
const { questionItem } = props;
const [showScriber, setShowScriber] = useState(false);

const { recorderControls } = useAudioRecorder();
const { formItem, customRequest, onChange, fileList } = useUploader(props);
const hasFiles = fileList.length > 0;

const onScribeChange = useCallback(
async (file: RcFile) => {
setShowScriber(false);

const fileClone = new File([file], file.name, {
type: file.type,
}) as any as UploadFile;
fileClone.uid = file.uid;
fileClone.status = 'uploading';
fileClone.percent = 0;

onChange({
fileList: [...fileList, fileClone],
file: fileClone,
});

const response = await customRequest({ file });

if (isSuccess(response)) {
fileClone.status = 'done';
fileClone.url = response.data.uploadUrl;
fileClone.percent = 100;

onChange({
fileList: [...fileList, fileClone],
file: fileClone,
});
}
},
[fileList],
);

const renderContent = () => {
if (hasFiles) {
return (
<S.Container>
<AudioPlayerControl files={fileList} />
</S.Container>
);
}

if (showScriber) {
return (
<S.Container>
<AudioRecorderControl recorderControls={recorderControls} onChange={onScribeChange} />
</S.Container>
);
}

return (
<>
<S.Button
icon={<AudioOutlined />}
type="primary"
onClick={() => {
setShowScriber(true);
recorderControls.startRecording();
}}
>
<span>
<Trans>Start scribe</Trans>
</span>
</S.Button>
<UploadFileControl
{...props}
questionItem={{
...questionItem,
text: undefined,
}}
/>
</>
);
};

return <Form.Item {...formItem}>{renderContent()}</Form.Item>;
}
Loading

0 comments on commit 0b4a65e

Please sign in to comment.