Skip to content

Commit

Permalink
Khp3 6301 add ability to add multiple orders to the lab manifest (#294)
Browse files Browse the repository at this point in the history
* Configured manifest types,updated formm to use the config

* Added modal form for collecting smaple informaton, mutation predicate optimized, active request hook adapted to endpoint payload

* Adding and removing orders to and from the manifest
  • Loading branch information
Omoshlawi authored Aug 2, 2024
1 parent 159019b commit a470c68
Show file tree
Hide file tree
Showing 12 changed files with 487 additions and 134 deletions.
26 changes: 23 additions & 3 deletions packages/esm-lab-manifest-app/src/config-schema.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
import { Type, validator } from '@openmrs/esm-framework';

export const configSchema = {};

export type Config = {};
export const configSchema = {
labmanifestTypes: {
_type: Type.Array,
_description: 'List of Lab manifest types',
_default: [
{
id: 1,
type: 'EID Type',
},
{
id: 2,
type: 'VL Type',
},
{
id: 3,
type: 'FLU Type',
},
],
},
};
export interface LabManifestConfig {
labmanifestTypes: Array<{ id: number; type: string }>;
}
29 changes: 13 additions & 16 deletions packages/esm-lab-manifest-app/src/forms/lab-manifest-form.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
display: flex;
justify-content: space-between;
.warning {
@include type.type-style('heading-compact-01');
@include type.type-style('heading-01');
color: $ui-05;
}
}
Expand Down Expand Up @@ -46,10 +46,11 @@
flex-wrap: nowrap;
gap: spacing.$spacing-05; // Adjust gap between columns
align-items: center;
width: 100%;
}

.datePickerInput {
width: 100%;
flex-grow: 1;
}

.button {
Expand All @@ -64,16 +65,23 @@
padding: 0rem;
margin-top: spacing.$spacing-05;
display: flex;
justify-content: flex-end;
gap: spacing.$spacing-05;
justify-content: space-between;
width: 100%;
margin-bottom: spacing.$spacing-05;
}

.datesContainer {
padding: 0rem;
justify-content: space-between;
width: 100%;
display: flex;
}
.inlineActions {
display: flex;
gap: spacing.$spacing-05; /* Adjust the spacing as needed */
}

.contactFormTitle {
.formTitle {
@include type.type-style('heading-02');
display: flex;
align-items: center;
Expand Down Expand Up @@ -113,14 +121,3 @@
gap: spacing.$spacing-05;
}
}

/* New Styles for Facility and Visit Type */
.facilityVisitRow {
display: flex;
flex-wrap: nowrap;
gap: spacing.$spacing-05; /* Adjust gap between columns */
}

.facilityColumn {
flex: 1 1 0%; /* Makes columns take up equal space */
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ import {
TextInput,
} from '@carbon/react';
import { zodResolver } from '@hookform/resolvers/zod';
import { DefaultWorkspaceProps, parseDate, showSnackbar, useLayoutType } from '@openmrs/esm-framework';
import { DefaultWorkspaceProps, parseDate, showSnackbar, useConfig, useLayoutType } from '@openmrs/esm-framework';
import React, { useEffect } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import { LabManifestFilters, labManifestFormSchema, manifestTypes, saveLabManifest } from '../lab-manifest.resources';
import { LabManifestFilters, labManifestFormSchema, saveLabManifest } from '../lab-manifest.resources';
import styles from './lab-manifest-form.scss';
import { County, MappedLabManifest } from '../types';
import { mutate } from 'swr';
import { LabManifestConfig } from '../config-schema';
interface LabManifestFormProps extends DefaultWorkspaceProps {
patientUuid: string;
manifest?: MappedLabManifest;
Expand All @@ -27,10 +28,13 @@ interface LabManifestFormProps extends DefaultWorkspaceProps {
type ContactListFormType = z.infer<typeof labManifestFormSchema>;

const LabManifestForm: React.FC<LabManifestFormProps> = ({ closeWorkspace, manifest }) => {
const { labmanifestTypes } = useConfig<LabManifestConfig>();
const counties = require('../counties.json') as County[];

const form = useForm<ContactListFormType>({
defaultValues: {
...manifest,
manifestType: manifest?.manifestType ? Number(manifest.manifestType) : undefined,
dispatchDate: manifest?.dispatchDate ? parseDate(manifest.dispatchDate) : undefined,
startDate: manifest?.startDate ? parseDate(manifest.startDate) : undefined,
endDate: manifest?.endDate ? parseDate(manifest.endDate) : undefined,
Expand All @@ -44,18 +48,15 @@ const LabManifestForm: React.FC<LabManifestFormProps> = ({ closeWorkspace, manif
const onSubmit = async (values: ContactListFormType) => {
try {
await saveLabManifest(values, manifest?.uuid);
if (manifest?.uuid) {
mutate((key) => {
return (
typeof key === 'string' &&
key.startsWith(`/ws/rest/v1/labmanifest/${manifest!.uuid}?status=${values.manifestStatus}`)
);
});
} else {
mutate((key) => {
return typeof key === 'string' && key.startsWith(`/ws/rest/v1/labmanifest?status=${values.manifestStatus}`);
});
}
const mutateLinks = [
`/ws/rest/v1/labmanifest?v=full&status=${values.manifestStatus}`,
`/ws/rest/v1/kemrorder/validorders?manifestUuid=${manifest?.uuid}`,
`/ws/rest/v1/labmanifest/${manifest?.uuid}`,
];
mutate((key) => {
return typeof key === 'string' && mutateLinks.some((link) => key.startsWith(link));
});

closeWorkspace();
showSnackbar({ title: 'Success', kind: 'success', subtitle: 'Lab manifest created successfully!' });
} catch (error) {
Expand All @@ -64,7 +65,7 @@ const LabManifestForm: React.FC<LabManifestFormProps> = ({ closeWorkspace, manif
};
return (
<Form onSubmit={form.handleSubmit(onSubmit)}>
<span className={styles.contactFormTitle}>{t('formTitle', 'Fill in the form details')}</span>
<span className={styles.formTitle}>{t('formTitle', 'Fill in the form details')}</span>
<Stack gap={4} className={styles.grid}>
<Column>
<Controller
Expand Down Expand Up @@ -129,8 +130,8 @@ const LabManifestForm: React.FC<LabManifestFormProps> = ({ closeWorkspace, manif
}}
initialSelectedItem={field.value}
label="Choose option"
items={manifestTypes.map((r) => r.value)}
itemToString={(item) => manifestTypes.find((r) => r.value === item)?.label ?? ''}
items={labmanifestTypes.map((r) => r.id)}
itemToString={(item) => labmanifestTypes.find((r) => r.id === item)?.type ?? ''}
/>
)}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import {
Button,
ButtonSet,
Column,
DatePicker,
DatePickerInput,
Dropdown,
Form,
ModalBody,
ModalFooter,
ModalHeader,
Row,
Stack,
} from '@carbon/react';
import { zodResolver } from '@hookform/resolvers/zod';
import { showSnackbar } from '@openmrs/esm-framework';
import React from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { mutate } from 'swr';
import { z } from 'zod';
import { addOrderToManifest, labManifestOrderToManifestFormSchema, sampleTypes } from '../lab-manifest.resources';
import styles from './lab-manifest-form.scss';
import { useLabManifest } from '../hooks';

interface LabManifestOrdersToManifestFormProps {
onClose: () => void;
props: {
title?: string;
selectedOrders: Array<{
labManifest: {
uuid: string;
};
order: {
uuid: string;
};
payload: string;
}>;
};
}

type OrderToManifestFormType = z.infer<typeof labManifestOrderToManifestFormSchema>;

const LabManifestOrdersToManifestForm: React.FC<LabManifestOrdersToManifestFormProps> = ({
onClose,
props: { title, selectedOrders },
}) => {
const { t } = useTranslation();
const form = useForm<OrderToManifestFormType>({
defaultValues: {},
resolver: zodResolver(labManifestOrderToManifestFormSchema),
});

const { error, isLoading, manifest } = useLabManifest(selectedOrders[0]?.labManifest?.uuid);
const onSubmit = async (values: OrderToManifestFormType) => {
try {
const results = await Promise.allSettled(
selectedOrders.map((order) => addOrderToManifest({ ...order, ...values })),
);
results.forEach((res) => {
if (res.status === 'fulfilled') {
showSnackbar({ title: 'Success', kind: 'success', subtitle: 'Order added succesfully' });
} else {
showSnackbar({ title: 'Failure', kind: 'error', subtitle: 'Error adding order to the manifest' });
}
});
const mutateLinks = [
`/ws/rest/v1/labmanifest?v=full&status=${manifest.manifestStatus}`,
`/ws/rest/v1/kemrorder/validorders?manifestUuid=${manifest?.uuid}`,
`/ws/rest/v1/labmanifest/${manifest?.uuid}`,
];
mutate((key) => {
return typeof key === 'string' && mutateLinks.some((link) => key.startsWith(link));
});
onClose();
} catch (error) {
showSnackbar({ title: 'Failure', kind: 'error', subtitle: 'Error adding orders to the manifest' });
}
};

return (
<React.Fragment>
<Form onSubmit={form.handleSubmit(onSubmit)}>
<ModalHeader closeModal={onClose} className={styles.heading}>
{title ?? t('updateSampleDetails', 'Update Sample Details')}
</ModalHeader>
<ModalBody>
<Stack gap={4} className={styles.grid}>
<Column>
<Controller
control={form.control}
name="sampleType"
render={({ field }) => (
<Dropdown
ref={field.ref}
invalid={form.formState.errors[field.name]?.message}
invalidText={form.formState.errors[field.name]?.message}
id="manifestType"
titleText={t('sampleType', 'Sample Type')}
onChange={(e) => {
field.onChange(e.selectedItem);
}}
initialSelectedItem={field.value}
label="Choose option"
items={sampleTypes.map((r) => r.value)}
itemToString={(item) => sampleTypes.find((r) => r.value === item)?.label ?? ''}
/>
)}
/>
</Column>
<Row className={styles.datePickersRow}>
<Column className={styles.datePickerInput}>
<Controller
control={form.control}
name="sampleCollectionDate"
render={({ field }) => (
<DatePicker
dateFormat="d/m/Y"
id="sampleCollectionDate"
datePickerType="single"
{...field}
invalid={form.formState.errors[field.name]?.message}
invalidText={form.formState.errors[field.name]?.message}>
<DatePickerInput
invalid={form.formState.errors[field.name]?.message}
invalidText={form.formState.errors[field.name]?.message}
placeholder="mm/dd/yyyy"
labelText={t('sampleCollectionDate', 'Sample collection date')}
/>
</DatePicker>
)}
/>
</Column>
<Column className={styles.datePickerInput}>
<Controller
control={form.control}
name="sampleSeparationDate"
render={({ field }) => (
<DatePicker
dateFormat="d/m/Y"
id="sampleSeparationDate"
datePickerType="single"
{...field}
invalid={form.formState.errors[field.name]?.message}
invalidText={form.formState.errors[field.name]?.message}>
<DatePickerInput
invalid={form.formState.errors[field.name]?.message}
invalidText={form.formState.errors[field.name]?.message}
placeholder="mm/dd/yyyy"
labelText={t('sampleSeparationDate', 'Sample seperation date')}
/>
</DatePicker>
)}
/>
</Column>
</Row>
</Stack>
</ModalBody>
<ModalFooter>
<ButtonSet className={styles.buttonSet}>
<Button className={styles.button} kind="primary" disabled={form.formState.isSubmitting} type="submit">
Submit
</Button>
<Button className={styles.button} kind="secondary" onClick={onClose}>
Cancel
</Button>
</ButtonSet>
</ModalFooter>
</Form>
</React.Fragment>
);
};

export default LabManifestOrdersToManifestForm;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Button, ButtonSet, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
import React from 'react';
import { useTranslation } from 'react-i18next';

interface SampleDeleteConfirmDialogProps {
onClose: () => void;
onDelete: () => void;
}
const SampleDeleteConfirmDialog: React.FC<SampleDeleteConfirmDialogProps> = ({ onClose, onDelete }) => {
const { t } = useTranslation();

return (
<React.Fragment>
<ModalHeader closeModal={onClose}>{t('warning', 'Warning!')}</ModalHeader>
<ModalBody>Are you sure you want to delete sample.This action is irriversible?</ModalBody>
<ModalFooter>
<ButtonSet>
<Button kind="secondary" onClick={onClose}>
Cancel
</Button>
<Button kind="primary" onClick={onDelete}>
Remove
</Button>
</ButtonSet>
</ModalFooter>
</React.Fragment>
);
};

export default SampleDeleteConfirmDialog;
Loading

0 comments on commit a470c68

Please sign in to comment.