diff --git a/client/codegen.yml b/client/codegen.yml index f1d16e8faa..f5ae3c668d 100644 --- a/client/codegen.yml +++ b/client/codegen.yml @@ -1,7 +1,7 @@ overwrite: true -schema: - [ +schema: [ 'http://localhost:8000/graphql', + # '../server/schema.graphql', './packages/common/src/additions.schema.graphql', ] generates: diff --git a/client/packages/common/package.json b/client/packages/common/package.json index 3327a171f9..943765fdb6 100644 --- a/client/packages/common/package.json +++ b/client/packages/common/package.json @@ -14,6 +14,10 @@ "@capacitor/core": "^3.5.1", "@emotion/react": "^11.7.1", "@emotion/styled": "^11.6.0", + "@jsonforms/core": "^3.0.0-beta.3", + "@jsonforms/material-renderers": "^3.0.0-beta.3", + "@jsonforms/react": "^3.0.0-beta.3", + "@mui/icons-material": "^5.8.0", "@mui/lab": "^5.0.0-alpha.59", "@mui/material": "^5.2.3", "@openmsupply-client/config": "^0.0.0", diff --git a/client/packages/common/src/styles/theme.ts b/client/packages/common/src/styles/theme.ts index 1dc63762ad..fbc21564f8 100644 --- a/client/packages/common/src/styles/theme.ts +++ b/client/packages/common/src/styles/theme.ts @@ -197,13 +197,15 @@ export const themeOptions = { }, body2: { color: '#555770', fontSize: 12, fontWeight: 500 }, fontFamily: 'InterVariable', - login: { color: '#fafafa' }, th: { color: '#1c1c28', fontSize: 14, fontWeight: 700 }, h6: { fontFamily: 'InterVariable', fontSize: 16, color: '#555770', }, + subtitle1: { fontSize: '1.2em' }, + // Custom text variants + login: { color: '#fafafa' }, }, }; export const createTheme = (themeOptions: ThemeOptions) => { diff --git a/client/packages/common/src/types/schema.ts b/client/packages/common/src/types/schema.ts index 2605995a1c..f6da6fba5c 100644 --- a/client/packages/common/src/types/schema.ts +++ b/client/packages/common/src/types/schema.ts @@ -564,6 +564,33 @@ export type DocumentNode = { type: Scalars['String']; }; +export type DocumentRegistryConnector = { + __typename: 'DocumentRegistryConnector'; + nodes: Array; + totalCount: Scalars['Int']; +}; + +export type DocumentRegistryNode = { + __typename: 'DocumentRegistryNode'; + context: DocumentRegistryNodeContext; + documentType: Scalars['String']; + id: Scalars['String']; + jsonSchema: Scalars['JSON']; + name?: Maybe; + parentId?: Maybe; + uiSchema: Scalars['JSON']; + uiSchemaType: Scalars['String']; +}; + +export enum DocumentRegistryNodeContext { + Custom = 'CUSTOM', + Encounter = 'ENCOUNTER', + Patient = 'PATIENT', + Program = 'PROGRAM' +} + +export type DocumentRegistryResponse = DocumentRegistryConnector; + export type DocumentResponse = DocumentConnector; export type EqualFilterBigNumberInput = { @@ -659,6 +686,14 @@ export type ForeignKeyError = DeleteInboundShipmentLineErrorInterface & DeleteIn key: ForeignKey; }; +export type FormSchemaNode = { + __typename: 'FormSchemaNode'; + id: Scalars['String']; + jsonSchema: Scalars['JSON']; + type: Scalars['String']; + uiSchema: Scalars['JSON']; +}; + export type FullMutation = { __typename: 'FullMutation'; /** Add requisition lines from master item master list */ @@ -687,10 +722,11 @@ export type FullMutation = { deleteRequestRequisitionLine: DeleteRequestRequisitionLineResponse; deleteStocktake: DeleteStocktakeResponse; deleteStocktakeLine: DeleteStocktakeLineResponse; + insertDocumentRegistry: InsertDocumentResponse; + insertFormSchema: InsertFormSchemaResponse; insertInboundShipment: InsertInboundShipmentResponse; insertInboundShipmentLine: InsertInboundShipmentLineResponse; insertInboundShipmentServiceLine: InsertInboundShipmentServiceLineResponse; - insertJsonSchema: InsertJsonSchemaResponse; insertLocation: InsertLocationResponse; insertOutboundShipment: InsertOutboundShipmentResponse; insertOutboundShipmentLine: InsertOutboundShipmentLineResponse; @@ -837,6 +873,16 @@ export type FullMutationDeleteStocktakeLineArgs = { }; +export type FullMutationInsertDocumentRegistryArgs = { + input: InsertDocumentRegistryInput; +}; + + +export type FullMutationInsertFormSchemaArgs = { + input: InsertFormSchemaInput; +}; + + export type FullMutationInsertInboundShipmentArgs = { input: InsertInboundShipmentInput; storeId: Scalars['String']; @@ -855,11 +901,6 @@ export type FullMutationInsertInboundShipmentServiceLineArgs = { }; -export type FullMutationInsertJsonSchemaArgs = { - input: InsertJsonSchemaInput; -}; - - export type FullMutationInsertLocationArgs = { input: InsertLocationInput; storeId: Scalars['String']; @@ -1028,8 +1069,11 @@ export type FullQuery = { * The refresh token is returned as a cookie */ authToken: AuthTokenResponse; + document?: Maybe; documentHistory: DocumentHistoryResponse; + documentRegistry: DocumentRegistryResponse; documents: DocumentResponse; + formSchema?: Maybe; insertPatient: InsertPatientResponse; invoice: InvoiceResponse; invoiceByNumber: InvoiceResponse; @@ -1037,7 +1081,6 @@ export type FullQuery = { invoices: InvoicesResponse; /** Query omSupply "item" entries */ items: ItemsResponse; - jsonSchema: JsonschemaResponse; /** Query omSupply "locations" entries */ locations: LocationsResponse; logout: LogoutResponse; @@ -1089,6 +1132,12 @@ export type FullQueryAuthTokenArgs = { }; +export type FullQueryDocumentArgs = { + name: Scalars['String']; + storeId: Scalars['String']; +}; + + export type FullQueryDocumentHistoryArgs = { name: Scalars['String']; storeId: Scalars['String']; @@ -1101,6 +1150,11 @@ export type FullQueryDocumentsArgs = { }; +export type FullQueryFormSchemaArgs = { + id: Scalars['String']; +}; + + export type FullQueryInsertPatientArgs = { input: InsertPatientInput; storeId: Scalars['String']; @@ -1142,11 +1196,6 @@ export type FullQueryItemsArgs = { }; -export type FullQueryJsonSchemaArgs = { - id: Scalars['String']; -}; - - export type FullQueryLocationsArgs = { filter?: InputMaybe; page?: InputMaybe; @@ -1301,10 +1350,30 @@ export type InboundInvoiceCounts = { created: InvoiceCountsSummary; }; +export type InsertDocumentRegistryInput = { + context: DocumentRegistryNodeContext; + documentType: Scalars['String']; + formSchemaId: Scalars['String']; + id: Scalars['String']; + name?: InputMaybe; + parentId?: InputMaybe; +}; + +export type InsertDocumentResponse = DocumentRegistryNode; + export type InsertErrorInterface = { description: Scalars['String']; }; +export type InsertFormSchemaInput = { + id: Scalars['String']; + jsonSchema: Scalars['JSON']; + type: Scalars['String']; + uiSchema: Scalars['JSON']; +}; + +export type InsertFormSchemaResponse = FormSchemaNode; + export type InsertInboundShipmentError = { __typename: 'InsertInboundShipmentError'; error: InsertInboundShipmentErrorInterface; @@ -1392,17 +1461,6 @@ export type InsertInboundShipmentServiceLineResponseWithId = { response: InsertInboundShipmentServiceLineResponse; }; -export type InsertJsonSchemaInput = { - schema: Scalars['String']; -}; - -export type InsertJsonSchemaNode = { - __typename: 'InsertJsonSchemaNode'; - id: Scalars['String']; -}; - -export type InsertJsonSchemaResponse = InsertJsonSchemaNode; - export type InsertLocationError = { __typename: 'InsertLocationError'; error: InsertLocationErrorInterface; @@ -1920,11 +1978,9 @@ export type ItemsResponse = ItemConnector; export type JsonschemaNode = { __typename: 'JsonschemaNode'; id: Scalars['String']; - schema: Scalars['JSON']; + jsonSchema: Scalars['JSON']; }; -export type JsonschemaResponse = JsonschemaNode; - export type LocationConnector = { __typename: 'LocationConnector'; nodes: Array; diff --git a/client/packages/common/src/ui/components/buttons/IconButton/IconButton.tsx b/client/packages/common/src/ui/components/buttons/IconButton/IconButton.tsx index 06d198eb71..16580c721b 100644 --- a/client/packages/common/src/ui/components/buttons/IconButton/IconButton.tsx +++ b/client/packages/common/src/ui/components/buttons/IconButton/IconButton.tsx @@ -8,6 +8,7 @@ interface ButtonProps { label: string; width?: string; height?: string; + color?: 'primary' | 'secondary' | undefined; sx?: SxProps; } @@ -18,6 +19,7 @@ export const IconButton: React.FC = ({ label, width, height, + color, sx, }) => ( @@ -27,6 +29,7 @@ export const IconButton: React.FC = ({ onClick={onClick} aria-label={label} size="small" + color={color} > {icon} diff --git a/client/packages/common/src/ui/forms/JsonForms/components/Date.tsx b/client/packages/common/src/ui/forms/JsonForms/components/Date.tsx new file mode 100644 index 0000000000..6f6e98079b --- /dev/null +++ b/client/packages/common/src/ui/forms/JsonForms/components/Date.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { rankWith, ControlProps, isDateControl } from '@jsonforms/core'; +import { withJsonFormsControlProps } from '@jsonforms/react'; +import { FormLabel, Box } from '@mui/material'; +import { BaseDatePickerInput } from '@openmsupply-client/common'; +import { + FORM_LABEL_COLUMN_WIDTH, + FORM_INPUT_COLUMN_WIDTH, +} from '../styleConstants'; + +export const dateTester = rankWith(5, isDateControl); + +const UIComponent = (props: ControlProps) => { + const { data, handleChange, label, path } = props; + + return ( + + + {label}: + + + handleChange(path, e)} + inputFormat="dd/MM/yyyy" + /> + + + ); +}; + +export const Date = withJsonFormsControlProps(UIComponent); diff --git a/client/packages/common/src/ui/forms/JsonForms/components/Group.tsx b/client/packages/common/src/ui/forms/JsonForms/components/Group.tsx new file mode 100644 index 0000000000..ef272d6c99 --- /dev/null +++ b/client/packages/common/src/ui/forms/JsonForms/components/Group.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { rankWith, uiTypeIs, LayoutProps, GroupLayout } from '@jsonforms/core'; +import { withJsonFormsLayoutProps } from '@jsonforms/react'; +import { MaterialLayoutRenderer } from '@jsonforms/material-renderers'; +import { Box, Typography } from '@mui/material'; +import { FORM_LABEL_COLUMN_WIDTH, GROUP_MAX_WIDTH } from '../styleConstants'; + +export const groupTester = rankWith(4, uiTypeIs('Group')); + +const UIComponent = (props: LayoutProps) => { + const { uischema, schema, visible, renderers, path } = props; + + const layoutProps = { + elements: (uischema as GroupLayout).elements, + schema: schema, + path: path, + direction: 'column' as 'column' | 'row', + visible: visible, + uischema: uischema, + renderers: renderers, + }; + return ( + + + {(uischema as GroupLayout).label} + + + + ); +}; + +export const Group = withJsonFormsLayoutProps(UIComponent); diff --git a/client/packages/common/src/ui/forms/JsonForms/components/Label.tsx b/client/packages/common/src/ui/forms/JsonForms/components/Label.tsx new file mode 100644 index 0000000000..483664454f --- /dev/null +++ b/client/packages/common/src/ui/forms/JsonForms/components/Label.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { + LayoutProps, + rankWith, + UISchemaElement, + uiTypeIs, +} from '@jsonforms/core'; +import { withJsonFormsLayoutProps } from '@jsonforms/react'; +import { SxProps, Typography } from '@mui/material'; +import { RegexUtils } from '@common/utils'; +import { FORM_LABEL_COLUMN_WIDTH } from '../styleConstants'; + +export const labelTester = rankWith(3, uiTypeIs('Label')); + +type LabelVariant = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p'; + +type LayoutPropsExtended = LayoutProps & { + uischema: UISchemaElement & { + sx?: SxProps; + text?: string; + variant?: LabelVariant; + }; +}; + +const variants: { [key in LabelVariant]: SxProps } = { + h1: { fontSize: '1.8em', fontWeight: 'bold', textAlign: 'center' }, + h2: { fontSize: '1.4em', fontWeight: 'bold', textAlign: 'center' }, + h3: { + fontWeight: 'bold', + textAlign: 'right', + width: FORM_LABEL_COLUMN_WIDTH, + height: '1.5em', // This shouldn't be necessary 🤷‍♂️ + marginTop: '1em', + }, + h4: {}, + h5: {}, + h6: {}, + p: {}, +}; + +const UIComponent = (props: LayoutPropsExtended) => { + const { + uischema: { variant = 'p', sx, text }, + data, + } = props; + const variantStyles = variants[variant]; + return ( + + {RegexUtils.formatTemplateString(text ?? '', data)} + + ); +}; + +export const Label = withJsonFormsLayoutProps(UIComponent); diff --git a/client/packages/common/src/ui/forms/JsonForms/components/Select.tsx b/client/packages/common/src/ui/forms/JsonForms/components/Select.tsx new file mode 100644 index 0000000000..27e6da352b --- /dev/null +++ b/client/packages/common/src/ui/forms/JsonForms/components/Select.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { rankWith, isEnumControl, ControlProps } from '@jsonforms/core'; +import { withJsonFormsControlProps } from '@jsonforms/react'; +import { FormLabel, Box } from '@mui/material'; +import { Select } from '@openmsupply-client/common'; +import { + FORM_LABEL_COLUMN_WIDTH, + FORM_INPUT_COLUMN_WIDTH, +} from '../styleConstants'; + +export const selectTester = rankWith(4, isEnumControl); + +const UIComponent = (props: ControlProps) => { + const { data, handleChange, label, schema, path } = props; + + const options = schema.enum + ? schema.enum.map((option: string) => ({ + label: option, + value: option, + })) + : []; + + return ( + + + {label}: + + +