Skip to content
Open
Changes from 5 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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import React, { useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import flattenDeep from 'lodash-es/flattenDeep';
import {
Expand Down Expand Up @@ -29,6 +29,75 @@ interface QuestionModalProps {
resetIndices: () => void;
}

/**
* Mapping of allowed top‑level property keys for each question type.
* Adjust these keys as needed for your implementation.
*/
const allowedPropertiesMapping: Record<string, string[]> = {
control: ['id', 'label', 'type', 'questionOptions'],
encounterDatetime: ['id', 'label', 'type', 'questionOptions', 'datePickerFormat'],
encounterLocation: ['id', 'label', 'type', 'questionOptions'],
encounterProvider: ['id', 'label', 'type', 'questionOptions'],
encounterRole: ['id', 'label', 'type', 'questionOptions'],
obs: ['id', 'label', 'type', 'questionOptions'],
obsGroup: ['id', 'label', 'type', 'questionOptions', 'questions'],
patientIdentifier: ['id', 'label', 'type', 'questionOptions'],
testOrder: ['id', 'label', 'type', 'questionOptions'],
programState: ['id', 'label', 'type', 'questionOptions'],
};

/**
* Mapping of allowed keys for the nested questionOptions object per question type.
*/
const allowedQuestionOptionsMapping: Record<string, string[]> = {
control: ['rendering', 'minLength', 'maxLength'],
encounterDatetime: ['rendering'],
encounterLocation: ['rendering'],
encounterProvider: ['rendering'],
encounterRole: ['rendering', 'isSearchable'],
obs: ['rendering', 'concept'],
obsGroup: ['rendering', 'concept'],
patientIdentifier: ['rendering', 'identifierType', 'minLength', 'maxLength'],
testOrder: ['rendering'],
programState: ['rendering', 'programUuid', 'workflowUuid', 'answers'],
};

/**
* Cleans the given questionOptions object by retaining only allowed keys for the new type.
*/
function cleanQuestionOptionsForType(options: any, newType: string): any {
const allowedOpts = allowedQuestionOptionsMapping[newType] || [];
const cleanedOpts = Object.fromEntries(Object.entries(options).filter(([optKey]) => allowedOpts.includes(optKey)));
// Ensure required property 'rendering' exists if present in the original options.
if (!('rendering' in cleanedOpts) && options.rendering) {
cleanedOpts.rendering = options.rendering;
}
return cleanedOpts;
}

/**
* Cleans the given form field by retaining only allowed top‑level properties for the new type.
* Also cleans nested questionOptions using the nested mapping.
*/
function cleanFormFieldForType(field: FormField, newType: string): FormField {
const allowedKeys = allowedPropertiesMapping[newType] || [];
const cleaned: Partial<FormField> = {} as Partial<FormField>;

// Copy only allowed top‑level properties.
(allowedKeys as (keyof FormField)[]).forEach((key) => {
if (key in field) {
(cleaned as any)[key] = field[key];
}
});

// If questionOptions is allowed and exists, clean it using the nested mapping.
if (cleaned.questionOptions && typeof cleaned.questionOptions === 'object') {
cleaned.questionOptions = cleanQuestionOptionsForType(cleaned.questionOptions, newType);
}

return { ...cleaned, type: newType } as FormField;
}

const QuestionModalContent: React.FC<QuestionModalProps> = ({
formField: formFieldProp,
closeModal,
Expand All @@ -40,6 +109,14 @@ const QuestionModalContent: React.FC<QuestionModalProps> = ({
}) => {
const { t } = useTranslation();
const { formField, setFormField } = useFormField();
useEffect(() => {
if (formField && formField.type) {
const cleaned = cleanFormFieldForType(formField, formField.type);
if (JSON.stringify(cleaned) !== JSON.stringify(formField)) {
setFormField(cleaned);
}
}
}, [formField?.type, formField, setFormField]);

const getAllQuestionIds = useCallback((questions?: FormField[]): string[] => {
if (!questions) return [];
Expand Down Expand Up @@ -81,11 +158,12 @@ const QuestionModalContent: React.FC<QuestionModalProps> = ({
}));
}, [setFormField]);

// Updated deletion: filter out the question by its id.
const deleteObsGroupQuestion = useCallback(
(index: number) => {
(id: string) => {
setFormField((prevFormField) => ({
...prevFormField,
questions: prevFormField.questions?.filter((_, i) => i !== index) || [],
questions: prevFormField.questions?.filter((q) => q.id !== id) || [],
}));
},
[setFormField],
Expand Down Expand Up @@ -150,7 +228,7 @@ const QuestionModalContent: React.FC<QuestionModalProps> = ({
<AccordionItem
key={`Question ${index + 1}`}
title={question.label ?? `Question ${index + 1}`}
open={index === formField.questions?.length - 1}
open={index === formField.questions.length - 1}
className={styles.obsGroupQuestionContent}
>
<FormFieldProvider
Expand All @@ -163,10 +241,10 @@ const QuestionModalContent: React.FC<QuestionModalProps> = ({
<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />
<Button
kind="danger"
onClick={() => deleteObsGroupQuestion(index)}
onClick={() => deleteObsGroupQuestion(question.id)}
className={styles.deleteObsGroupQuestionButton}
>
{t('deleteQuestion', 'Delete question')}
{t('deleteQuestion', 'Delete')}
</Button>
</FormFieldProvider>
</AccordionItem>
Expand Down
Loading