Skip to content
Merged
Show file tree
Hide file tree
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
18 changes: 9 additions & 9 deletions apps/backend/src/utils/initDb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
Field,
FieldType,
PatientTagsField,
PatientTagSyria,
ReservedStep,
RootStep,
RootStepFieldKeys,
Expand All @@ -16,7 +17,6 @@ import encrypt from 'mongoose-encryption'
import { StepModel } from '../models/Metadata'
import { fileSchema } from '../schemas/fileSchema'
import { signatureSchema } from '../schemas/signatureSchema'
import { PatientTagSyria } from '@3dp4me/types';

/**
* Initalizes and connects to the DB. Should be called at app startup.
Expand Down Expand Up @@ -47,17 +47,17 @@ const clearModels = async () => {

// Migrations for root step
const initReservedSteps = async () => {
log.info("Initializing the reserved step")
log.info('Initializing the reserved step')
const rootStep = await StepModel.findOne({ key: ReservedStep.Root }).lean()
if (!rootStep) {
log.info("Creating the reserved step")
log.info('Creating the reserved step')
return StepModel.create(RootStep)
}

// Older version missing the tag field
const tagField = rootStep.fields.find((f) => f.key === RootStepFieldKeys.Tags)
if (!tagField) {
log.info("Tags is missing from reserved step, adding it")
log.info('Tags is missing from reserved step, adding it')
return StepModel.updateOne(
{ key: ReservedStep.Root },
{ $push: { fields: PatientTagsField } }
Expand All @@ -67,17 +67,17 @@ const initReservedSteps = async () => {
// Older version missing the syria option
const syriaOption = tagField.options.find((o) => o.Question.EN === PatientTagSyria.Question.EN)
if (!syriaOption) {
log.info("Syria is missing from tag options, adding it")
log.info('Syria is missing from tag options, adding it')
return StepModel.updateOne(
{
{
key: ReservedStep.Root,
"fields.key": RootStepFieldKeys.Tags
'fields.key': RootStepFieldKeys.Tags,
},
{ $push: { "fields.$.options": PatientTagSyria } }
{ $push: { 'fields.$.options': PatientTagSyria } }
)
}

log.info("Reserved step is up to date")
log.info('Reserved step is up to date')
return null
}

Expand Down
65 changes: 65 additions & 0 deletions apps/frontend/src/components/Fields/DropdownField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { NativeSelect } from '@mui/material'

import { useTranslations } from '../../hooks/useTranslations'
import { DropdownInput } from '../DropdownInput/DropdownInput'
import { FormOption } from './FormOption'

export interface DropdownFieldProps<T extends string> {
fieldId: T
title: string
value?: string
options: FormOption[]
isDisabled?: boolean
onChange?: (field: T, value: string) => void
}

const DropdownField = <T extends string>({
fieldId,
title,
value = '',
options,
isDisabled = false,
onChange,
}: DropdownFieldProps<T>) => {
const [translations, selectedLang] = useTranslations()

const shouldHideOption = (option: FormOption) =>
option.IsHidden && value?.toString() !== option._id.toString()

const optionsFields = options.map((option) => {
if (shouldHideOption(option)) return null

return (
<option
value={option._id}
disabled={isDisabled}
key={option._id}
>
{option.Question[selectedLang]}
</option>
)
})

if (value === '') {
optionsFields.unshift(
<option value="" key="empty">
{translations.components.swal.field.selectOption}
</option>
)
}

return (
<div>
<h3>{title}</h3>
<NativeSelect
onChange={(e) => onChange?.(fieldId, e.target.value)}
value={value}
input={<DropdownInput />}
>
{optionsFields}
</NativeSelect>
</div>
)
}

export default DropdownField
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ function canFieldBeDisplayedInTable(metadata: Field) {
case FieldType.MULTILINE_STRING:
case FieldType.DATE:
case FieldType.PHONE:
case FieldType.RADIO_BUTTON:
return true
default:
return false
Expand Down
31 changes: 29 additions & 2 deletions apps/frontend/src/components/Fields/FieldGroup/FieldGroupTable.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
/* eslint import/no-cycle: "off" */
// Unfortunately, there has to be an import cycle, because this is by nature, recursive
import { Language } from '@3dp4me/types'
import { FieldType, Language } from '@3dp4me/types'
import AddIcon from '@mui/icons-material/Add'
import { TableCell } from '@mui/material'
import { useMemo } from 'react'
import styled from 'styled-components'

import XIcon from '../../../assets/x-icon.png'
import { useTranslations } from '../../../hooks/useTranslations'
import { DisplayFieldType } from '../../../utils/constants'
import {
ColumnMetadata,
defaultTableHeaderRenderer,
Expand Down Expand Up @@ -151,10 +152,16 @@ const FieldGroupTable = ({
field.id
)}`

let fieldType = metadata.subFields[i].fieldType as FieldType | DisplayFieldType
if (fieldType === FieldType.RADIO_BUTTON) {
fieldType = DisplayFieldType.DROPDOWN
}

return (
<CellEditContainer key={fieldKey}>
<StepField
displayName={''} // No display name since the header already has one
fieldType={fieldType}
metadata={metadata.subFields[i]}
value={itemData[field.id]}
key={field.id}
Expand Down Expand Up @@ -187,6 +194,26 @@ const FieldGroupTable = ({
return cols
}

const disabledRowRenderer = <T extends Record<string, any>>(
rowData: ColumnMetadata<T>[],
itemData: T,
lang: Language
) => {
const itemDataCopy = { ...itemData }

rowData.forEach((field, i) => {
if (field.dataType === FieldType.RADIO_BUTTON) {
const fieldMeta = metadata.subFields[i]
const selectedOption = fieldMeta.options.find(
(option) => option._id === itemData[field.id]
)
itemDataCopy[field.id] = (selectedOption?.Question?.[lang] || '') as any
}
})

return defaultTableRowRenderer(rowData, itemDataCopy, lang)
}

return (
<>
<h3 key={`${metadata.key}-table-title`}>{metadata.displayName[selectedLang]}</h3>
Expand All @@ -195,7 +222,7 @@ const FieldGroupTable = ({
headers={tableHeaders}
rowData={tableColumnMetadata}
renderHeader={defaultTableHeaderRenderer}
renderTableRow={isDisabled ? defaultTableRowRenderer : tableRowRenderer}
renderTableRow={isDisabled ? disabledRowRenderer : tableRowRenderer}
rowStyle={{ height: '50px' }}
containerStyle={{
marginTop: '6px',
Expand Down
18 changes: 17 additions & 1 deletion apps/frontend/src/components/StepField/StepField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Divider from '@mui/material/Divider'
import React from 'react'

import { useTranslations } from '../../hooks/useTranslations'
import { DisplayFieldType } from '../../utils/constants'

const AudioRecorder = React.lazy(() => import('../AudioRecorder/AudioRecorder'))
const DateField = React.lazy(() => import('../Fields/DateField'))
Expand All @@ -17,9 +18,11 @@ const TextArea = React.lazy(() => import('../Fields/TextArea'))
const TextField = React.lazy(() => import('../Fields/TextField'))
const Files = React.lazy(() => import('../Files/Files'))
const PhoneField = React.lazy(() => import('../Fields/PhoneField'))
const DropdownField = React.lazy(() => import('../Fields/DropdownField'))

export interface StepFieldProps {
metadata: Field
fieldType?: FieldType | DisplayFieldType
value: any
patientId: string
displayName: string
Expand All @@ -34,6 +37,7 @@ export interface StepFieldProps {

const StepField = ({
metadata,
fieldType,
value,
patientId = '',
displayName,
Expand All @@ -46,9 +50,10 @@ const StepField = ({
handleFileDelete = () => {},
}: StepFieldProps) => {
const selectedLang = useTranslations()[1]
const type = fieldType || metadata.fieldType

const generateField = () => {
switch (metadata.fieldType) {
switch (type) {
case FieldType.STRING:
return (
<TextField
Expand Down Expand Up @@ -127,6 +132,17 @@ const StepField = ({
onChange={handleSimpleUpdate}
/>
)
case DisplayFieldType.DROPDOWN:
return (
<DropdownField
fieldId={metadata.key}
isDisabled={isDisabled}
title={displayName}
value={value}
options={metadata.options}
onChange={handleSimpleUpdate}
/>
)

case FieldType.AUDIO:
return (
Expand Down
2 changes: 2 additions & 0 deletions apps/frontend/src/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@
"createFieldTitle": "New Field",
"editFieldTitle": "Edit Field",
"fieldSettings": "Field Settings",
"selectOption": "Select Option",
"fieldType": "Field Type",
"clearance": "Clearance Level",
"customization": "Customization",
Expand Down Expand Up @@ -463,6 +464,7 @@
"patientTags": "علامات المريض"
},
"field": {
"selectOption": "اختر خيار",
"createFieldTitle": "حقل جديد",
"editFieldTitle": "تعديل الحقل",
"fieldSettings": "إعدادات الحقل",
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export enum DisplayFieldType {
STEP_STATUS = 'StepStatus',
PATIENT_STATUS = 'PatientStatus',
ACCESS = 'Access',
DROPDOWN = 'Dropdown',
}

export type AnyFieldType = FieldType | DisplayFieldType
Expand Down
17 changes: 11 additions & 6 deletions apps/frontend/src/utils/fields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ export const fieldToString = (
case FieldType.STRING:
case FieldType.NUMBER:
case FieldType.PHONE:
case FieldType.RADIO_BUTTON:
case DisplayFieldType.DROPDOWN:
return fieldData
case FieldType.DATE:
return formatDate(new Date(fieldData), selectedLang)
Expand All @@ -337,16 +339,20 @@ export const fieldToString = (
* @returns The JSX
*/
export const fieldToJSX = (fieldData: any, fieldType: AnyFieldType, selectedLang: Language) => {
const stringifiedField = fieldToString(fieldData, fieldType, selectedLang)

switch (fieldType) {
case FieldType.MULTILINE_STRING:
return <p style={{ whiteSpace: 'pre-wrap', margin: 0 }}>{stringifiedField}</p>
return (
<p style={{ whiteSpace: 'pre-wrap', margin: 0 }}>
{fieldToString(fieldData, fieldType, selectedLang)}
</p>
)
case FieldType.STRING:
case FieldType.NUMBER:
case FieldType.PHONE:
case FieldType.DATE:
return stringifiedField
case FieldType.RADIO_BUTTON:
case DisplayFieldType.DROPDOWN:
return fieldToString(fieldData, fieldType, selectedLang)
case FieldType.SIGNATURE:
return signatureToJSX(fieldData)
case DisplayFieldType.STEP_STATUS:
Expand All @@ -357,9 +363,8 @@ export const fieldToJSX = (fieldData: any, fieldType: AnyFieldType, selectedLang
return accessToJSX(fieldData, selectedLang)
default:
console.error(`fieldToJSX(): Unrecognized field: ${fieldType}`)
return fieldToString(fieldData, fieldType, selectedLang)
}

return stringifiedField
}

export type HasDisplayName<T> = T & { displayName: { [key in Language]: string } }
Expand Down
1 change: 0 additions & 1 deletion packages/types/src/models/field.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Nullish, Unsaved } from '../utils'

import { File } from './file'
import { MapPoint } from './map'
import { Signature } from './signature'
Expand Down
Loading