Skip to content

Commit 9da92e0

Browse files
authored
Merge pull request #659 from hack4impact-uiuc/add-radio-button-to-table
Add radio button to table
2 parents fb59d7a + 7b9f1b4 commit 9da92e0

9 files changed

Lines changed: 135 additions & 19 deletions

File tree

apps/backend/src/utils/initDb.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
Field,
33
FieldType,
44
PatientTagsField,
5+
PatientTagSyria,
56
ReservedStep,
67
RootStep,
78
RootStepFieldKeys,
@@ -16,7 +17,6 @@ import encrypt from 'mongoose-encryption'
1617
import { StepModel } from '../models/Metadata'
1718
import { fileSchema } from '../schemas/fileSchema'
1819
import { signatureSchema } from '../schemas/signatureSchema'
19-
import { PatientTagSyria } from '@3dp4me/types';
2020

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

4848
// Migrations for root step
4949
const initReservedSteps = async () => {
50-
log.info("Initializing the reserved step")
50+
log.info('Initializing the reserved step')
5151
const rootStep = await StepModel.findOne({ key: ReservedStep.Root }).lean()
5252
if (!rootStep) {
53-
log.info("Creating the reserved step")
53+
log.info('Creating the reserved step')
5454
return StepModel.create(RootStep)
5555
}
5656

5757
// Older version missing the tag field
5858
const tagField = rootStep.fields.find((f) => f.key === RootStepFieldKeys.Tags)
5959
if (!tagField) {
60-
log.info("Tags is missing from reserved step, adding it")
60+
log.info('Tags is missing from reserved step, adding it')
6161
return StepModel.updateOne(
6262
{ key: ReservedStep.Root },
6363
{ $push: { fields: PatientTagsField } }
@@ -67,17 +67,17 @@ const initReservedSteps = async () => {
6767
// Older version missing the syria option
6868
const syriaOption = tagField.options.find((o) => o.Question.EN === PatientTagSyria.Question.EN)
6969
if (!syriaOption) {
70-
log.info("Syria is missing from tag options, adding it")
70+
log.info('Syria is missing from tag options, adding it')
7171
return StepModel.updateOne(
72-
{
72+
{
7373
key: ReservedStep.Root,
74-
"fields.key": RootStepFieldKeys.Tags
74+
'fields.key': RootStepFieldKeys.Tags,
7575
},
76-
{ $push: { "fields.$.options": PatientTagSyria } }
76+
{ $push: { 'fields.$.options': PatientTagSyria } }
7777
)
7878
}
7979

80-
log.info("Reserved step is up to date")
80+
log.info('Reserved step is up to date')
8181
return null
8282
}
8383

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { NativeSelect } from '@mui/material'
2+
3+
import { useTranslations } from '../../hooks/useTranslations'
4+
import { DropdownInput } from '../DropdownInput/DropdownInput'
5+
import { FormOption } from './FormOption'
6+
7+
export interface DropdownFieldProps<T extends string> {
8+
fieldId: T
9+
title: string
10+
value?: string
11+
options: FormOption[]
12+
isDisabled?: boolean
13+
onChange?: (field: T, value: string) => void
14+
}
15+
16+
const DropdownField = <T extends string>({
17+
fieldId,
18+
title,
19+
value = '',
20+
options,
21+
isDisabled = false,
22+
onChange,
23+
}: DropdownFieldProps<T>) => {
24+
const [translations, selectedLang] = useTranslations()
25+
26+
const shouldHideOption = (option: FormOption) =>
27+
option.IsHidden && value?.toString() !== option._id.toString()
28+
29+
const optionsFields = options.map((option) => {
30+
if (shouldHideOption(option)) return null
31+
32+
return (
33+
<option
34+
value={option._id}
35+
disabled={isDisabled}
36+
key={option._id}
37+
>
38+
{option.Question[selectedLang]}
39+
</option>
40+
)
41+
})
42+
43+
if (value === '') {
44+
optionsFields.unshift(
45+
<option value="" key="empty">
46+
{translations.components.swal.field.selectOption}
47+
</option>
48+
)
49+
}
50+
51+
return (
52+
<div>
53+
<h3>{title}</h3>
54+
<NativeSelect
55+
onChange={(e) => onChange?.(fieldId, e.target.value)}
56+
value={value}
57+
input={<DropdownInput />}
58+
>
59+
{optionsFields}
60+
</NativeSelect>
61+
</div>
62+
)
63+
}
64+
65+
export default DropdownField

apps/frontend/src/components/Fields/FieldGroup/FieldGroupHelpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ function canFieldBeDisplayedInTable(metadata: Field) {
2828
case FieldType.MULTILINE_STRING:
2929
case FieldType.DATE:
3030
case FieldType.PHONE:
31+
case FieldType.RADIO_BUTTON:
3132
return true
3233
default:
3334
return false

apps/frontend/src/components/Fields/FieldGroup/FieldGroupTable.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
/* eslint import/no-cycle: "off" */
22
// Unfortunately, there has to be an import cycle, because this is by nature, recursive
3-
import { Language } from '@3dp4me/types'
3+
import { FieldType, Language } from '@3dp4me/types'
44
import AddIcon from '@mui/icons-material/Add'
55
import { TableCell } from '@mui/material'
66
import { useMemo } from 'react'
77
import styled from 'styled-components'
88

99
import XIcon from '../../../assets/x-icon.png'
1010
import { useTranslations } from '../../../hooks/useTranslations'
11+
import { DisplayFieldType } from '../../../utils/constants'
1112
import {
1213
ColumnMetadata,
1314
defaultTableHeaderRenderer,
@@ -151,10 +152,16 @@ const FieldGroupTable = ({
151152
field.id
152153
)}`
153154

155+
let fieldType = metadata.subFields[i].fieldType as FieldType | DisplayFieldType
156+
if (fieldType === FieldType.RADIO_BUTTON) {
157+
fieldType = DisplayFieldType.DROPDOWN
158+
}
159+
154160
return (
155161
<CellEditContainer key={fieldKey}>
156162
<StepField
157163
displayName={''} // No display name since the header already has one
164+
fieldType={fieldType}
158165
metadata={metadata.subFields[i]}
159166
value={itemData[field.id]}
160167
key={field.id}
@@ -187,6 +194,26 @@ const FieldGroupTable = ({
187194
return cols
188195
}
189196

197+
const disabledRowRenderer = <T extends Record<string, any>>(
198+
rowData: ColumnMetadata<T>[],
199+
itemData: T,
200+
lang: Language
201+
) => {
202+
const itemDataCopy = { ...itemData }
203+
204+
rowData.forEach((field, i) => {
205+
if (field.dataType === FieldType.RADIO_BUTTON) {
206+
const fieldMeta = metadata.subFields[i]
207+
const selectedOption = fieldMeta.options.find(
208+
(option) => option._id === itemData[field.id]
209+
)
210+
itemDataCopy[field.id] = (selectedOption?.Question?.[lang] || '') as any
211+
}
212+
})
213+
214+
return defaultTableRowRenderer(rowData, itemDataCopy, lang)
215+
}
216+
190217
return (
191218
<>
192219
<h3 key={`${metadata.key}-table-title`}>{metadata.displayName[selectedLang]}</h3>
@@ -195,7 +222,7 @@ const FieldGroupTable = ({
195222
headers={tableHeaders}
196223
rowData={tableColumnMetadata}
197224
renderHeader={defaultTableHeaderRenderer}
198-
renderTableRow={isDisabled ? defaultTableRowRenderer : tableRowRenderer}
225+
renderTableRow={isDisabled ? disabledRowRenderer : tableRowRenderer}
199226
rowStyle={{ height: '50px' }}
200227
containerStyle={{
201228
marginTop: '6px',

apps/frontend/src/components/StepField/StepField.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Divider from '@mui/material/Divider'
55
import React from 'react'
66

77
import { useTranslations } from '../../hooks/useTranslations'
8+
import { DisplayFieldType } from '../../utils/constants'
89

910
const AudioRecorder = React.lazy(() => import('../AudioRecorder/AudioRecorder'))
1011
const DateField = React.lazy(() => import('../Fields/DateField'))
@@ -17,9 +18,11 @@ const TextArea = React.lazy(() => import('../Fields/TextArea'))
1718
const TextField = React.lazy(() => import('../Fields/TextField'))
1819
const Files = React.lazy(() => import('../Files/Files'))
1920
const PhoneField = React.lazy(() => import('../Fields/PhoneField'))
21+
const DropdownField = React.lazy(() => import('../Fields/DropdownField'))
2022

2123
export interface StepFieldProps {
2224
metadata: Field
25+
fieldType?: FieldType | DisplayFieldType
2326
value: any
2427
patientId: string
2528
displayName: string
@@ -34,6 +37,7 @@ export interface StepFieldProps {
3437

3538
const StepField = ({
3639
metadata,
40+
fieldType,
3741
value,
3842
patientId = '',
3943
displayName,
@@ -46,9 +50,10 @@ const StepField = ({
4650
handleFileDelete = () => {},
4751
}: StepFieldProps) => {
4852
const selectedLang = useTranslations()[1]
53+
const type = fieldType || metadata.fieldType
4954

5055
const generateField = () => {
51-
switch (metadata.fieldType) {
56+
switch (type) {
5257
case FieldType.STRING:
5358
return (
5459
<TextField
@@ -127,6 +132,17 @@ const StepField = ({
127132
onChange={handleSimpleUpdate}
128133
/>
129134
)
135+
case DisplayFieldType.DROPDOWN:
136+
return (
137+
<DropdownField
138+
fieldId={metadata.key}
139+
isDisabled={isDisabled}
140+
title={displayName}
141+
value={value}
142+
options={metadata.options}
143+
onChange={handleSimpleUpdate}
144+
/>
145+
)
130146

131147
case FieldType.AUDIO:
132148
return (

apps/frontend/src/translations.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@
201201
"createFieldTitle": "New Field",
202202
"editFieldTitle": "Edit Field",
203203
"fieldSettings": "Field Settings",
204+
"selectOption": "Select Option",
204205
"fieldType": "Field Type",
205206
"clearance": "Clearance Level",
206207
"customization": "Customization",
@@ -463,6 +464,7 @@
463464
"patientTags": "علامات المريض"
464465
},
465466
"field": {
467+
"selectOption": "اختر خيار",
466468
"createFieldTitle": "حقل جديد",
467469
"editFieldTitle": "تعديل الحقل",
468470
"fieldSettings": "إعدادات الحقل",

apps/frontend/src/utils/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export enum DisplayFieldType {
4646
STEP_STATUS = 'StepStatus',
4747
PATIENT_STATUS = 'PatientStatus',
4848
ACCESS = 'Access',
49+
DROPDOWN = 'Dropdown',
4950
}
5051

5152
export type AnyFieldType = FieldType | DisplayFieldType

apps/frontend/src/utils/fields.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,8 @@ export const fieldToString = (
311311
case FieldType.STRING:
312312
case FieldType.NUMBER:
313313
case FieldType.PHONE:
314+
case FieldType.RADIO_BUTTON:
315+
case DisplayFieldType.DROPDOWN:
314316
return fieldData
315317
case FieldType.DATE:
316318
return formatDate(new Date(fieldData), selectedLang)
@@ -337,16 +339,20 @@ export const fieldToString = (
337339
* @returns The JSX
338340
*/
339341
export const fieldToJSX = (fieldData: any, fieldType: AnyFieldType, selectedLang: Language) => {
340-
const stringifiedField = fieldToString(fieldData, fieldType, selectedLang)
341-
342342
switch (fieldType) {
343343
case FieldType.MULTILINE_STRING:
344-
return <p style={{ whiteSpace: 'pre-wrap', margin: 0 }}>{stringifiedField}</p>
344+
return (
345+
<p style={{ whiteSpace: 'pre-wrap', margin: 0 }}>
346+
{fieldToString(fieldData, fieldType, selectedLang)}
347+
</p>
348+
)
345349
case FieldType.STRING:
346350
case FieldType.NUMBER:
347351
case FieldType.PHONE:
348352
case FieldType.DATE:
349-
return stringifiedField
353+
case FieldType.RADIO_BUTTON:
354+
case DisplayFieldType.DROPDOWN:
355+
return fieldToString(fieldData, fieldType, selectedLang)
350356
case FieldType.SIGNATURE:
351357
return signatureToJSX(fieldData)
352358
case DisplayFieldType.STEP_STATUS:
@@ -357,9 +363,8 @@ export const fieldToJSX = (fieldData: any, fieldType: AnyFieldType, selectedLang
357363
return accessToJSX(fieldData, selectedLang)
358364
default:
359365
console.error(`fieldToJSX(): Unrecognized field: ${fieldType}`)
366+
return fieldToString(fieldData, fieldType, selectedLang)
360367
}
361-
362-
return stringifiedField
363368
}
364369

365370
export type HasDisplayName<T> = T & { displayName: { [key in Language]: string } }

packages/types/src/models/field.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Nullish, Unsaved } from '../utils'
2-
32
import { File } from './file'
43
import { MapPoint } from './map'
54
import { Signature } from './signature'

0 commit comments

Comments
 (0)