Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) enhancing the functionality of the procedure order and redesign UI #16

Merged
merged 3 commits into from
Jan 13, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ interface CompletedListProps {
export const CompletedList: React.FC<CompletedListProps> = ({ fulfillerStatus }) => {
const { t } = useTranslation();

const { workListEntries, isLoading } = useOrdersWorklist('', fulfillerStatus);
const { workListEntries, isLoading } = useOrdersWorklist('COMPLETED', fulfillerStatus);

if (isLoading) {
return <DataTableSkeleton role="progressbar" />;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import styles from './empty-state.scss';
import { EmptyDataIllustration } from '@openmrs/esm-patient-common-lib';

interface EmptyStateProps {
subTitle: string;
}

const EmptyState: React.FC<EmptyStateProps> = ({ subTitle }) => {
const { t } = useTranslation();

return (
<div className={styles.emptyStateContainer}>
<EmptyDataIllustration />
<p className={styles.subTitle}>{subTitle}</p>
</div>
);
};

export default EmptyState;
23 changes: 23 additions & 0 deletions packages/esm-procedure-orders-app/src/empty-state/empty-state.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@use '@carbon/colors';
@use '@carbon/layout';
@use '@carbon/type';

.emptyStateContainer {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 300px;
background-color: colors.$white;
row-gap: layout.$spacing-02;

& form {
border: none;
}

.subTitle {
@include type.type-style('body-compact-01');
color: colors.$cool-gray-70;
font-weight: bold;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import React, { useCallback, useState } from 'react';
import { OpenmrsDatePicker, showSnackbar, useConfig, useDebounce, useSession } from '@openmrs/esm-framework';
import React, { useCallback, useEffect, useState } from 'react';
import {
DefaultWorkspaceProps,
OpenmrsDatePicker,
showSnackbar,
useConfig,
useDebounce,
useSession,
} from '@openmrs/esm-framework';
import {
Form,
Stack,
@@ -13,6 +20,8 @@ import {
InlineLoading,
Tile,
Tag,
DatePicker,
DatePickerInput,
} from '@carbon/react';
import { useTranslation } from 'react-i18next';
import styles from './post-procedure-form.scss';
@@ -28,8 +37,10 @@ import { updateOrder } from '../../procedures-ordered/pick-procedure-order/add-t
import { mutate } from 'swr';

const validationSchema = z.object({
startDatetime: z.date({ required_error: 'Start datetime is required' }),
endDatetime: z.date({ required_error: 'End datetime is required' }),
startDatetime: z.date({
required_error: 'Start datetime is required',
}),
endDatetime: z.date({ required_error: 'End datetime is required', invalid_type_error: 'Please select a valid date' }),
outcome: z.string({ required_error: 'Outcome is required' }),
procedureReport: z.string({ required_error: 'Procedure report is required' }),
participants: z.string().optional(),
@@ -38,12 +49,18 @@ const validationSchema = z.object({

type PostProcedureFormSchema = z.infer<typeof validationSchema>;

type PostProcedureFormProps = {
type PostProcedureFormProps = DefaultWorkspaceProps & {
patientUuid: string;
procedure: Result;
order: Result;
};

const PostProcedureForm: React.FC<PostProcedureFormProps> = ({ patientUuid, procedure }) => {
const PostProcedureForm: React.FC<PostProcedureFormProps> = ({
patientUuid,
order,
closeWorkspace,
closeWorkspaceWithSavedChanges,
promptBeforeClosing,
}) => {
const { sessionLocation } = useSession();
const { t } = useTranslation();

@@ -104,7 +121,7 @@ const PostProcedureForm: React.FC<PostProcedureFormProps> = ({ patientUuid, proc

const {
control,
formState: { errors },
formState: { errors, isSubmitting, isDirty },
handleSubmit,
} = useForm<PostProcedureFormSchema>({
defaultValues: {},
@@ -115,6 +132,12 @@ const PostProcedureForm: React.FC<PostProcedureFormProps> = ({ patientUuid, proc
setSelectedProvider(selectedProvider);
}, []);

useEffect(() => {
if (promptBeforeClosing && isDirty) {
promptBeforeClosing(() => isDirty);
}
}, [promptBeforeClosing, isDirty, closeWorkspace]);

const onSubmit = async (data: PostProcedureFormSchema) => {
if (!data.startDatetime || !data.endDatetime) {
// Handle the error case when dates are invalid or missing
@@ -149,12 +172,12 @@ const PostProcedureForm: React.FC<PostProcedureFormProps> = ({ patientUuid, proc
complications.push(complication);
});

const payload: ProcedurePayload = {
const reportPayload = {
patient: patientUuid,
procedureOrder: procedure.uuid,
concept: procedure.concept.uuid,
procedureReason: procedure.orderReason?.uuid,
category: procedure.orderType?.uuid,
procedureOrder: order?.uuid,
concept: order?.concept?.uuid,
procedureReason: order?.orderReason?.uuid,
category: order?.orderType?.uuid,
status: 'COMPLETED',
outcome: data.outcome,
location: sessionLocation?.uuid,
@@ -171,35 +194,21 @@ const PostProcedureForm: React.FC<PostProcedureFormProps> = ({ patientUuid, proc
},
],
};
const body = {
fulfillerComment: '',
fulfillerStatus: 'COMPLETED',
};
try {
const response = await savePostProcedure(payload);
if (
response.status === 201 &&
(procedure?.numberOfRepeats === null || procedure?.procedures?.length === procedure?.numberOfRepeats)
) {
updateOrder(procedure.uuid, body) &&
showSnackbar({
title: t('procedureSaved', 'Procedure saved'),
subtitle: t('procedureSavedSuccessfully', 'Procedure saved successfully'),
timeoutInMs: 5000,
isLowContrast: true,
kind: 'success',
});
} else if (response.status === 201 && procedure?.procedures?.length < procedure?.numberOfRepeats) {
const response = await savePostProcedure(reportPayload);
if (response.ok) {
showSnackbar({
title: t('procedureSaved', 'Procedure saved'),
subtitle: t('procedureSavedSuccessfully', 'Procedure saved successfully'),
timeoutInMs: 5000,
isLowContrast: true,
kind: 'success',
});
mutate((key) => typeof key === 'string' && key.startsWith('/ws/rest/v1/order'), undefined, {
revalidate: true,
});
closeWorkspaceWithSavedChanges();
}
mutate((key) => typeof key === 'string' && key.startsWith('/ws/rest/v1/order'), undefined, { revalidate: true });
closeOverlay();
} catch (error) {
console.error(error);
showSnackbar({
@@ -225,33 +234,51 @@ const PostProcedureForm: React.FC<PostProcedureFormProps> = ({ patientUuid, proc
<Controller
control={control}
name="startDatetime"
render={({ field: { onChange, value } }) => (
<OpenmrsDatePicker
value={value}
id="startDatetime"
labelText={t('startDatetime', 'Start Datetime')}
onChange={onChange}
isInvalid={!!errors.startDatetime}
autoFocus
/>
render={({ field, fieldState }) => (
<DatePicker
datePickerType="single"
className={styles.formDatePicker}
onChange={(event) => {
field.onChange(event[0]);
}}
value={field.value}>
<DatePickerInput
placeholder="mm/dd/yyyy"
labelText={t('startDatetime', 'Start Datetime')}
id="startDatetime"
size="md"
invalid={!!errors.startDatetime}
invalidText={errors.startDatetime?.message}
/>
</DatePicker>
)}
/>
</Layer>
<Layer>
<Controller
control={control}
name="endDatetime"
render={({ field: { onChange, value } }) => (
<OpenmrsDatePicker
value={value}
id="endDatetime"
labelText={t('endDatetime', 'End Datetime')}
onChange={onChange}
isInvalid={!!errors.endDatetime}
/>
render={({ field, fieldState }) => (
<DatePicker
datePickerType="single"
className={styles.formDatePicker}
onChange={(event) => {
field.onChange(event[0]);
}}
value={field.value}>
<DatePickerInput
placeholder="mm/dd/yyyy"
labelText={t('endDatetime', 'End Datetime')}
id="endDatetime"
size="md"
invalid={!!errors.endDatetime}
invalidText={errors.endDatetime?.message}
/>
</DatePicker>
)}
/>
</Layer>

<Layer>
<FormLabel className={styles.formLabel}>{t('procedureOutcome', 'Procedure outcome')}</FormLabel>
<Controller
@@ -273,7 +300,6 @@ const PostProcedureForm: React.FC<PostProcedureFormProps> = ({ patientUuid, proc
},
]}
itemToString={(item) => (item ? item.text : '')}
titleText={t('outcome', 'Outcome')}
placeholder={t('selectOutcome', 'Select outcome')}
invalid={!!errors.outcome}
invalidText={errors.outcome?.message}
@@ -289,7 +315,6 @@ const PostProcedureForm: React.FC<PostProcedureFormProps> = ({ patientUuid, proc
render={({ field: { onChange } }) => (
<TextArea
id="procedureReport"
labelText={t('procedureReport', 'Procedure report')}
rows={4}
onChange={onChange}
placeholder={t('procedureReportPlaceholder', 'Enter procedure report')}
@@ -302,26 +327,23 @@ const PostProcedureForm: React.FC<PostProcedureFormProps> = ({ patientUuid, proc
<Layer>
<FormLabel className={styles.formLabel}>{t('participants', 'Participants')}</FormLabel>
<div>
{selectedParticipants?.map(
(item) =>
(
<>
<Tag style={{ display: 'inline-flex', alignItems: 'center' }}>
<span style={{ marginRight: '8px' }}>{item.display}</span>
<svg
focusable="false"
fill="currentColor"
width="16"
height="16"
viewBox="0 0 32 32"
aria-hidden="true"
onClick={() => setSelectedParticipants((prevItems) => prevItems.filter((i) => i !== item))}>
<path d={StringPath}></path>
</svg>
</Tag>
</>
) ?? '-',
)}
{selectedParticipants?.map((item) => (
<>
<Tag style={{ display: 'inline-flex', alignItems: 'center' }}>
<span style={{ marginRight: '8px' }}>{item.display}</span>
<svg
focusable="false"
fill="currentColor"
width="16"
height="16"
viewBox="0 0 32 32"
aria-hidden="true"
onClick={() => setSelectedParticipants((prevItems) => prevItems.filter((i) => i !== item))}>
<path d={StringPath}></path>
</svg>
</Tag>
</>
))}
</div>
<div>
<Search
@@ -369,26 +391,23 @@ const PostProcedureForm: React.FC<PostProcedureFormProps> = ({ patientUuid, proc
<Layer>
<FormLabel className={styles.formLabel}>{t('complications', 'Complications')}</FormLabel>
<div>
{selectedItems?.map(
(item) =>
(
<>
<Tag style={{ display: 'inline-flex', alignItems: 'center' }}>
<span style={{ marginRight: '8px' }}>{item.display}</span>
<svg
focusable="false"
fill="currentColor"
width="16"
height="16"
viewBox="0 0 32 32"
aria-hidden="true"
onClick={() => setSelectedItems((prevItems) => prevItems.filter((i) => i !== item))}>
<path d={StringPath}></path>
</svg>
</Tag>
</>
) ?? '-',
)}
{selectedItems?.map((item) => (
<>
<Tag style={{ display: 'inline-flex', alignItems: 'center' }}>
<span style={{ marginRight: '8px' }}>{item.display}</span>
<svg
focusable="false"
fill="currentColor"
width="16"
height="16"
viewBox="0 0 32 32"
aria-hidden="true"
onClick={() => setSelectedItems((prevItems) => prevItems.filter((i) => i !== item))}>
<path d={StringPath}></path>
</svg>
</Tag>
</>
))}
</div>
<div>
<Search
@@ -435,10 +454,10 @@ const PostProcedureForm: React.FC<PostProcedureFormProps> = ({ patientUuid, proc
</Layer>
</Stack>
<ButtonSet className={styles.buttonSetContainer}>
<Button onClick={() => closeOverlay()} size="lg" kind="secondary">
<Button onClick={closeWorkspace} size="md" kind="secondary">
{t('discard', 'Discard')}
</Button>
<Button type="submit" size="lg" kind="primary">
<Button type="submit" size="md" kind="primary">
{t('saveAndClose', 'Save & Close')}
</Button>
</ButtonSet>
Original file line number Diff line number Diff line change
@@ -82,6 +82,8 @@

.formContainer {
margin: 1rem;
position: relative;
z-index: 100;
}

.button {
@@ -181,3 +183,7 @@
background-color: $ui-03;
}
}

.formDatePicker input {
min-width: 23rem;
}
Loading
Loading