From 2985845fb3f71632581dde1e4eb8718052536556 Mon Sep 17 00:00:00 2001 From: Jenny Vong Date: Tue, 31 Oct 2023 10:14:51 -0400 Subject: [PATCH] Caregiver CaseOverview component --- backend/python/app/models/caregiver.py | 17 +- backend/python/app/rest/caregiver_routes.py | 58 +++++ .../implementations/caregiver_service.py | 47 +++++ .../services/interfaces/caregiver_service.py | 15 ++ ...a_add_relationship_to_child_enum_values.py | 84 ++++++++ .../functional/test_caregiver_service.py | 16 +- .../functional/test_visit_cadence_service.py | 2 +- backend/python/tools/db_seed.py | 4 +- frontend/src/APIClients/CaregiverAPIClient.ts | 198 ++++++++++++++++++ .../components/intake/IndividualDetails.tsx | 2 +- .../intake/IndividualDetailsEntry.tsx | 2 +- .../components/intake/NewCaregiverModal.tsx | 180 ++++++++++++---- .../src/components/intake/ReviewCaseForm.tsx | 2 +- .../indivDetails/CaregiverProviderForm.tsx | 11 +- .../src/components/overview/CasePromptBox.tsx | 118 +++++++++++ .../src/components/pages/CaseOverview.tsx | 158 ++++++++++---- frontend/src/components/pages/IntakePage.tsx | 2 +- frontend/src/theme/buttonStyles.tsx | 17 ++ frontend/src/types/CaregiverDetailTypes.ts | 15 ++ frontend/src/types/CaregiverRelationship.ts | 16 ++ 20 files changed, 862 insertions(+), 102 deletions(-) create mode 100644 backend/python/migrations/versions/2023-10-31_db7c2f31d3ea_add_relationship_to_child_enum_values.py create mode 100644 frontend/src/APIClients/CaregiverAPIClient.ts create mode 100644 frontend/src/components/overview/CasePromptBox.tsx create mode 100644 frontend/src/types/CaregiverDetailTypes.ts create mode 100644 frontend/src/types/CaregiverRelationship.ts diff --git a/backend/python/app/models/caregiver.py b/backend/python/app/models/caregiver.py index cd8de1e99..f5fe55b8a 100644 --- a/backend/python/app/models/caregiver.py +++ b/backend/python/app/models/caregiver.py @@ -2,9 +2,18 @@ from .base_mixin import BaseMixin relationship_to_child_enum = db.Enum( - "FOSTER_CAREGIVER", - "KINSHIP_CAREGIVER", - "BIOLOGICAL_FAMILY", + "ADOPTIVE_PARENT", + "FOSTER_PARENT", + "BIOLOGICAL_PARENT", + "STEP_PARENT", + "MATERNAL_GRANDPARENT", + "PATERNAL_GRANDPARENT", + "SIBLING", + "STEP_SIBLING", + "HALF_SIBLING", + "UNCLE/AUNT", + "OTHER_RELATIVE", + "OTHER", name="caregivers_relationship_to_child", ) @@ -18,7 +27,7 @@ class Caregiver(db.Model, BaseMixin): individual_considerations = db.Column(db.String, nullable=True) primary_phone_number = db.Column(db.String, nullable=False) secondary_phone_number = db.Column(db.String, nullable=True) - email = db.Column(db.String, nullable=False) + email = db.Column(db.String, nullable=True) address = db.Column(db.String, nullable=False) relationship_to_child = db.Column(relationship_to_child_enum, nullable=False) additional_contact_notes = db.Column(db.String, nullable=True) diff --git a/backend/python/app/rest/caregiver_routes.py b/backend/python/app/rest/caregiver_routes.py index 45efcfe75..b1499b489 100644 --- a/backend/python/app/rest/caregiver_routes.py +++ b/backend/python/app/rest/caregiver_routes.py @@ -21,6 +21,20 @@ def get_all_caregivers(): return jsonify(error), 400 +# get all caregivers by intake_id +@blueprint.route("/", methods=["GET"], strict_slashes=False) +# @require_authorization_by_role({"Admin"}) +def get_caregivers_by_intake_id(intake_id): + if intake_id: + if type(intake_id) is not int: + return jsonify({"error:" "intake_id query parameter must be an int"}) + try: + caregivers = caregiver_service.get_caregivers_by_intake_id(intake_id) + return jsonify(list(map(lambda user: user.__dict__, caregivers))), 200 + except Exception as error: + return jsonify(error), 400 + + # create a caregiver @blueprint.route("/", methods=["POST"], strict_slashes=False) # @require_authorization_by_role({"Admin"}) @@ -32,3 +46,47 @@ def create_caregiver(): return jsonify(new_caregiver.__dict__), 201 except Exception as error: return jsonify(str(error)), 400 + + +# update a caregiver +@blueprint.route("/", methods=["PUT"], strict_slashes=False) +def update_caregiver(caregiver_id): + try: + new_caregiver = request.json + updated_caregiver = caregiver_service.update_caregiver( + caregiver_id, new_caregiver + ) + return jsonify(updated_caregiver.__dict__), 200 + + except Exception as error: + return jsonify(str(error)), 400 + + +@blueprint.route("/", methods=["DELETE"], strict_slashes=False) +def delete_intake(): + """ + Delete intake by caretgiver_id specified through a query parameter + """ + caregiver_id = int(request.args.get("caregiver_id")) + + if caregiver_id: + if type(caregiver_id) is not int: + return ( + jsonify({"error": "caregiver_id query parameter must be an int"}), + 400, + ) + else: + try: + caregiver_service.delete_caregiver(caregiver_id) + return "caregiver deleted", 200 + except Exception as e: + error_message = getattr(e, "message", None) + return ( + jsonify({"error": (error_message if error_message else str(e))}), + 500, + ) + + return ( + jsonify({"error": "Must supply intake id as query parameter."}), + 400, + ) diff --git a/backend/python/app/services/implementations/caregiver_service.py b/backend/python/app/services/implementations/caregiver_service.py index 10cb34472..6bd1480f2 100644 --- a/backend/python/app/services/implementations/caregiver_service.py +++ b/backend/python/app/services/implementations/caregiver_service.py @@ -63,3 +63,50 @@ def get_caregivers_by_intake_id(self, intake_id): except Exception as error: self.logger.error(str(error)) raise error + + def update_caregiver(self, caregiver_id: int, updated_caregiver): + try: + if not caregiver_id: + raise Exception( + "Empty caregiver id passed to update_caregiver function" + ) + if not isinstance(caregiver_id, int): + raise Exception("Caregiver id passed is not of int type") + + caregiver = Caregiver.query.filter_by(id=caregiver_id).first() + if not caregiver: + raise Exception("Caregiver with id {} not found".format(caregiver_id)) + + if "name" in updated_caregiver: + caregiver.name = updated_caregiver["name"] + if "date_of_birth" in updated_caregiver: + caregiver.date_of_birth = updated_caregiver["date_of_birth"] + if "individual_considerations" in updated_caregiver: + caregiver.individual_considerations = updated_caregiver[ + "individual_considerations" + ] + if "primary_phone_number" in updated_caregiver: + caregiver.primary_phone_number = updated_caregiver[ + "primary_phone_number" + ] + if "secondary_phone_number" in updated_caregiver: + caregiver.secondary_phone_number = updated_caregiver[ + "secondary_phone_number" + ] + if "email" in updated_caregiver: + caregiver.email = updated_caregiver["email"] + if "address" in updated_caregiver: + caregiver.address = updated_caregiver["address"] + if "relationship_to_child" in updated_caregiver: + caregiver.relationship_to_child = updated_caregiver[ + "relationship_to_child" + ] + if "additional_contact_notes" in updated_caregiver: + caregiver.additional_contact_notes = updated_caregiver[ + "additional_contact_notes" + ] + db.session.commit() + return CaregiverDTO(**caregiver.to_dict()) + except Exception as error: + db.session.rollback() + raise error diff --git a/backend/python/app/services/interfaces/caregiver_service.py b/backend/python/app/services/interfaces/caregiver_service.py index 9cfb92121..09d7e312f 100644 --- a/backend/python/app/services/interfaces/caregiver_service.py +++ b/backend/python/app/services/interfaces/caregiver_service.py @@ -48,3 +48,18 @@ def get_caregivers_by_intake_id(self, intake_id): :raises Exception: if an error occurs in the database query """ + pass + + @abstractmethod + def update_caregiver(self, caregiver_id, updated_caregiver): + """Updates the Caregiver with new name, date of birth, individual considerations, + primary and/or secondary phone number, email, address, relationship to child, additional contact notes + :param Caregiver_id: the caregiver_id of the caregiver to be updated + :type caregiver_id: int + :param updated_caregiver: the new updated CaregiverDTO object + :type updated_caregiver: CaregiverDTO + :return: CaregiverDTO + :rtype: CaregiverDTO + :raises Exception: if caregiver_id or updated_caregiver is not valid or if an error occurs during update + """ + pass diff --git a/backend/python/migrations/versions/2023-10-31_db7c2f31d3ea_add_relationship_to_child_enum_values.py b/backend/python/migrations/versions/2023-10-31_db7c2f31d3ea_add_relationship_to_child_enum_values.py new file mode 100644 index 000000000..75a590d2f --- /dev/null +++ b/backend/python/migrations/versions/2023-10-31_db7c2f31d3ea_add_relationship_to_child_enum_values.py @@ -0,0 +1,84 @@ +"""add relationship_to_child enum values + +Revision ID: db7c2f31d3ea +Revises: 2e3a95429cdf +Create Date: 2023-10-31 11:43:49.965500 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "db7c2f31d3ea" +down_revision = "2e3a95429cdf" +branch_labels = None +depends_on = None + +# Enum 'type' for PostgreSQL +enum_name = "caregivers_relationship_to_child" +# Set temporary enum 'type' for PostgreSQL +tmp_enum_name = "tmp_" + enum_name + +# Options for Enum +old_options = ("FOSTER_CAREGIVER", "KINSHIP_CAREGIVER", "BIOLOGICAL_FAMILY") +new_options = sorted( + old_options + + ( + "ADOPTIVE_PARENT", + "FOSTER_PARENT", + "BIOLOGICAL_PARENT", + "STEP_PARENT", + "MATERNAL_GRANDPARENT", + "PATERNAL_GRANDPARENT", + "SIBLING", + "STEP_SIBLING", + "HALF_SIBLING", + "UNCLE/AUNT", + "OTHER_RELATIVE", + "OTHER", + ) +) + +# Create enum fields +old_type = sa.Enum(*old_options, name=enum_name) +new_type = sa.Enum(*new_options, name=enum_name) + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + # Rename current enum type to tmp_ + op.execute("ALTER TYPE " + enum_name + " RENAME TO " + tmp_enum_name) + # Create new enum type in db + new_type.create(op.get_bind()) + # Update column to use new enum type + op.execute( + "ALTER TABLE caregivers ALTER COLUMN relationship_to_child TYPE " + + enum_name + + " USING relationship_to_child::text::" + + enum_name + ) + # Drop old enum type + op.execute("DROP TYPE " + tmp_enum_name) + + op.alter_column("caregivers", "email", existing_type=sa.String(), nullable=True) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + # Rename enum type to tmp_ + op.execute("ALTER TYPE " + enum_name + " RENAME TO " + tmp_enum_name) + # Create enum type using old values + old_type.create(op.get_bind()) + # Set enum type as type for event_type column + op.execute( + "ALTER TABLE caregivers ALTER COLUMN relationship_to_child TYPE " + + enum_name + + " USING relationship_to_child::text::" + + enum_name + ) + # Drop temp enum type + op.execute("DROP TYPE " + tmp_enum_name) + + op.alter_column("caregivers", "email", existing_type=sa.String(), nullable=False) + # ### end Alembic commands ### diff --git a/backend/python/tests/functional/test_caregiver_service.py b/backend/python/tests/functional/test_caregiver_service.py index 58fc0d439..0ddc19be4 100644 --- a/backend/python/tests/functional/test_caregiver_service.py +++ b/backend/python/tests/functional/test_caregiver_service.py @@ -51,7 +51,7 @@ def caregiver_service(): "secondary_phone_number": "2345678901", "email": "test123@uwaterloo.ca", "address": "123 Fake Street", - "relationship_to_child": "FOSTER_CAREGIVER", + "relationship_to_child": "FOSTER_PARENT", "intake_id": 1, } @@ -87,7 +87,7 @@ def test_normal_case(self, caregiver_service): secondary_phone_number="2345678901", email="test123@uwaterloo.ca", address="1234 Lester Street", - relationship_to_child="FOSTER_CAREGIVER", + relationship_to_child="FOSTER_PARENT", intake_id=1, ) caregiver_instance = caregiver_service.create_caregiver(param) @@ -101,7 +101,7 @@ def test_nullable_false_case(self, caregiver_service): primary_phone_number="1234567890", email="test123@uwaterloo.ca", address="1234 Lester Street", - relationship_to_child="FOSTER_CAREGIVER", + relationship_to_child="FOSTER_PARENT", intake_id=1, ) caregiver_instance = caregiver_service.create_caregiver(param) @@ -116,7 +116,7 @@ def test_missing_field(self, caregiver_service): primary_phone_number="1234567890", email="test123@uwaterloo.ca", address="1234 Lester Street", - relationship_to_child="FOSTER_CAREGIVER", + relationship_to_child="FOSTER_PARENT", intake_id=1, ) with pytest.raises(Exception): @@ -131,7 +131,7 @@ def test_empty_input_string(self): secondary_phone_number="2345678901", email="test123@uwaterloo.ca", address="1234 Lester Street", - relationship_to_child="FOSTER_CAREGIVER", + relationship_to_child="FOSTER_PARENT", intake_id=1, ) with pytest.raises(Exception): @@ -150,3 +150,9 @@ def test_delete_success(self, caregiver_service): def test_delete_nonexistent_id_fail(self, caregiver_service): with pytest.raises(Exception): caregiver_service.delete_caregiver(999) + + +class GetCaregivers: + def tet_get_caregivers_by_intake_id(self, caregiver_service): + caregivers = caregiver_service.get_caregivers_by_intake_id(1) + assert len(caregivers) is 1 diff --git a/backend/python/tests/functional/test_visit_cadence_service.py b/backend/python/tests/functional/test_visit_cadence_service.py index 92831e66a..11953f2e3 100644 --- a/backend/python/tests/functional/test_visit_cadence_service.py +++ b/backend/python/tests/functional/test_visit_cadence_service.py @@ -55,7 +55,7 @@ "secondary_phone_number": "2345678901", "email": "test123@uwaterloo.ca", "address": "123 Fake Street", - "relationship_to_child": "FOSTER_CAREGIVER", + "relationship_to_child": "FOSTER_PARENT", "intake_id": 1, } DUMMY_CHILD_DATA = { diff --git a/backend/python/tools/db_seed.py b/backend/python/tools/db_seed.py index 09c5206a1..d25e8912f 100644 --- a/backend/python/tools/db_seed.py +++ b/backend/python/tools/db_seed.py @@ -94,8 +94,8 @@ def insert_test_data(): # Caregivers values = [ - ('Yor Forger', '1999-01-01', 'considerations', '555-555-5555', '777-777-7777', 'email@email.com', 'address', 'FOSTER_CAREGIVER', 'NULL', 1), - ('Loid Forger', '1999-01-01', 'considerations', '777-777-7777', '555-555-5555', 'email@email.com', 'address', 'FOSTER_CAREGIVER', 'NULL', 1) + ('Yor Forger', '1999-01-01', 'considerations', '555-555-5555', '777-777-7777', 'email@email.com', 'address', 'FOSTER_PARENT', 'NULL', 1), + ('Loid Forger', '1999-01-01', 'considerations', '777-777-7777', '555-555-5555', 'email@email.com', 'address', 'MATERNAL_GRANDPARENT', 'NULL', 1) ] for value in values: diff --git a/frontend/src/APIClients/CaregiverAPIClient.ts b/frontend/src/APIClients/CaregiverAPIClient.ts new file mode 100644 index 000000000..0199ef8ae --- /dev/null +++ b/frontend/src/APIClients/CaregiverAPIClient.ts @@ -0,0 +1,198 @@ +import baseAPIClient from "./BaseAPIClient"; +import AUTHENTICATED_USER_KEY from "../constants/AuthConstants"; +import { getLocalStorageObjProperty } from "../utils/LocalStorageUtils"; +import { Caregivers } from "../types/CaregiverDetailTypes"; + +interface Caregiver { + address: string; + date_of_birth: string; + email: string; + id: number; + intake_id: number; + name: string; + primary_phone_number: string; + relationship_to_child: string; + additional_contact_notes?: string; + individual_considerations?: string; + secondary_phone_number?: string; +} + +const post = async ( + intakeId: number, + caregiverName: string, + caregiverEmail: string, + dateOfBirth: string, + primaryPhoneNo: string, + relationship: string, + caregiverAddress: string, + secondaryPhoneNo?: string, + indivConsiderations?: string, + contactNotes?: string, +): Promise => { + const bearerToken = `Bearer ${getLocalStorageObjProperty( + AUTHENTICATED_USER_KEY, + "access_token", + )}`; + try { + let caregiverData = { + intake_id: intakeId, + name: caregiverName, + email: caregiverEmail, + date_of_birth: dateOfBirth, + primary_phone_number: primaryPhoneNo, + relationship_to_child: relationship, + address: caregiverAddress, + }; + if (indivConsiderations) { + const updatedData = { + ...caregiverData, + individual_considerations: indivConsiderations, + }; + caregiverData = updatedData; + } + if (secondaryPhoneNo) { + const updatedData = { + ...caregiverData, + secondary_phone_number: secondaryPhoneNo, + }; + caregiverData = updatedData; + } + if (contactNotes) { + const updatedData = { + ...caregiverData, + additional_contact_notes: contactNotes, + }; + caregiverData = updatedData; + } + const { data } = await baseAPIClient.post("/caregiver", caregiverData, { + headers: { Authorization: bearerToken }, + }); + return data; + } catch (error) { + return error; + } +}; + +const getById = async (intake_id: number): Promise => { + const bearerToken = `Bearer ${getLocalStorageObjProperty( + AUTHENTICATED_USER_KEY, + "access_token", + )}`; + try { + const { data } = await baseAPIClient.get( + `/caregiver/${intake_id}`, + { + headers: { Authorization: bearerToken }, + params: { + intake_id, + }, + }, + ); + + const mappedData: Caregivers = data.map((caregiver) => ({ + intakeId: caregiver.intake_id, + caregiverName: caregiver.name, + dateOfBirth: caregiver.date_of_birth, + primaryPhoneNo: caregiver.primary_phone_number, + email: caregiver.email, + secondaryPhoneNo: caregiver.secondary_phone_number + ? caregiver.secondary_phone_number + : undefined, + contactNotes: caregiver.additional_contact_notes + ? caregiver.additional_contact_notes + : "", + address: caregiver.address, + relationship: caregiver.relationship_to_child, + indivConsiderations: caregiver.individual_considerations + ? caregiver.individual_considerations + : "", + id: caregiver.id, + })); + + return mappedData; + } catch (error) { + return error; + } +}; + +const put = async ( + caregiverId: number, + intakeId: number, + caregiverName?: string, + caregiverEmail?: string, + dateOfBirth?: string, + primaryPhoneNo?: string, + relationship?: string, + caregiverAddress?: string, + secondaryPhoneNo?: string, + indivConsiderations?: string, + contactNotes?: string, +): Promise => { + const bearerToken = `Bearer ${getLocalStorageObjProperty( + AUTHENTICATED_USER_KEY, + "access_token", + )}`; + try { + let caregiverData = { + id: caregiverId, + intake_id: intakeId, + name: caregiverName, + email: caregiverEmail, + date_of_birth: dateOfBirth, + primary_phone_number: primaryPhoneNo, + relationship_to_child: relationship, + address: caregiverAddress, + }; + if (indivConsiderations) { + const updatedData = { + ...caregiverData, + individual_considerations: indivConsiderations, + }; + caregiverData = updatedData; + } + if (secondaryPhoneNo) { + const updatedData = { + ...caregiverData, + secondary_phone_number: secondaryPhoneNo, + }; + caregiverData = updatedData; + } + if (contactNotes) { + const updatedData = { + ...caregiverData, + additional_contact_notes: contactNotes, + }; + caregiverData = updatedData; + } + const { data } = await baseAPIClient.put( + `/caregiver/${caregiverId}`, + caregiverData, + { + headers: { Authorization: bearerToken }, + }, + ); + return data; + } catch (error) { + return error; + } +}; + +const deleteCaregiver = async (caregiverId: number): Promise => { + const bearerToken = `Bearer ${getLocalStorageObjProperty( + AUTHENTICATED_USER_KEY, + "access_token", + )}`; + try { + const response = await baseAPIClient.delete( + `/caregiver/?caregiver_id=${caregiverId}`, + { + headers: { Authorization: bearerToken }, + }, + ); + return response.data; + } catch (error) { + return error; + } +}; + +export default { post, getById, put, deleteCaregiver }; diff --git a/frontend/src/components/intake/IndividualDetails.tsx b/frontend/src/components/intake/IndividualDetails.tsx index dff35a50b..278abaac9 100644 --- a/frontend/src/components/intake/IndividualDetails.tsx +++ b/frontend/src/components/intake/IndividualDetails.tsx @@ -2,7 +2,7 @@ import React from "react"; import { Button, VStack, Text, HStack, Icon, Divider } from "@chakra-ui/react"; import { ArrowRight } from "react-feather"; import { Children } from "./child-information/AddChildPage"; -import { Caregivers } from "./NewCaregiverModal"; +import { Caregivers } from "../../types/CaregiverDetailTypes"; type IndividualDetailsProps = { childrenDetails: Children; diff --git a/frontend/src/components/intake/IndividualDetailsEntry.tsx b/frontend/src/components/intake/IndividualDetailsEntry.tsx index 3d6f19581..e03e320d6 100644 --- a/frontend/src/components/intake/IndividualDetailsEntry.tsx +++ b/frontend/src/components/intake/IndividualDetailsEntry.tsx @@ -1,6 +1,6 @@ import React from "react"; import { VStack } from "@chakra-ui/react"; -import { Caregivers } from "./NewCaregiverModal"; +import { Caregivers } from "../../types/CaregiverDetailTypes"; import Stepper from "./Stepper"; import IntakeSteps from "./intakeSteps"; import IntakeFooter from "./IntakeFormFooter"; diff --git a/frontend/src/components/intake/NewCaregiverModal.tsx b/frontend/src/components/intake/NewCaregiverModal.tsx index 2f4d799c4..49a05c266 100644 --- a/frontend/src/components/intake/NewCaregiverModal.tsx +++ b/frontend/src/components/intake/NewCaregiverModal.tsx @@ -5,21 +5,12 @@ import ModalComponent from "../common/ModalComponent"; import CustomInput from "../common/CustomInput"; import OptionalLabel from "./OptionalLabel"; import { CustomSelectNonFormik } from "./CustomSelectField"; - -export type CaregiverDetails = { - caregiverName: string; - dateOfBirth: string; - primaryPhoneNo: string; - secondaryPhoneNo?: string; - contactNotes?: string; - address: string; - relationship: string; - indivConsiderations?: string; -}; - -export type Caregivers = CaregiverDetails[]; +import CaregiverAPIClient from "../../APIClients/CaregiverAPIClient"; +import { CaregiverDetails } from "../../types/CaregiverDetailTypes"; +import CaregiverRelationship from "../../types/CaregiverRelationship"; type NewCaregiverProps = { + intakeId: number; isOpen: boolean; onClick: (newCaregiver: CaregiverDetails) => void; onClose: () => void; @@ -27,20 +18,35 @@ type NewCaregiverProps = { }; const NewCaregiverModal = ({ + intakeId, isOpen, onClick, onClose, caregiver, }: NewCaregiverProps): React.ReactElement => { // ideally refactor to have less complicated state logic - const [caregiverName, setCaregiverName] = useState(""); - const [dateOfBirth, setDateOfBirth] = useState(""); - const [primaryPhoneNo, setPrimaryPhoneNo] = useState(""); - const [secondaryPhoneNo, setSecondaryPhoneNo] = useState(""); - const [contactNotes, setContactNotes] = useState(""); - const [address, setAddress] = useState(""); - const [relationship, setRelationship] = useState(""); - const [indivConsiderations, setIndivConsiderations] = useState(""); + const [caregiverName, setCaregiverName] = useState( + caregiver ? caregiver.caregiverName : "", + ); + const [dateOfBirth, setDateOfBirth] = useState( + caregiver ? caregiver.dateOfBirth : "", + ); + const [primaryPhoneNo, setPrimaryPhoneNo] = useState( + caregiver ? caregiver.primaryPhoneNo : "", + ); + const [secondaryPhoneNo, setSecondaryPhoneNo] = useState( + caregiver ? caregiver.secondaryPhoneNo : "", + ); + const [contactNotes, setContactNotes] = useState( + caregiver ? caregiver.contactNotes : "", + ); + const [address, setAddress] = useState(caregiver ? caregiver.address : ""); + const [relationship, setRelationship] = useState< + CaregiverRelationship | string + >(caregiver ? caregiver.relationship : ""); + const [indivConsiderations, setIndivConsiderations] = useState( + caregiver ? caregiver.indivConsiderations : "", + ); const [caregiverNameChanged, setCaregiverNameChanged] = useState(false); const [dateOfBirthChanged, setDateOfBirthChanged] = useState(false); @@ -54,14 +60,14 @@ const NewCaregiverModal = ({ ); const handleClose = () => { - setCaregiverName(""); - setDateOfBirth(""); - setPrimaryPhoneNo(""); - setSecondaryPhoneNo(""); - setContactNotes(""); - setAddress(""); - setRelationship(""); - setIndivConsiderations(""); + setCaregiverName(caregiver ? caregiver.caregiverName : ""); + setDateOfBirth(caregiver ? caregiver.dateOfBirth : ""); + setPrimaryPhoneNo(caregiver ? caregiver.primaryPhoneNo : ""); + setSecondaryPhoneNo(caregiver ? caregiver.secondaryPhoneNo : ""); + setContactNotes(caregiver ? caregiver.contactNotes : ""); + setAddress(caregiver ? caregiver.address : ""); + setRelationship(caregiver ? caregiver.relationship : ""); + setIndivConsiderations(caregiver ? caregiver.indivConsiderations : ""); setCaregiverNameChanged(false); setDateOfBirthChanged(false); @@ -74,6 +80,32 @@ const NewCaregiverModal = ({ onClose(); }; + const RelationshipToChild: Record = { + Other: CaregiverRelationship.OTHER, + Sibling: CaregiverRelationship.SIBLING, + "Biological Parent": CaregiverRelationship.BIOLOGICAL_PARENT, + "Adoptive Parent": CaregiverRelationship.ADOPTIVE_PARENT, + "Foster Parent": CaregiverRelationship.FOSTER_PARENT, + "Step-Parent": CaregiverRelationship.STEP_PARENT, + "Maternal Grandparent": CaregiverRelationship.MATERNAL_GRANDPARENT, + "Paternal Grandparent": CaregiverRelationship.PATERNAL_GRANDPARENT, + "Step-Sibling": CaregiverRelationship.STEP_SIBLING, + "Half-Sibling": CaregiverRelationship.HALF_SIBLING, + "Uncle/Aunt": CaregiverRelationship.UNCLE_AUNT, + "Other Relative": CaregiverRelationship.OTHER_RELATIVE, + }; + + const formattedDate = (date: string): string => { + const inputDate = new Date(date); + inputDate.setDate(inputDate.getDate() + 1); + const newDate = inputDate.toLocaleDateString("en-CA", { + year: "numeric", + month: "2-digit", + day: "2-digit", + }); + return newDate; + }; + return ( } onChange={(event) => { setDateOfBirth(event.target.value); @@ -147,7 +181,7 @@ const NewCaregiverModal = ({ - + ADDITIONAL CONTACT NOTES } value={ - relationshipChanged ? relationship : caregiver.relationship + relationshipChanged && relationship !== undefined + ? RelationshipToChild[relationship] + : caregiver?.relationship } defaultValue={caregiver ? caregiver.relationship : ""} setValue={setRelationship} @@ -231,8 +268,10 @@ const NewCaregiverModal = ({ } - onClick={() => { + onClick={async () => { const newCaregiver: CaregiverDetails = { + intakeId: caregiver.intakeId, + email: caregiver.email ? caregiver.email : "email@email.com", caregiverName: caregiverNameChanged ? caregiverName : caregiver.caregiverName, @@ -250,13 +289,68 @@ const NewCaregiverModal = ({ : caregiver.contactNotes, address: addressChanged ? address : caregiver.address, relationship: relationshipChanged - ? relationship + ? RelationshipToChild[relationship] : caregiver.relationship, indivConsiderations: indivConsiderationsChanged ? indivConsiderations : caregiver.indivConsiderations, }; - onClick(newCaregiver); + if (caregiver.id) { + const madeCaregiver = await CaregiverAPIClient.put( + caregiver.id, + newCaregiver.intakeId, + newCaregiver.caregiverName, + newCaregiver.email, + newCaregiver.dateOfBirth, + newCaregiver.primaryPhoneNo, + newCaregiver.relationship, + newCaregiver.address, + newCaregiver.secondaryPhoneNo, + newCaregiver.indivConsiderations, + newCaregiver.contactNotes, + ); + const madeCaregiverDetails = { + id: madeCaregiver.id, + caregiverName: madeCaregiver.name, + intakeId: madeCaregiver.intake_id, + email: madeCaregiver.email, + dateOfBirth: madeCaregiver.date_of_birth, + primaryPhoneNo: madeCaregiver.primary_phone_number, + relationship: madeCaregiver.relationship_to_child, + address: madeCaregiver.address, + secondaryPhoneNo: madeCaregiver.secondary_phone_number, + indivConsiderations: madeCaregiver.individual_considerations, + contactNotes: madeCaregiver.additional_contact_notes, + }; + onClick(madeCaregiverDetails); + } else { + const madeCaregiver = await CaregiverAPIClient.post( + intakeId, + newCaregiver.caregiverName, + newCaregiver.email, + formattedDate(newCaregiver.dateOfBirth), + newCaregiver.primaryPhoneNo, + newCaregiver.relationship, + newCaregiver.address, + newCaregiver.secondaryPhoneNo, + newCaregiver.indivConsiderations, + newCaregiver.contactNotes, + ); + const madeCaregiverDetails = { + id: madeCaregiver.id, + caregiverName: madeCaregiver.name, + intakeId: madeCaregiver.intake_id, + email: madeCaregiver.email, + dateOfBirth: formattedDate(madeCaregiver.date_of_birth), + primaryPhoneNo: madeCaregiver.primary_phone_number, + relationship: madeCaregiver.relationship_to_child, + address: madeCaregiver.address, + secondaryPhoneNo: madeCaregiver.secondary_phone_number, + indivConsiderations: madeCaregiver.individual_considerations, + contactNotes: madeCaregiver.additional_contact_notes, + }; + onClick(madeCaregiverDetails); + } handleClose(); }} isOpen={isOpen} diff --git a/frontend/src/components/intake/ReviewCaseForm.tsx b/frontend/src/components/intake/ReviewCaseForm.tsx index 394e9e038..038211ea4 100644 --- a/frontend/src/components/intake/ReviewCaseForm.tsx +++ b/frontend/src/components/intake/ReviewCaseForm.tsx @@ -8,7 +8,7 @@ import ProgramForm, { ProgramDetails } from "./ProgramForm"; import IntakeSteps from "./intakeSteps"; import IntakeFooter from "./IntakeFormFooter"; import { Children } from "./child-information/AddChildPage"; -import { Caregivers } from "./NewCaregiverModal"; +import { Caregivers } from "../../types/CaregiverDetailTypes"; type ReviewFormProps = { referralDetails: ReferralDetails; diff --git a/frontend/src/components/intake/indivDetails/CaregiverProviderForm.tsx b/frontend/src/components/intake/indivDetails/CaregiverProviderForm.tsx index 5e492045e..212610ac5 100644 --- a/frontend/src/components/intake/indivDetails/CaregiverProviderForm.tsx +++ b/frontend/src/components/intake/indivDetails/CaregiverProviderForm.tsx @@ -1,11 +1,12 @@ import { useDisclosure, Icon } from "@chakra-ui/react"; import React, { useState } from "react"; import { UserPlus } from "react-feather"; -import NewCaregiverModal, { +import NewCaregiverModal from "../NewCaregiverModal"; +import PromptBox, { IndividualDetailsOverview } from "../PromptBox"; +import { Caregivers, CaregiverDetails, -} from "../NewCaregiverModal"; -import PromptBox, { IndividualDetailsOverview } from "../PromptBox"; +} from "../../../types/CaregiverDetailTypes"; type CaregiverFormProps = { caregivers: Caregivers; @@ -39,7 +40,6 @@ const CaregiverForm = ({ // ideally should have something useEffect, but current way of passing data does not work well with it setCaregiversDeleted(caregiversDeleted + 1); setCaregivers(caregivers); - setSelectedIndex(-1); }; const caregiverDetailsOverview: IndividualDetailsOverview[] = caregivers.map( @@ -53,7 +53,9 @@ const CaregiverForm = ({ ); const emptyCaregiver: CaregiverDetails = { + intakeId: 1, caregiverName: "", + email: "", dateOfBirth: "", primaryPhoneNo: "", secondaryPhoneNo: "", @@ -76,6 +78,7 @@ const CaregiverForm = ({ setSelectedIndex={setSelectedIndex} /> void; + secondaryButtonText?: string; + secondaryButtonIcon?: ReactElement; + secondaryOnButtonClick?: () => void; + individualDetails?: IndividualDetailsOverview[]; + setSelectedIndex?: React.Dispatch>; + useSecondaryOnClick?: boolean; + deleteIndividual?: (index: number, deleteId: number) => void; +}; + +const PromptBox = ({ + descriptionText, + buttonText, + buttonIcon, + onButtonClick, + secondaryOnButtonClick, + individualDetails, + setSelectedIndex, + useSecondaryOnClick, + deleteIndividual, +}: CasePromptBoxProps): React.ReactElement => { + return ( + + + {individualDetails && individualDetails.length > 0 + ? individualDetails.map((indiv, i) => ( + + + + + {indiv.name} + { + if ( + deleteIndividual !== undefined && + indiv.id !== undefined + ) { + deleteIndividual(i, indiv.id); + } + }} + as={Trash} + h="16px" + color="grey.600" + cursor="pointer" + /> + + + + + + ); +}; + +export default PromptBox; diff --git a/frontend/src/components/pages/CaseOverview.tsx b/frontend/src/components/pages/CaseOverview.tsx index 3d113450a..70b83213f 100644 --- a/frontend/src/components/pages/CaseOverview.tsx +++ b/frontend/src/components/pages/CaseOverview.tsx @@ -1,9 +1,10 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { useHistory, useParams, useLocation } from "react-router-dom"; import { Box, Button, Flex, + Icon, Input, Text, useDisclosure, @@ -14,6 +15,12 @@ import CaseOverviewFooter from "../overview/CaseOverviewFooter"; import colors from "../../theme/colors"; import VisitCadenceModal from "../dashboard/VisitCadenceModal"; import intakeAPIClient from "../../APIClients/IntakeAPIClient"; +import NewCaregiverModal from "../intake/NewCaregiverModal"; +import CaregiverAPIClient from "../../APIClients/CaregiverAPIClient"; +import { Caregivers, CaregiverDetails } from "../../types/CaregiverDetailTypes"; +import CasePromptBox, { + IndividualDetailsOverview, +} from "../overview/CasePromptBox"; const CaseOverviewBody = (): React.ReactElement => { const history = useHistory(); @@ -27,6 +34,35 @@ const CaseOverviewBody = (): React.ReactElement => { referringWorker, ); + const [selectedIndex, setSelectedIndex] = useState(-1); + + interface StateType { + caregiversList: Caregivers; + caregiversDetailsOverview: IndividualDetailsOverview[]; + } + const [data, setData] = useState({ + caregiversList: [], + caregiversDetailsOverview: [], + }); + + const dummyCaregiver = { + intakeId: 1, + caregiverName: "", + dateOfBirth: "", + email: "", + primaryPhoneNo: "", + secondaryPhoneNo: "", + contactNotes: "", + address: "", + relationship: "", + indivConsiderations: "", + }; + const { + onOpen: onOpenAddCaregiver, + isOpen: isOpenNewCaregiverModal, + onClose: onCloseNewCaregiverModal, + } = useDisclosure(); + const { onOpen: onOpenVisitCadenceModal, isOpen: isOpenVisitCadenceModal, @@ -54,6 +90,68 @@ const CaseOverviewBody = (): React.ReactElement => { } }; + const mapCaregiversToCaregiverDetailsOverview = ( + caregivers: Caregivers, + ): IndividualDetailsOverview[] => { + if (caregivers.length > 0) { + return caregivers.map((caregiver) => ({ + name: caregiver.caregiverName, + id: caregiver.id, + })); + } + return []; + }; + + useEffect(() => { + const fetchData = async () => { + const caregivers = await CaregiverAPIClient.getById(caseNumber); + + setData({ + caregiversList: caregivers, + caregiversDetailsOverview: mapCaregiversToCaregiverDetailsOverview( + caregivers, + ), + }); + }; + + fetchData(); + }, [caseNumber]); + + const onClickNewCaregiver = (newCaregiver: CaregiverDetails) => { + const list = data.caregiversList; + if (selectedIndex >= 0) { + list.splice(selectedIndex, 1, newCaregiver); + setData({ + caregiversList: [...list], + caregiversDetailsOverview: mapCaregiversToCaregiverDetailsOverview([ + ...list, + ]), + }); + } else { + setData({ + caregiversList: [...list, newCaregiver], + caregiversDetailsOverview: mapCaregiversToCaregiverDetailsOverview([ + ...list, + newCaregiver, + ]), + }); + } + }; + + const deleteCaregiver = async (index: number, deleteId: number) => { + const caregiverList = [...data.caregiversList]; + caregiverList.splice(index, 1); + + setData({ + caregiversList: [...caregiverList], + caregiversDetailsOverview: mapCaregiversToCaregiverDetailsOverview([ + ...caregiverList, + ]), + }); + + await CaregiverAPIClient.deleteCaregiver(deleteId); + }; + return ( - + } + onButtonClick={onOpenAddCaregiver} + individualDetails={data.caregiversDetailsOverview} + setSelectedIndex={setSelectedIndex} + deleteIndividual={deleteCaregiver} + /> @@ -334,6 +403,17 @@ const CaseOverviewBody = (): React.ReactElement => { goToIntake={goToIntake} childName="Anne Chovy" /> + = 0 + ? data.caregiversList[selectedIndex] + : dummyCaregiver + } + /> ); }; diff --git a/frontend/src/components/pages/IntakePage.tsx b/frontend/src/components/pages/IntakePage.tsx index 7737aab30..6848c612f 100644 --- a/frontend/src/components/pages/IntakePage.tsx +++ b/frontend/src/components/pages/IntakePage.tsx @@ -18,7 +18,7 @@ import IntakeHeader from "../intake/IntakeHeader"; import ProgramForm from "../intake/ProgramForm"; import ReviewForm from "../intake/ReviewCaseForm"; import IndividualDetailsEntry from "../intake/IndividualDetailsEntry"; -import { Caregivers } from "../intake/NewCaregiverModal"; +import { Caregivers } from "../../types/CaregiverDetailTypes"; import IntakeSteps from "../intake/intakeSteps"; import { PermittedIndividuals } from "../intake/PermittedIndividualsModal"; import PermittedIndividualsForm from "../intake/PermittedIndividualsForm"; diff --git a/frontend/src/theme/buttonStyles.tsx b/frontend/src/theme/buttonStyles.tsx index 0534e02f4..77eaf6eb6 100644 --- a/frontend/src/theme/buttonStyles.tsx +++ b/frontend/src/theme/buttonStyles.tsx @@ -78,6 +78,23 @@ const Button: ComponentStyleConfig = { "0px 0px 16px rgba(255, 0, 0, 0.1), 0px 0px 16px rgba(102, 0, 0, 0.2)", }, }, + caseoverview: { + background: "blue.100", + border: "1px solid #00287D", + borderRadius: "8px", + color: "blue.400", + _hover: { + background: "gray.100", + }, + _active: { + background: "#DCE1FF", + boxShadow: "box-shadow: inset 0px 0px 32px rgba(0, 40, 125, 0.2)", + }, + _disabled: { + opacity: "0.5", + pointerEvents: "none", + }, + }, }, defaultProps: { variant: "primary", diff --git a/frontend/src/types/CaregiverDetailTypes.ts b/frontend/src/types/CaregiverDetailTypes.ts new file mode 100644 index 000000000..0d971244b --- /dev/null +++ b/frontend/src/types/CaregiverDetailTypes.ts @@ -0,0 +1,15 @@ +export type CaregiverDetails = { + id?: number; + intakeId: number; + caregiverName: string; + dateOfBirth: string; + primaryPhoneNo: string; + email: string; + secondaryPhoneNo?: string; + contactNotes?: string; + address: string; + relationship: string; + indivConsiderations?: string; +}; + +export type Caregivers = CaregiverDetails[]; diff --git a/frontend/src/types/CaregiverRelationship.ts b/frontend/src/types/CaregiverRelationship.ts new file mode 100644 index 000000000..f81129545 --- /dev/null +++ b/frontend/src/types/CaregiverRelationship.ts @@ -0,0 +1,16 @@ +enum CaregiverRelationship { + BIOLOGICAL_PARENT = "BIOLOGICAL_PARENT", + ADOPTIVE_PARENT = "ADOPTIVE_PARENT", + FOSTER_PARENT = "FOSTER_PARENT", + STEP_PARENT = "STEP_PARENT", + MATERNAL_GRANDPARENT = "MATERNAL_GRANDPARENT", + PATERNAL_GRANDPARENT = "PATERNAL_GRANDPARENT", + SIBLING = "SIBLING", + HALF_SIBLING = "HALF_SIBLING", + STEP_SIBLING = "STEP_SIBLING", + UNCLE_AUNT = "UNCLE_AUNT", + OTHER_RELATIVE = "OTHER_RELATIVE", + OTHER = "OTHER", +} + +export default CaregiverRelationship;