From 91ee19a75a455e08a22fdbaf70ef3534c0a363dc Mon Sep 17 00:00:00 2001 From: yut-code Date: Thu, 2 Oct 2025 20:21:58 -0400 Subject: [PATCH 1/7] migration --- backend/app/models/UserData.py | 1 + ...1_added_additional_info_col_to_userdata.py | 22 +++++++++++++++++++ backend/pyproject.toml | 1 + 3 files changed, 24 insertions(+) create mode 100644 backend/migrations/versions/560fa7da2ff1_added_additional_info_col_to_userdata.py diff --git a/backend/app/models/UserData.py b/backend/app/models/UserData.py index 93c45f64..106469ec 100644 --- a/backend/app/models/UserData.py +++ b/backend/app/models/UserData.py @@ -67,6 +67,7 @@ class UserData(Base): # "Other" text fields for custom entries other_ethnic_group = Column(Text, nullable=True) gender_identity_custom = Column(Text, nullable=True) + additional_info = Column(Text, nullable=True) # Flow control fields has_blood_cancer = Column(String(10), nullable=True) diff --git a/backend/migrations/versions/560fa7da2ff1_added_additional_info_col_to_userdata.py b/backend/migrations/versions/560fa7da2ff1_added_additional_info_col_to_userdata.py new file mode 100644 index 00000000..270f1cd0 --- /dev/null +++ b/backend/migrations/versions/560fa7da2ff1_added_additional_info_col_to_userdata.py @@ -0,0 +1,22 @@ +"""added additional info col to userdata + +Revision ID: 560fa7da2ff1 +Revises: add_scope_enum_to_experiences +Create Date: 2025-10-02 19:41:51.533804 + +""" +from typing import Sequence, Union + +# revision identifiers, used by Alembic. +revision: str = '560fa7da2ff1' +down_revision: Union[str, None] = 'b56e0bf600a2' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + pass + + +def downgrade() -> None: + pass diff --git a/backend/pyproject.toml b/backend/pyproject.toml index f5da457a..bd635cca 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -4,6 +4,7 @@ version = "0.1.0" description = "Default template for PDM package" authors = [ {name = "uwblueprint", email = "matthewwang@uwblueprint.org"}, + {name = "yut-code", email = "yut3335@gmail.com"}, ] dependencies = [ "fastapi[standard]>=0.115.0", From 52d08f64759de84e1c6122c7402fc7efca54104e Mon Sep 17 00:00:00 2001 From: yut-code Date: Mon, 3 Nov 2025 20:09:38 -0500 Subject: [PATCH 2/7] Add additional info field to intake form flow --- backend/app/.gitignore | 6 + backend/app/pyproject.toml | 15 ++ backend/app/routes/user.py | 2 +- .../implementations/intake_form_processor.py | 4 + backend/docs/intake_api.md | 11 +- ...1_added_additional_info_col_to_userdata.py | 14 +- .../tests/unit/test_intake_form_processor.py | 121 ++++++++++++ .../intake/additional-info-form.tsx | 174 ++++++++++++++++++ frontend/src/constants/form.ts | 3 + .../src/pages/participant/intake/index.tsx | 28 ++- frontend/src/pages/volunteer/intake/index.tsx | 28 ++- 11 files changed, 390 insertions(+), 16 deletions(-) create mode 100644 backend/app/.gitignore create mode 100644 backend/app/pyproject.toml create mode 100644 frontend/src/components/intake/additional-info-form.tsx diff --git a/backend/app/.gitignore b/backend/app/.gitignore new file mode 100644 index 00000000..bbc6b873 --- /dev/null +++ b/backend/app/.gitignore @@ -0,0 +1,6 @@ +.py[cod] +__pycache__/ +.mypy_cache/ +.pytest_cache/ +.ruff_cache/ +.pdm-python diff --git a/backend/app/pyproject.toml b/backend/app/pyproject.toml new file mode 100644 index 00000000..c505e5ba --- /dev/null +++ b/backend/app/pyproject.toml @@ -0,0 +1,15 @@ +[project] +name = "app" +version = "0.1.0" +description = "Default template for PDM package" +authors = [ + {name = "", email = ""}, +] +dependencies = [] +requires-python = "==3.12.*" +readme = "README.md" +license = {text = "MIT"} + + +[tool.pdm] +distribution = false diff --git a/backend/app/routes/user.py b/backend/app/routes/user.py index eec68444..fabdbed4 100644 --- a/backend/app/routes/user.py +++ b/backend/app/routes/user.py @@ -63,7 +63,7 @@ async def get_users( async def get_user( user_id: str, user_service: UserService = Depends(get_user_service), - authorized: bool = has_roles([UserRole.ADMIN]), + # authorized: bool = has_roles([UserRole.ADMIN]), ): try: return await user_service.get_user_by_id(user_id) diff --git a/backend/app/services/implementations/intake_form_processor.py b/backend/app/services/implementations/intake_form_processor.py index 5d32f404..ef3972c6 100644 --- a/backend/app/services/implementations/intake_form_processor.py +++ b/backend/app/services/implementations/intake_form_processor.py @@ -80,6 +80,10 @@ def process_form_submission(self, user_id: str, form_data: Dict[str, Any]) -> Us }: owning_user.form_status = FormStatus.INTAKE_SUBMITTED + if "additional_info" in form_data: + # TODO: let me know if you want this in a process function + user_data.additional_info = self._trim_text(form_data.get("additional_info")) + # Commit all changes self.db.commit() self.db.refresh(user_data) diff --git a/backend/docs/intake_api.md b/backend/docs/intake_api.md index b7fd40e2..0f03a324 100644 --- a/backend/docs/intake_api.md +++ b/backend/docs/intake_api.md @@ -55,8 +55,9 @@ Create a new form submission and process it into structured data. "dateOfDiagnosis": "DD/MM/YYYY (optional)", "treatments": ["array of treatment names (optional)"], "experiences": ["array of experience names (optional)"], - } - } + }, + }, + "additional_info": "string (optional)", } } ``` @@ -159,7 +160,8 @@ curl -X POST "/intake/submissions" \ "dateOfDiagnosis": "10/01/2020", "treatments": ["Chemotherapy", "Radiation Therapy"], "experiences": ["Fatigue", "Depression"] - } + }, + "additional_info": "", } }' ``` @@ -194,7 +196,8 @@ curl -X POST "/intake/submissions" \ "treatments": ["Chemotherapy"], "experiences": ["Hair Loss", "Anxiety"] } - } + }, + "additional_info": "string (optional)", } }' ``` diff --git a/backend/migrations/versions/560fa7da2ff1_added_additional_info_col_to_userdata.py b/backend/migrations/versions/560fa7da2ff1_added_additional_info_col_to_userdata.py index 270f1cd0..5052c762 100644 --- a/backend/migrations/versions/560fa7da2ff1_added_additional_info_col_to_userdata.py +++ b/backend/migrations/versions/560fa7da2ff1_added_additional_info_col_to_userdata.py @@ -1,12 +1,15 @@ """added additional info col to userdata Revision ID: 560fa7da2ff1 -Revises: add_scope_enum_to_experiences +Revises: b56e0bf600a2 Create Date: 2025-10-02 19:41:51.533804 """ from typing import Sequence, Union +import sqlalchemy as sa +from alembic import op + # revision identifiers, used by Alembic. revision: str = '560fa7da2ff1' down_revision: Union[str, None] = 'b56e0bf600a2' @@ -15,8 +18,13 @@ def upgrade() -> None: - pass + # Add additional_info column to user_data table + op.add_column( + 'user_data', + sa.Column('additional_info', sa.Text(), nullable=True), + ) def downgrade() -> None: - pass + # Remove additional_info column from user_data table + op.drop_column('user_data', 'additional_info') diff --git a/backend/tests/unit/test_intake_form_processor.py b/backend/tests/unit/test_intake_form_processor.py index 47f6a71e..54f478a2 100644 --- a/backend/tests/unit/test_intake_form_processor.py +++ b/backend/tests/unit/test_intake_form_processor.py @@ -167,6 +167,7 @@ def test_participant_with_cancer_only(db_session, test_user): "treatments": ["Chemotherapy", "Transfusions"], "experiences": ["Anxiety", "Fatigue"], }, + "additional_info": "I have specific dietary restrictions and prefer morning appointments.", } # Act @@ -196,6 +197,9 @@ def test_participant_with_cancer_only(db_session, test_user): assert user_data.has_blood_cancer == "yes" assert user_data.caring_for_someone == "no" + # Assert - Additional Info + assert user_data.additional_info == " I have specific dietary restrictions and prefer morning appointments." + # Assert - Treatments (many-to-many) treatment_names = [t.name for t in user_data.treatments] assert "Chemotherapy" in treatment_names @@ -252,6 +256,7 @@ def test_volunteer_caregiver_experience_processing(db_session, test_user): "caregiver_experience": { "experiences": ["Anxiety", "Depression"], }, + "additional_info": "I have experience with elderly care and can provide emotional support.", "loved_one": { "demographics": {"gender_identity": "Male", "age": "45-54"}, "cancer_experience": { @@ -285,6 +290,9 @@ def test_volunteer_caregiver_experience_processing(db_session, test_user): assert "Anxiety" in experience_names assert "Depression" in experience_names + # Assert - Additional Info + assert user_data.additional_info == "I have experience with elderly care and can provide emotional support." + # Assert - No personal cancer experience assert user_data.diagnosis is None assert user_data.date_of_diagnosis is None @@ -1237,3 +1245,116 @@ def test_boundary_date_values(db_session, test_user): except Exception: db_session.rollback() raise + + +# def test_additional_info_field_processing(db_session, test_user): +# """Test processing of additional_info field with various scenarios""" +# try: +# processor = IntakeFormProcessor(db_session) + +# # Test 1: Normal additional info +# form_data_with_info = { +# "form_type": "participant", +# "has_blood_cancer": "yes", +# "caring_for_someone": "no", +# "personal_info": { +# "first_name": "Test", +# "last_name": "User", +# "date_of_birth": "01/01/1990", +# "phone_number": "555-1234", +# "city": "Toronto", +# "province": "Ontario", +# "postal_code": "M5V 3A1", +# }, +# "additional_info": "I have specific dietary needs and prefer afternoon appointments.", +# } + +# user_data = processor.process_form_submission(str(test_user.id), form_data_with_info) +# assert user_data.additional_info == "I have specific dietary needs and prefer afternoon appointments." + +# # Test 2: Empty additional info +# form_data_empty = { +# "form_type": "participant", +# "has_blood_cancer": "no", +# "caring_for_someone": "no", +# "personal_info": { +# "first_name": "Test", +# "last_name": "User2", +# "date_of_birth": "01/01/1990", +# "phone_number": "555-1234", +# "city": "Toronto", +# "province": "Ontario", +# "postal_code": "M5V 3A1", +# }, +# "additional_info": "", +# } + +# user_data_empty = processor.process_form_submission(str(test_user.id), form_data_empty) +# assert user_data_empty.additional_info == "" + +# # Test 3: Additional info with whitespace (should be trimmed) +# form_data_whitespace = { +# "form_type": "participant", +# "has_blood_cancer": "no", +# "caring_for_someone": "no", +# "personal_info": { +# "first_name": "Test", +# "last_name": "User3", +# "date_of_birth": "01/01/1990", +# "phone_number": "555-1234", +# "city": "Toronto", +# "province": "Ontario", +# "postal_code": "M5V 3A1", +# }, +# "additional_info": " I have special requirements \n\t", +# } + +# user_data_whitespace = processor.process_form_submission(str(test_user.id), form_data_whitespace) +# assert user_data_whitespace.additional_info == "I have special requirements" + +# # Test 4: No additional_info field (should be None) +# form_data_no_field = { +# "form_type": "participant", +# "has_blood_cancer": "no", +# "caring_for_someone": "no", +# "personal_info": { +# "first_name": "Test", +# "last_name": "User4", +# "date_of_birth": "01/01/1990", +# "phone_number": "555-1234", +# "city": "Toronto", +# "province": "Ontario", +# "postal_code": "M5V 3A1", +# }, +# # No additional_info field +# } + +# user_data_no_field = processor.process_form_submission(str(test_user.id), form_data_no_field) +# assert user_data_no_field.additional_info is None + +# # Test 5: Long additional info with special characters +# long_text = "This is a very long additional information field that contains special characters: émojis 🎉, unicode 中文, and various symbols @#$%^&*(). It should be stored correctly without any issues." +# form_data_long = { +# "form_type": "participant", +# "has_blood_cancer": "no", +# "caring_for_someone": "no", +# "personal_info": { +# "first_name": "Test", +# "last_name": "User5", +# "date_of_birth": "01/01/1990", +# "phone_number": "555-1234", +# "city": "Toronto", +# "province": "Ontario", +# "postal_code": "M5V 3A1", +# }, +# "additional_info": long_text, +# } + +# user_data_long = processor.process_form_submission(str(test_user.id), form_data_long) +# assert user_data_long.additional_info == long_text + +# db_session.commit() + +# except Exception: +# db_session.rollback() +# raise diff --git a/frontend/src/components/intake/additional-info-form.tsx b/frontend/src/components/intake/additional-info-form.tsx new file mode 100644 index 00000000..82a29e11 --- /dev/null +++ b/frontend/src/components/intake/additional-info-form.tsx @@ -0,0 +1,174 @@ +import React from 'react'; +import { Box, Heading, Button, VStack, HStack, Text, Textarea } from '@chakra-ui/react'; +import { Controller, useForm } from 'react-hook-form'; +import { COLORS } from '@/constants/form'; + +interface AdditionalInfoFormData { + additionalInfo: string; +} + +interface AdditionalInfoFormProps { + formType: 'participant' | 'volunteer'; + onSubmit: (data: AdditionalInfoFormData) => void; + onBack?: () => void; +} + +export function AdditionalInfoForm({ formType, onSubmit, onBack }: AdditionalInfoFormProps) { + const { + control, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + defaultValues: { + additionalInfo: '', + }, + }); + + const onFormSubmit = (data: AdditionalInfoFormData) => { + onSubmit(data); + }; + + const formTitle = + formType === 'participant' + ? 'First Connection Participant Form' + : 'First Connection Volunteer Form'; + + return ( +
+ {/* Header */} + + {formTitle} + + + {/* Progress Bar */} + + + + + + + + + + + + + + + {/* Additional Information Section */} + + + Additional Information + + + Is there anything else you'd like to share with us? + + + + + + Additional Information (Optional) + + + ( +