diff --git a/packages/esm-lab-manifest-app/src/config-schema.ts b/packages/esm-lab-manifest-app/src/config-schema.ts index 7f10afa8b..24b129179 100644 --- a/packages/esm-lab-manifest-app/src/config-schema.ts +++ b/packages/esm-lab-manifest-app/src/config-schema.ts @@ -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 }>; +} diff --git a/packages/esm-lab-manifest-app/src/forms/lab-manifest-form.scss b/packages/esm-lab-manifest-app/src/forms/lab-manifest-form.scss index 73e4dfe92..2b511513f 100644 --- a/packages/esm-lab-manifest-app/src/forms/lab-manifest-form.scss +++ b/packages/esm-lab-manifest-app/src/forms/lab-manifest-form.scss @@ -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; } } @@ -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 { @@ -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; @@ -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 */ -} diff --git a/packages/esm-lab-manifest-app/src/forms/lab-manifest-form.workspace.tsx b/packages/esm-lab-manifest-app/src/forms/lab-manifest-form.workspace.tsx index 01543883f..955d75299 100644 --- a/packages/esm-lab-manifest-app/src/forms/lab-manifest-form.workspace.tsx +++ b/packages/esm-lab-manifest-app/src/forms/lab-manifest-form.workspace.tsx @@ -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; @@ -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, @@ -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) { @@ -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 @@ -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 ?? ''} /> )} /> diff --git a/packages/esm-lab-manifest-app/src/forms/lab-manifest-orders-to-manifest.modal.tsx b/packages/esm-lab-manifest-app/src/forms/lab-manifest-orders-to-manifest.modal.tsx new file mode 100644 index 000000000..68a8c15cc --- /dev/null +++ b/packages/esm-lab-manifest-app/src/forms/lab-manifest-orders-to-manifest.modal.tsx @@ -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; diff --git a/packages/esm-lab-manifest-app/src/forms/sample-delete-confirm-dialog.modal.tsx b/packages/esm-lab-manifest-app/src/forms/sample-delete-confirm-dialog.modal.tsx new file mode 100644 index 000000000..29bf3d33e --- /dev/null +++ b/packages/esm-lab-manifest-app/src/forms/sample-delete-confirm-dialog.modal.tsx @@ -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; diff --git a/packages/esm-lab-manifest-app/src/hooks/useActiveRequests.ts b/packages/esm-lab-manifest-app/src/hooks/useActiveRequests.ts index a90f2bae3..2a11f3ccd 100644 --- a/packages/esm-lab-manifest-app/src/hooks/useActiveRequests.ts +++ b/packages/esm-lab-manifest-app/src/hooks/useActiveRequests.ts @@ -1,24 +1,15 @@ -import { restBaseUrl } from '@openmrs/esm-framework'; +import { FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework'; import useSWR from 'swr'; import { activeRequests } from '../lab-manifest.mock'; import { ActiveRequests } from '../types'; -const mockeFetch = async (url: string) => { - const status = url.split('=').at(-1); - return await new Promise<Array<ActiveRequests>>((resolve, _) => { - setTimeout(() => { - resolve(activeRequests); - }, 3000); - }); -}; - -const useActiveRequests = () => { - const url = `${restBaseUrl}/active-request`; - const { isLoading, error, data } = useSWR<Array<ActiveRequests>>(url, mockeFetch); +const useActiveRequests = (labManifestUuid: string) => { + const url = `${restBaseUrl}/kemrorder/validorders?manifestUuid=${labManifestUuid}`; + const { isLoading, error, data } = useSWR<FetchResponse<ActiveRequests>>(url, openmrsFetch); return { isLoading, - requests: data ?? [], + request: data?.data, error, }; }; diff --git a/packages/esm-lab-manifest-app/src/index.ts b/packages/esm-lab-manifest-app/src/index.ts index f4f6164d3..0b2c8e6c5 100644 --- a/packages/esm-lab-manifest-app/src/index.ts +++ b/packages/esm-lab-manifest-app/src/index.ts @@ -26,7 +26,14 @@ export function startupApp() { export const root = getAsyncLifecycle(() => import('./root.component'), options); export const labManifestForm = getAsyncLifecycle(() => import('./forms/lab-manifest-form.workspace'), options); - +export const labManifestOrderToManifestForm = getAsyncLifecycle( + () => import('./forms/lab-manifest-orders-to-manifest.modal'), + options, +); +export const sampleDeleteConfirmDialog = getAsyncLifecycle( + () => import('./forms/sample-delete-confirm-dialog.modal'), + options, +); export const labManifestDashboardLink = getSyncLifecycle( createLeftPanelLink({ name: 'lab-manifest', diff --git a/packages/esm-lab-manifest-app/src/lab-manifest.resources.ts b/packages/esm-lab-manifest-app/src/lab-manifest.resources.ts index c07cdfcb1..f769cf759 100644 --- a/packages/esm-lab-manifest-app/src/lab-manifest.resources.ts +++ b/packages/esm-lab-manifest-app/src/lab-manifest.resources.ts @@ -1,4 +1,4 @@ -import { generateOfflineUuid, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework'; +import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework'; import { z } from 'zod'; import { LabManifest, MappedLabManifest } from './types'; @@ -42,10 +42,15 @@ export const LabManifestFilters = [ ]; const PHONE_NUMBER_REGEX = /^(\+?254|0)((7|1)\d{8})$/; +export const sampleTypes = [ + { label: 'Frozen plasma', value: 'Frozen plasma' }, + { label: 'Whole Blood', value: 'Whole Blood' }, +]; + export const labManifestFormSchema = z.object({ startDate: z.date({ coerce: true }), endDate: z.date({ coerce: true }), - manifestType: z.string(), + manifestType: z.number({ coerce: true }), dispatchDate: z.date({ coerce: true }), courierName: z.string().optional(), personHandedTo: z.string().optional(), @@ -59,12 +64,11 @@ export const labManifestFormSchema = z.object({ manifestStatus: z.string(), }); -export const manifestTypes = [ - { - value: 'VL', - label: 'Viral load', - }, -]; +export const labManifestOrderToManifestFormSchema = z.object({ + sampleType: z.string(), + sampleCollectionDate: z.date({ coerce: true }), + sampleSeparationDate: z.date({ coerce: true }), +}); export const saveLabManifest = async (data: z.infer<typeof labManifestFormSchema>, manifestId: string | undefined) => { let url; @@ -81,7 +85,44 @@ export const saveLabManifest = async (data: z.infer<typeof labManifestFormSchema 'Content-Type': 'application/json', }, method: 'POST', - body: JSON.stringify(data), + body: JSON.stringify({ + startDate: data.startDate, + endDate: data.endDate, + dispatchDate: data.dispatchDate, + courier: data.courierName, + courierOfficer: data.personHandedTo, + status: data.manifestStatus, + county: data.county, + subCounty: data.subCounty, + facilityEmail: data.facilityEmail, + facilityPhoneContact: data.facilityPhoneContact, + clinicianPhoneContact: data.clinicianContact, + clinicianName: data.clinicianName, + labPocPhoneNumber: data.labPersonContact, + manifestType: data.manifestType, + }), + signal: abortController.signal, + }); +}; + +export const addOrderToManifest = async (data: z.infer<typeof labManifestOrderToManifestFormSchema>) => { + let url = `${restBaseUrl}/labmanifestorder`; + const abortController = new AbortController(); + return openmrsFetch(url, { + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + body: JSON.stringify({ ...data, status: 'Pending' }), + signal: abortController.signal, + }); +}; + +export const removeSampleFromTheManifest = async (orderUuid: string) => { + let url = `${restBaseUrl}/labmanifestorder/${orderUuid}`; + const abortController = new AbortController(); + return openmrsFetch(url, { + method: 'DELETE', signal: abortController.signal, }); }; @@ -90,7 +131,7 @@ export const extractLabManifest = (manifest: LabManifest) => ({ uuid: manifest.uuid, dispatchDate: manifest.dispatchDate, - endDate: manifest.dispatchDate, + endDate: manifest.endDate, startDate: manifest.startDate, clinicianContact: manifest.clinicianPhoneContact, clinicianName: manifest.clinicianName, @@ -101,7 +142,7 @@ export const extractLabManifest = (manifest: LabManifest) => labPersonContact: manifest.labPocPhoneNumber, manifestId: manifest.identifier, manifestStatus: manifest.status, - // manifestType: manifest.manifestType, + manifestType: String(manifest.manifestType), personHandedTo: manifest.courierOfficer, subCounty: manifest.subCounty, samples: manifest.labManifestOrders ?? [], diff --git a/packages/esm-lab-manifest-app/src/routes.json b/packages/esm-lab-manifest-app/src/routes.json index 15456c3ca..14d64ef55 100644 --- a/packages/esm-lab-manifest-app/src/routes.json +++ b/packages/esm-lab-manifest-app/src/routes.json @@ -18,6 +18,14 @@ "name": "lab-manifest-form", "component": "labManifestForm" }, + { + "name": "lab-manifest-order-modal-form", + "component": "labManifestOrderToManifestForm" + }, + { + "name": "sample-delete-confirm-dialog", + "component": "sampleDeleteConfirmDialog" + }, { "component": "root", "name": "lab-manifest-dashboard-root", diff --git a/packages/esm-lab-manifest-app/src/tables/lab-manifest-active-requests.component.tsx b/packages/esm-lab-manifest-app/src/tables/lab-manifest-active-requests.component.tsx index 3688b5928..f0708f6d0 100644 --- a/packages/esm-lab-manifest-app/src/tables/lab-manifest-active-requests.component.tsx +++ b/packages/esm-lab-manifest-app/src/tables/lab-manifest-active-requests.component.tsx @@ -10,61 +10,41 @@ import { TableHead, TableHeader, TableRow, + TableSelectAll, + TableSelectRow, } from '@carbon/react'; -import { View } from '@carbon/react/icons'; -import { ErrorState, navigate, useLayoutType, usePagination } from '@openmrs/esm-framework'; +import { Add } from '@carbon/react/icons'; +import { ErrorState, showModal, usePagination } from '@openmrs/esm-framework'; import { CardHeader, EmptyState, usePaginationInfo } from '@openmrs/esm-patient-common-lib'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import styles from './lab-manifest-table.scss'; import useActiveRequests from '../hooks/useActiveRequests'; -import { ActiveRequests } from '../types'; +import styles from './lab-manifest-table.scss'; interface LabManifestActiveRequestsProps { manifestUuid: string; } const LabManifestActiveRequests: React.FC<LabManifestActiveRequestsProps> = ({ manifestUuid }) => { - const { error, isLoading, requests } = useActiveRequests(); + const { error, isLoading, request: request } = useActiveRequests(manifestUuid); const { t } = useTranslation(); const [pageSize, setPageSize] = useState(10); const headerTitle = t('activeRequests', 'Active Requests'); - const { results, totalPages, currentPage, goTo } = usePagination(requests, pageSize); + const { results, totalPages, currentPage, goTo } = usePagination(request?.Orders ?? [], pageSize); const { pageSizes } = usePaginationInfo(pageSize, totalPages, currentPage, results.length); - const headers = [ { header: t('patientName', 'Patient name'), - key: 'startDate', + key: 'patientName', }, { header: t('cccKDODNumber', 'CCC/KDOD Number'), - key: 'cccKDODNumber', - }, - { - header: t('batchNumber', 'Batch Number'), - key: 'batchNumber', + key: 'cccKdod', }, { - header: t('sampleType', 'Sample type'), - key: 'sampleType', - }, - { - header: t('manifestId', 'Manifest Id'), - key: 'manifestId', - }, - { - header: t('labPersonContact', 'Lab person Contact'), - key: 'labPersonContact', - }, - { - header: t('status', 'Status'), - key: 'status', - }, - { - header: t('dispatch', 'Dispatch'), - key: 'dispatch', + header: t('dateRequested', 'Date Requested'), + key: 'dateRequested', }, { header: t('actions', 'Actions'), @@ -72,22 +52,54 @@ const LabManifestActiveRequests: React.FC<LabManifestActiveRequestsProps> = ({ m }, ]; - const handleViewManifestSamples = (manifestUuid: string) => { - navigate({ to: window.getOpenmrsSpaBase() + `home/lab-manifest/${manifestUuid}` }); + const handleAddSelectedToManifest = ( + selected: Array<{ + labManifest: { + uuid: string; + }; + order: { + uuid: string; + }; + payload: string; + }>, + ) => { + if (selected.length > 0) { + const dispose = showModal('lab-manifest-order-modal-form', { + onClose: () => dispose(), + props: { + title: selected.length > 1 ? 'Add Multiple Orders To Manifest' : undefined, + selectedOrders: selected, + }, + }); + } }; const tableRows = - (results as ActiveRequests[])?.map((activeRequest) => { + results?.map((activeRequest) => { return { - id: `${activeRequest.uuid}`, + id: `${activeRequest.orderUuid}`, + patientName: activeRequest.patientName, + cccKdod: activeRequest.cccKdod, + dateRequested: activeRequest.dateRequested, actions: ( <Button - renderIcon={View} - hasIconOnly kind="tertiary" - iconDescription={t('view', 'View')} - onClick={() => handleViewManifestSamples(activeRequest.uuid)} - /> + iconDescription={t('addToManifest', 'Add To manifest')} + onClick={() => + handleAddSelectedToManifest([ + { + labManifest: { + uuid: manifestUuid, + }, + order: { + uuid: activeRequest.orderUuid, + }, + payload: activeRequest.payload, + }, + ]) + }> + Add To manifest + </Button> ), }; }) ?? []; @@ -99,7 +111,7 @@ const LabManifestActiveRequests: React.FC<LabManifestActiveRequestsProps> = ({ m return <ErrorState headerTitle={headerTitle} error={error} />; } - if (requests.length === 0) { + if ((request?.Orders ?? []).length === 0) { return ( <EmptyState headerTitle={t('activeRequests', 'Active Requests')} @@ -109,46 +121,75 @@ const LabManifestActiveRequests: React.FC<LabManifestActiveRequestsProps> = ({ m } return ( <div className={styles.widgetContainer}> - <CardHeader title={headerTitle}>{''}</CardHeader> <DataTable useZebraStyles size="sm" rows={tableRows ?? []} headers={headers} - render={({ rows, headers, getHeaderProps, getTableProps, getTableContainerProps }) => ( - <TableContainer {...getTableContainerProps()}> - <Table {...getTableProps()}> - <TableHead> - <TableRow> - {headers.map((header) => ( - <TableHeader - {...getHeaderProps({ - header, - isSortable: header.isSortable, - })}> - {header.header?.content ?? header.header} - </TableHeader> - ))} - </TableRow> - </TableHead> - <TableBody> - {rows.map((row) => ( - <TableRow key={row.id}> - {row.cells.map((cell) => ( - <TableCell key={cell.id}>{cell.value}</TableCell> + render={({ + rows, + headers, + getHeaderProps, + getRowProps, + getSelectionProps, + getTableProps, + getTableContainerProps, + selectedRows, + }) => ( + <> + <CardHeader title={headerTitle}> + <Button + onClick={() => { + const data = selectedRows.map(({ id }) => ({ + labManifest: { + uuid: manifestUuid, + }, + order: { + uuid: id, + }, + payload: request.Orders.find(({ orderUuid }) => orderUuid === id)?.payload ?? '', + })); + + handleAddSelectedToManifest(data); + }} + renderIcon={Add} + kind="ghost"> + {t('addSelectedSamples', 'Add Selected Samples')} + </Button> + </CardHeader> + <TableContainer {...getTableContainerProps()}> + <Table {...getTableProps()}> + <TableHead> + <TableRow> + <TableSelectAll {...getSelectionProps()} /> + {headers.map((header, i) => ( + <TableHeader key={i} {...getHeaderProps({ header })}> + {header.header} + </TableHeader> ))} </TableRow> - ))} - </TableBody> - </Table> - </TableContainer> + </TableHead> + <TableBody> + {rows.map((row, i) => ( + <TableRow key={i} {...getRowProps({ row })} onClick={(evt) => {}}> + <TableSelectRow {...getSelectionProps({ row })} /> + {row.cells.map((cell) => ( + <TableCell key={cell.id}>{cell.value}</TableCell> + ))} + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + </> )} /> + <Pagination page={currentPage} pageSize={pageSize} pageSizes={pageSizes} - totalItems={requests.length} + totalItems={(request?.Orders ?? []).length} onChange={({ page, pageSize }) => { goTo(page); setPageSize(pageSize); diff --git a/packages/esm-lab-manifest-app/src/tables/lab-manifest-samples.component.tsx b/packages/esm-lab-manifest-app/src/tables/lab-manifest-samples.component.tsx index 9935564e6..85f8856b6 100644 --- a/packages/esm-lab-manifest-app/src/tables/lab-manifest-samples.component.tsx +++ b/packages/esm-lab-manifest-app/src/tables/lab-manifest-samples.component.tsx @@ -12,13 +12,24 @@ import { TableRow, } from '@carbon/react'; import { TrashCan, View } from '@carbon/react/icons'; -import { ErrorState, formatDate, navigate, parseDate, useLayoutType, usePagination } from '@openmrs/esm-framework'; +import { + ErrorState, + formatDate, + navigate, + parseDate, + showModal, + showSnackbar, + useLayoutType, + usePagination, +} from '@openmrs/esm-framework'; import { CardHeader, EmptyState, usePaginationInfo } from '@openmrs/esm-patient-common-lib'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import styles from './lab-manifest-table.scss'; import { useLabManifest } from '../hooks'; import { LabManifestSample } from '../types'; +import { removeSampleFromTheManifest } from '../lab-manifest.resources'; +import { mutate } from 'swr'; interface LabManifestSamplesProps { manifestUuid: string; @@ -68,8 +79,28 @@ const LabManifestSamples: React.FC<LabManifestSamplesProps> = ({ manifestUuid }) }, ]; - const handleDeleteManifestSample = (manifestUuid: string) => { - // TODO Implement delete logic when endpoint is a ready + const handleDeleteManifestSample = (sampleUUid: string) => { + const dispose = showModal('sample-delete-confirm-dialog', { + onClose: () => dispose(), + onDelete: async () => { + try { + await removeSampleFromTheManifest(sampleUUid); + 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)); + }); + + dispose(); + showSnackbar({ title: 'Success', kind: 'success', subtitle: 'Sample removed from manifest successfully!' }); + } catch (e) { + showSnackbar({ title: 'Failure', kind: 'error', subtitle: 'Error removing sample from the manifest' }); + } + }, + }); }; const tableRows = diff --git a/packages/esm-lab-manifest-app/src/types/index.ts b/packages/esm-lab-manifest-app/src/types/index.ts index 06017dd03..9c20fae00 100644 --- a/packages/esm-lab-manifest-app/src/types/index.ts +++ b/packages/esm-lab-manifest-app/src/types/index.ts @@ -59,7 +59,19 @@ export interface LabManifestSample { } export interface ActiveRequests { - uuid: string; + Orders: Array<{ + orderId: number; + orderUuid: string; + patientId: number; + patientUuid: string; + patientName: string; + cccKdod: string; + dateRequested: string; + payload: string; + hasProblem; + }>; + cccNumberType: number; + heiNumberType: number; } export interface Constiuency {