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

6266 todays encounter report #6375

Open
wants to merge 12 commits into
base: develop
Choose a base branch
from
2 changes: 2 additions & 0 deletions client/packages/common/src/intl/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,7 @@
"messages.finalised-stock-take": "This stocktake is finalised and cannot be edited",
"messages.fridge-tag-import-successful": "Success! {{numberOfLogs}} logs and {{numberOfBreaches}} breaches imported.",
"messages.full-screen-enabled": "Full screen mode enabled, press ESC or click the Exit button in the top right to exit",
"messages.how-to-read-encounters": "Column descriptions: \n* Status: Patient's status in the selected program",
"messages.how-to-read-expiring-items": "Column descriptions: \n* Expiring in (days): Number of days left until batch expires using the expiring items period preference. \nYellow - batches below the expiring item period. \nGreen - anything above the expiring item period. \nRed - expired items. \n* Expected usage: AMC of the item multiplied by the number of months left until expiry. \n* Stock at risk: Quantity of stock that will likely expire before it is used. \nSOH - Expected usage. If the stock is expired, then this should show all SOH for that stock line.",
"messages.how-to-read-item-usage": "Column descriptions: \n* Stock on order: Sum of quantity in Internal Orders minus any linked Inbound Shipments \n* Months cover: Number of months the current stock is expected to last. The value is 'In stock' / 'AMC'. Note that AMC is calculated using the number of months given by the 'AMC lookback period' shown in the report filter which is configured with the 'monthly consumption lookback period' store preference. A default value will be used if no value is provided in store preference.",
"messages.how-to-read-report": "How to read {{reportName}} report?",
Expand Down Expand Up @@ -1671,6 +1672,7 @@
"report.pack-size": "Pack size",
"report.packing-slip": "Packing slip",
"report.page": "Page",
"report.pending-encounters": "Pending Encounters",
"report.printed-date": "Printed date",
"report.quan": "Quan",
"report.received-from": "Received from",
Expand Down
1 change: 1 addition & 0 deletions client/packages/common/src/types/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5257,6 +5257,7 @@ export type PatientNode = {
isDeceased: Scalars['Boolean']['output'];
lastName?: Maybe<Scalars['String']['output']>;
name: Scalars['String']['output'];
nextOfKin?: Maybe<PatientNode>;
nextOfKinId?: Maybe<Scalars['String']['output']>;
phone?: Maybe<Scalars['String']['output']>;
programEnrolments: ProgramEnrolmentResponse;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React, { useEffect } from 'react';
import { Autocomplete } from '@openmsupply-client/common';
import { DocumentRegistryFragment } from '../api/operations.generated';
import { useDocumentRegistry } from '../api';

type PatientProgramSearchInputProps = {
value: DocumentRegistryFragment | null;
onChange: (newProgram: DocumentRegistryFragment) => void;
};

export const PatientProgramSearchInput = ({
value,
onChange,
}: PatientProgramSearchInputProps) => {
const { data, isLoading } = useDocumentRegistry.get.programRegistries();

// If there is only one value, set it automatically
useEffect(() => {
if (data?.nodes.length == 1 && !value) {
onChange(data.nodes[0]!); // if length is 1, the first element must exist
}
}, [data?.nodes.length]);

return (
<Autocomplete
fullWidth
loading={isLoading}
options={data?.nodes ?? []}
optionKey="name"
onChange={(_, newVal) =>
newVal && newVal.id !== value?.id && onChange(newVal)
}
value={value ? { label: value.name ?? '', ...value } : null}
isOptionEqualToValue={(option, value) => option.id === value.id}
clearable={false}
/>
);
};
1 change: 1 addition & 0 deletions client/packages/programs/src/Components/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './DocumentHistory';
export * from './ProgramSearchModal';
export * from './ProgramSearchInput';
export * from './PatientProgramSearchInput';
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import React, { FC } from 'react';
import React from 'react';
import { rankWith, ControlProps, isDateTimeControl } from '@jsonforms/core';
import { withJsonFormsControlProps } from '@jsonforms/react';
import {
TextFieldProps,
StandardTextFieldProps,
BasicTextInput,
DetailInputWithLabelRow,
DateTimePicker,
DateTimePickerProps,
DateUtils,
DateTimePickerInput,
} from '@openmsupply-client/common';
import { FORM_LABEL_WIDTH } from '../styleConstants';
import { z } from 'zod';
Expand All @@ -27,44 +23,6 @@ const Options = z

type Options = z.infer<typeof Options>;

const TextField = (params: TextFieldProps) => {
const textInputProps: StandardTextFieldProps = {
...params,
variant: 'standard',
};
return <BasicTextInput {...textInputProps} />;
};

const DateTimePickerInput: FC<
Omit<DateTimePickerProps<Date>, 'renderInput'> & {
error: string;
showTime?: boolean;
onError?: (validationError: string) => void;
}
> = ({ showTime, onError, ...props }) => (
<DateTimePicker
format={showTime ? 'P p' : 'P'}
disabled={props.disabled}
slots={{ textField: TextField }}
slotProps={{
textField: {
error: !!props.error,
helperText: props.error,
FormHelperTextProps: !!props.error
? { sx: { color: 'error.main' } }
: undefined,
},
}}
onError={onError}
views={
showTime
? ['year', 'month', 'day', 'hours', 'minutes', 'seconds']
: ['year', 'month', 'day']
}
{...props}
/>
);

export const datetimeTester = rankWith(5, isDateTimeControl);

const UIComponent = (props: ControlProps) => {
Expand Down Expand Up @@ -120,10 +78,7 @@ const UIComponent = (props: ControlProps) => {
inputAlignment="start"
Input={
!dateOnly ? (
<DateTimePickerInput
// undefined is displayed as "now" and null as unset
{...sharedComponentProps}
/>
<DateTimePickerInput showTime {...sharedComponentProps} />
) : (
<DateTimePickerInput
{...sharedComponentProps}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import { rankWith, ControlProps, uiTypeIs } from '@jsonforms/core';
import { withJsonFormsControlProps } from '@jsonforms/react';
import { Box, DetailInputWithLabelRow } from '@openmsupply-client/common';
import { DefaultFormRowSx, FORM_GAP, FORM_LABEL_WIDTH } from '../common';
import { PatientProgramSearchInput } from '../../Components';
import { DocumentRegistryFragment } from '../../api';

export const programSearchTester = rankWith(10, uiTypeIs('ProgramSearch'));

const UIComponent = (props: ControlProps) => {
const { handleChange, label, path } = props;

const [program, setProgram] = React.useState<DocumentRegistryFragment | null>(
null
);

const onChangeProgram = async (program: DocumentRegistryFragment) => {
setProgram(program);
handleChange(path, program.contextId);
};

return (
<DetailInputWithLabelRow
sx={DefaultFormRowSx}
label={label}
labelWidthPercentage={FORM_LABEL_WIDTH}
inputAlignment={'start'}
Input={
<Box display="flex" alignItems="center" gap={FORM_GAP} width="100%">
<PatientProgramSearchInput
onChange={onChangeProgram}
value={program}
/>
</Box>
}
/>
);
};

const UIComponentWrapper = (props: ControlProps) => {
if (!props.visible) {
return null;
}
return <UIComponent {...props} />;
};

export const ProgramSearch = withJsonFormsControlProps(UIComponentWrapper);
1 change: 1 addition & 0 deletions client/packages/programs/src/JsonForms/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export * from './ProgramEvent';
export * from './HistoricEncounterData';
export * from './BloodPressure';
export * from './PatientSearch';
export * from './ProgramSearch';
export * from './Prescription/Prescription';
6 changes: 4 additions & 2 deletions client/packages/reports/src/ListView/ListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ export const ListView = () => {
);
const programReports = data?.nodes?.filter(
report =>
report?.subContext === 'HIVCareProgram' &&
report?.context === ReportContext.Dispensary
report?.context === ReportContext.Dispensary &&
(report?.subContext === 'HIVCareProgram' ||
// TODO: Also check vaccine module enabled?
report.subContext === 'Encounters')
);
const onReportClick = (report: ReportRowFragment) => {
if (report.argumentSchema) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import React, { FC, useState } from 'react';
import { JsonFormsRendererRegistryEntry } from '@jsonforms/core';

import { JsonData, JsonForm } from '@openmsupply-client/programs';
import {
JsonData,
JsonForm,
ProgramSearch,
programSearchTester,
} from '@openmsupply-client/programs';
import { ReportRowFragment } from '../api';
import { useDialog, useUrlQuery } from '@common/hooks';
import { DialogButton, Typography } from '@common/components';
Expand All @@ -13,6 +19,10 @@ export type ReportArgumentsModalProps = {
onArgumentsSelected: (report: ReportRowFragment, args: JsonData) => void;
};

const additionalRenderers: JsonFormsRendererRegistryEntry[] = [
{ tester: programSearchTester, renderer: ProgramSearch },
];

export const ReportArgumentsModal: FC<ReportArgumentsModalProps> = ({
report,
onReset,
Expand Down Expand Up @@ -82,6 +92,7 @@ export const ReportArgumentsModal: FC<ReportArgumentsModalProps> = ({
updateData={(newData: JsonData) => {
setData(newData);
}}
additionalRenderers={additionalRenderers}
/>
</>
</Modal>
Expand Down
21 changes: 20 additions & 1 deletion server/graphql/types/src/types/program/patient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use async_graphql::dataloader::DataLoader;
use async_graphql::*;
use chrono::{DateTime, Local, NaiveDate, Utc};
use graphql_core::generic_filters::{DateFilterInput, EqualFilterStringInput, StringFilterInput};
use graphql_core::loader::DocumentLoader;
use graphql_core::loader::{DocumentLoader, PatientLoader};
use graphql_core::{map_filter, ContextExt};

use graphql_core::pagination::PaginationInput;
Expand Down Expand Up @@ -217,6 +217,25 @@ impl PatientNode {
})
}

pub async fn next_of_kin(&self, ctx: &Context<'_>) -> Result<Option<PatientNode>> {
let loader = ctx.get_loader::<DataLoader<PatientLoader>>();

if let Some(next_of_kin_id) = &self.patient.next_of_kin_id {
let result = loader
.load_one(next_of_kin_id.to_owned())
.await?
.map(|patient| PatientNode {
patient,
allowed_ctx: self.allowed_ctx.clone(),
store_id: self.store_id.clone(),
});

return Ok(result);
} else {
return Ok(None);
}
}

pub async fn document(&self, ctx: &Context<'_>) -> Result<Option<DocumentNode>> {
let loader = ctx.get_loader::<DataLoader<DocumentLoader>>();

Expand Down
30 changes: 30 additions & 0 deletions server/reports/encounters/2_6_0/argument_schemas/arguments.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"Filters": {
"properties": {
"programId": {
"description": "Program ID",
"type": "string"
},
"after": {
"description": "From date",
"format": "date-time",
"type": "string"
},
"before": {
"description": "To date",
"format": "date-time",
"type": "string"
}
},
"required": ["programId"]
}
},
"type": "object",
"allOf": [
{
"$ref": "#/definitions/Filters"
}
]
}
26 changes: 26 additions & 0 deletions server/reports/encounters/2_6_0/argument_schemas/arguments_ui.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"type": "VerticalLayout",
"elements": [
{
"type": "ProgramSearch",
"scope": "#/properties/programId",
"label": "T#label.program"
},
{
"type": "Control",
"scope": "#/properties/after",
"label": "T#label.from-date",
"options": {
"dateOnly": true
}
},
{
"type": "Control",
"scope": "#/properties/before",
"label": "T#label.to-date",
"options": {
"dateOnly": true
}
}
]
}
15 changes: 15 additions & 0 deletions server/reports/encounters/2_6_0/report-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"is_custom": false,
"version": "2.6.0",
"code": "encounters",
"context": "DISPENSARY",
"sub_context": "Encounters",
"name": "Pending Encounters",
"queries": {
"gql": "encountersQuery.graphql"
},
"arguments": {
"schema": "argument_schemas/arguments.json",
"ui": "argument_schemas/arguments_ui.json"
}
}
Loading
Loading