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..41adeb5e5 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_all_caregivers() + 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,17 @@ 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 diff --git a/backend/python/app/rest/child_routes.py b/backend/python/app/rest/child_routes.py index 16d89a436..c4569cc24 100644 --- a/backend/python/app/rest/child_routes.py +++ b/backend/python/app/rest/child_routes.py @@ -1,19 +1,175 @@ import json +import sys from flask import Blueprint, current_app, jsonify, request from ..middlewares.auth import require_authorization_by_role from ..middlewares.validate import validate_request from ..resources.child_dto import ChildDTO, CreateChildDTO +from ..resources.daytime_contact_dto import CreateDaytimeContactDTO, DaytimeContactDTO from ..services.implementations.child_service import ChildService +from ..services.implementations.daytime_contact_service import DaytimeContactService +from ..services.implementations.provider_service import ProviderService child_service = ChildService(current_app.logger) +provider_service = ProviderService(current_app.logger) +daytimeContact_service = DaytimeContactService(current_app.logger) blueprint = Blueprint("child", __name__, url_prefix="/children") -@blueprint.route("/", methods=["POST"], strict_slashes=False) -@require_authorization_by_role({"Admin"}) -@validate_request("ChildDTO") -def create_child(): - pass +@blueprint.route("/", methods=["GET"], strict_slashes=False) +# @require_authorization_by_role({"Admin"}) +def get_child(): + try: + args = request.args + intake_id = args.get("intake_id") + just_children = child_service.get_children_by_intake_id(intake_id) + new_children = [] + for child in just_children: + providers = provider_service.get_providers_by_child_id(child.id) + child_info = { + "childName": child.first_name, + "dateOfBirth": child.date_of_birth, + "cpinFileNumber": child.cpin_number, + "workerName": child.service_worker, + "specialNeeds": child.special_needs, + "concerns": [], + "childId": child.id, + } + + # need to get by child_id instead + daytime_contact = daytimeContact_service.get_daytime_contact_by_intake_id( + intake_id + ) + + provider_list = [] + for provider in providers: + provider_list.append( + { + "providerName": provider.name, + "providerFileNo": provider.file_number, + "primaryPhoneNo": provider.primary_phone_number, + "secondaryPhoneNo": provider.secondary_phone_number, + "email": provider.email, + "address": provider.address, + "relationship": provider.relationship_to_child, + "contactNotes": provider.additional_contact_notes, + "status": "previous", + } + ) + + new_child = { + "childDetails": child_info, + "schoolDetails": daytime_contact, + "providers": provider_list, + } + + new_children.append(new_child) + + return jsonify(list(new_children)), 200 + + except Exception as error: + return jsonify(error), 400 + + +@blueprint.route("/", methods=["POST"], strict_slashes=False) +# @require_authorization_by_role({"Admin"}) +# @validate_request("ChildDTO") +def create_child(intake_id): + undos = [] + + def run_undos(): + for undo in undos: + service, fn, arg = undo + service.__dict__[fn](arg) + + child_details = request.json["child_details"] + daytimeContact_details = request.json["school_details"] + providers = request.json["providers"] + + daytimeContact_obj = { + "name": daytimeContact_details["school_name"], + "address": daytimeContact_details["school_address"], + "contact_information": daytimeContact_details["school_phone_no"], + "dismissal_time": daytimeContact_details["dismissal_time"], + } + + try: + daytime_response = daytimeContact_service.create_new_daytime_contact( + CreateDaytimeContactDTO(**daytimeContact_obj) + ) + print(daytime_response, file=sys.stderr) + undos.append((daytime_response, "delete_daytime_contact", daytime_response.id)) + except Exception as error: + run_undos() + return jsonify(error), 400 + + child_obj = { + "first_name": child_details["child_name"], + "last_name": ".", + "intake_id": intake_id, + "date_of_birth": child_details["date_of_birth"], + "cpin_number": child_details["cpin_file_number"], + "service_worker": child_details["worker_name"], + "special_needs": child_details["special_needs"], + "daytime_contact_id": daytime_response.id, + } + + try: + child_response = child_service.add_new_child(CreateChildDTO(**child_obj)) + print(child_response, file=sys.stderr) + undos.append((child_service, "delete_child", child_response.id)) + except Exception as error: + run_undos() + return jsonify(error), 400 + + return jsonify(child_response.__dict__), 201 + + +@blueprint.route("/", methods=["PUT"], strict_slashes=False) +# @require_authorization_by_role({"Admin"}) +# @validate_request("ChildDTO") +def edit_child(intake_id): + undos = [] + + def run_undos(): + for undo in undos: + service, fn, arg = undo + service.__dict__[fn](arg) + + child_details = request.json["child_details"] + daytimeContact_details = request.json["school_details"] + providers = request.json["providers"] + + child_obj = { + "first_name": child_details["child_name"], + "last_name": ".", + "date_of_birth": child_details["date_of_birth"], + "cpin_number": child_details["cpin_file_number"], + "service_worker": child_details["worker_name"], + "special_needs": child_details["special_needs"], + } + + try: + child_response = child_service.edit_child(child_obj, child_details["child_id"]) + # print(child_response, file=sys.stderr) + # undos.append((child_service, "delete_child", child_response.id)) + except Exception as error: + # run_undos() + return jsonify(error), 400 + + # daytimeContact_obj ={ + # "name": daytimeContact_details["school_name"], + # "contact_information": daytimeContact_details["school_phone_no"], + # "address": daytimeContact_details["school_address"], + # "dismissal_time": daytimeContact_details["dismissal_time"], + # } + + # try: + # daytime_response = daytimeContact_service.edit_daytime_contact(daytimeContact_obj, daytimeContact_details["school_id"]) + # except Exception as error: + # # run_undos() + # return jsonify(error),400 + + return jsonify(child_response.__dict__), 200 diff --git a/backend/python/app/rest/testbad.html b/backend/python/app/rest/testbad.html new file mode 100644 index 000000000..f885c26d0 --- /dev/null +++ b/backend/python/app/rest/testbad.html @@ -0,0 +1 @@ +"(psycopg2.errors.InvalidTextRepresentation) invalid input value for enum intakes_court_status: \"i am not a valid court status\"\nLINE 1: ...1-01', 'Family Name', '1234321', 'INVESTIGATION', 'i am not ...\n ^\n\n[SQL: INSERT INTO intakes (user_id, intake_status, referring_worker_name, referring_worker_contact, referral_date, family_name, cpin_number, cpin_file_type, court_status, court_order_file, first_nation_heritage, first_nation_band, transportation_requirements, scheduling_requirements, suggested_start_date, date_accepted, access_location, lead_access_worker_id, denial_reason) VALUES (%(user_id)s, %(intake_status)s, %(referring_worker_name)s, %(referring_worker_contact)s, %(referral_date)s, %(family_name)s, %(cpin_number)s, %(cpin_file_type)s, %(court_status)s, %(court_order_file)s, %(first_nation_heritage)s, %(first_nation_band)s, %(transportation_requirements)s, %(scheduling_requirements)s, %(suggested_start_date)s, %(date_accepted)s, %(access_location)s, %(lead_access_worker_id)s, %(denial_reason)s) RETURNING intakes.id]\n[parameters: {'user_id': 1, 'intake_status': 'SUBMITTED', 'referring_worker_name': 'Referring Worker', 'referring_worker_contact': 'unused', 'referral_date': '2019-01-01', 'family_name': 'Family Name', 'cpin_number': '1234321', 'cpin_file_type': 'INVESTIGATION', 'court_status': 'i am not a valid court status', 'court_order_file': 'file binary', 'first_nation_heritage': 'i am also invalid', 'first_nation_band': 'first nation band', 'transportation_requirements': 'transport requirements', 'scheduling_requirements': 'scheduling requirements', 'suggested_start_date': '2019-01-01', 'date_accepted': None, 'access_location': None, 'lead_access_worker_id': None, 'denial_reason': None}]\n(Background on this error at: http://sqlalche.me/e/13/9h9h)" diff --git a/backend/python/app/rest/testbad.json b/backend/python/app/rest/testbad.json new file mode 100644 index 000000000..960ce2985 --- /dev/null +++ b/backend/python/app/rest/testbad.json @@ -0,0 +1,73 @@ +{ + "userId": 1, + "caseReferral": { + "referringWorker": "Referring Worker", + "referringWorkerContact": "unused", + "cpinFileNumber": "1234321", + "cpinFileType": "INVESTIGATION", + "familyName": "Family Name", + "referralDate": "2019-01-01" + }, + "courtInformation": { + "courtStatus": "i am not a valid court status", + "orderReferral": "file binary", + "firstNationHeritage": "i am also invalid", + "firstNationBand": "first nation band" + }, + "children": [ + { + "childInfo": { + "name": "Child Name", + "dateOfBirth": "2019-01-01", + "cpinFileNumber": "1234321", + "serviceWorker": "Service Worker", + "specialNeeds": "special needs", + "concerns": ["concern1", "concern2"] + }, + "daytimeContact": { + "name": "Daytime Contact", + "contactInfo": "contact info", + "address": "address", + "dismissalTime": "dismissal time" + }, + "provider": [ + { + "name": "Provider Name", + "fileNumber": "1234321", + "primaryPhoneNumber": "primary phone number", + "secondaryPhoneNumber": "secondary phone number", + "additionalContactNotes": "additional contact notes", + "relationshipToChild": "FOSTER_CAREGIVER" + } + ] + } + ], + "caregivers": [ + { + "name": "Caregiver Name", + "dateOfBirth": "2019-01-01", + "primaryPhoneNumber": "primary phone number", + "secondaryPhoneNumber": "secondary phone number", + "additionalContactNotes": "additional contact notes", + "address": "address", + "relationshipToChild": "FOSTER_CAREGIVER", + "individualConsiderations": "individual considerations" + } + ], + "programDetails": { + "transportRequirements": "transport requirements", + "schedulingRequirements": "scheduling requirements", + "suggestedStartDate": "2019-01-01", + "shortTermGoals": ["goal1", "goal2"], + "longTermGoals": ["goal3", "goal4"], + "familialConcerns": ["concern1", "concern2"], + "permittedIndividuals": [ + { + "name": "Permitted Individual Name", + "phoneNumber": "phone number", + "relationshipToChildren": "relationship to children", + "additionalNotes": "additional notes" + } + ] + } +} diff --git a/backend/python/app/rest/testbad.sh b/backend/python/app/rest/testbad.sh new file mode 100644 index 000000000..0a97a48a1 --- /dev/null +++ b/backend/python/app/rest/testbad.sh @@ -0,0 +1 @@ +curl -X POST -H "Content-Type: application/json" -d @testbad.json http://localhost:5000/intake > testbad.html diff --git a/backend/python/app/rest/testgood.html b/backend/python/app/rest/testgood.html new file mode 100644 index 000000000..a796230ad --- /dev/null +++ b/backend/python/app/rest/testgood.html @@ -0,0 +1,22 @@ +{ + "access_location": null, + "court_order_file": "file binary", + "court_status": "INTERIM_CARE", + "cpin_file_type": "INVESTIGATION", + "cpin_number": "1234321", + "date_accepted": null, + "denial_reason": null, + "family_name": "Family Name", + "first_nation_band": "first nation band", + "first_nation_heritage": "FIRST_NATION_REGISTERED", + "id": 8, + "intake_status": "SUBMITTED", + "lead_access_worker_id": null, + "referral_date": "Tue, 01 Jan 2019 00:00:00 GMT", + "referring_worker_contact": "unused", + "referring_worker_name": "Referring Worker", + "scheduling_requirements": "scheduling requirements", + "suggested_start_date": "Tue, 01 Jan 2019 00:00:00 GMT", + "transportation_requirements": "transport requirements", + "user_id": 1 +} diff --git a/backend/python/app/rest/testgood.json b/backend/python/app/rest/testgood.json new file mode 100644 index 000000000..3aa85e3ed --- /dev/null +++ b/backend/python/app/rest/testgood.json @@ -0,0 +1,77 @@ +{ + "userId": 1, + "caseReferral": { + "referringWorker": "Referring Worker", + "referringWorkerContact": "unused", + "cpinFileNumber": "1234321", + "cpinFileType": "INVESTIGATION", + "familyName": "Family Name", + "referralDate": "2019-01-01" + }, + "courtInformation": { + "courtStatus": "INTERIM_CARE", + "orderReferral": "file binary", + "firstNationHeritage": "FIRST_NATION_REGISTERED", + "firstNationBand": "first nation band" + }, + "children": [ + { + "childInfo": { + "first_name": "Child", + "last_name": "Child", + "dateOfBirth": "2019-01-01", + "cpinFileNumber": "1234321", + "serviceWorker": "Service Worker", + "specialNeeds": "special needs", + "concerns": ["concern1", "concern2"] + }, + "daytimeContact": { + "name": "Daytime Contact", + "contactInfo": "contact info", + "address": "address", + "dismissalTime": "dismissal time" + }, + "provider": [ + { + "name": "Provider Name", + "email": "test@test.com", + "address": "address", + "fileNumber": "1234321", + "primaryPhoneNumber": "primary phone number", + "secondaryPhoneNumber": "secondary phone number", + "additionalContactNotes": "additional contact notes", + "relationshipToChild": "FOSTER_CAREGIVER" + } + ] + } + ], + "caregivers": [ + { + "name": "Caregiver Name", + "dateOfBirth": "2019-01-01", + "email": "test@test.com", + "primaryPhoneNumber": "primary phone number", + "secondaryPhoneNumber": "secondary phone number", + "additionalContactNotes": "additional contact notes", + "address": "address", + "relationshipToChild": "FOSTER_CAREGIVER", + "individualConsiderations": "individual considerations" + } + ], + "programDetails": { + "transportRequirements": "transport requirements", + "schedulingRequirements": "scheduling requirements", + "suggestedStartDate": "2019-01-01", + "shortTermGoals": ["goal1", "goal2"], + "longTermGoals": ["goal3", "goal4"], + "familialConcerns": ["concern1", "concern2"], + "permittedIndividuals": [ + { + "name": "Permitted Individual Name", + "phoneNumber": "phone number", + "relationshipToChildren": "relationship to children", + "additionalNotes": "additional notes" + } + ] + } +} diff --git a/backend/python/app/rest/testgood.sh b/backend/python/app/rest/testgood.sh new file mode 100644 index 000000000..dea29aa61 --- /dev/null +++ b/backend/python/app/rest/testgood.sh @@ -0,0 +1 @@ +curl -X POST -H "Content-Type: application/json" -d @testgood.json http://localhost:5000/intake > testgood.html 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/implementations/child_service.py b/backend/python/app/services/implementations/child_service.py index ed48279b6..2ada7970d 100644 --- a/backend/python/app/services/implementations/child_service.py +++ b/backend/python/app/services/implementations/child_service.py @@ -51,6 +51,23 @@ def delete_child(self, child_id): db.session.rollback() raise error + def edit_child(self, child_data, child_id): + try: + child = Child.query.filter_by(id=child_id).first() + if not child: + raise Exception("Child with id {} not found".format(child_id)) + child.first_name = child_data["first_name"] + child.last_name = child_data["last_name"] + child.date_of_birth = child_data["date_of_birth"] + child.cpin_number = child_data["cpin_number"] + child.service_worker = child_data["service_worker"] + child.special_needs = child_data["special_needs"] + db.session.merge(child) + db.session.commit() + return ChildDTO(**child.to_dict()) + except Exception as error: + db.session.rollback() + def get_children_by_intake_id(self, intake_id): try: children = Child.query.filter_by(intake_id=intake_id) diff --git a/backend/python/app/services/implementations/daytime_contact_service.py b/backend/python/app/services/implementations/daytime_contact_service.py index afe8c230a..2cbe777b9 100644 --- a/backend/python/app/services/implementations/daytime_contact_service.py +++ b/backend/python/app/services/implementations/daytime_contact_service.py @@ -39,6 +39,23 @@ def create_new_daytime_contact(self, contact: CreateDaytimeContactDTO): db.session.rollback() raise error + def edit_daytime_contact(self, daytime_data, daytime_contact_id): + try: + daytime_contact = DaytimeContact.query.filter_by( + id=daytime_contact_id + ).first() + if not daytime_contact: + raise Exception("Child with id {} not found".format(daytime_contact_id)) + daytime_contact.name = daytime_data["name"] + daytime_contact.contact_information = daytime_data["contact_information"] + daytime_contact.address = daytime_data["address"] + daytime_contact.dismissal_time = daytime_data["dismissal_time"] + db.session.merge(daytime_contact) + db.session.commit() + return DaytimeContactDTO(**daytime_contact.to_dict()) + except Exception as error: + db.session.rollback() + def delete_daytime_contact(self, daytime_contact_id): try: if not daytime_contact_id: @@ -69,19 +86,21 @@ def get_daytime_contact_by_intake_id(self, intake_id): ).first() if daytime_contact: result = { - "name": daytime_contact.name, - "contactInfo": daytime_contact.contact_information, - "address": daytime_contact.address, + "schoolName": daytime_contact.name, + "schoolPhoneNo": daytime_contact.contact_information, + "schoolAddress": daytime_contact.address, "dismissalTime": daytime_contact.dismissal_time, + "schoolId": daytime_contact.id, } return result else: return ( { - "name": "", - "contactInfo": "", - "address": "", + "schoolName": "", + "schoolPhoneNo": "", + "schoolAddress": "", "dismissalTime": "", + "schoolID": "", }, ) except Exception as 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 68b7c7d96..466e20028 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..485eee1c3 --- /dev/null +++ b/frontend/src/APIClients/CaregiverAPIClient.ts @@ -0,0 +1,195 @@ +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/${caregiverId}`, { + headers: { Authorization: bearerToken }, + }); + return response.data; + } catch (error) { + return error; + } +}; + +export default { post, getById, put, deleteCaregiver }; diff --git a/frontend/src/APIClients/ChildAPIClient.ts b/frontend/src/APIClients/ChildAPIClient.ts new file mode 100644 index 000000000..39ee6ae47 --- /dev/null +++ b/frontend/src/APIClients/ChildAPIClient.ts @@ -0,0 +1,75 @@ +import baseAPIClient from "./BaseAPIClient"; +import AUTHENTICATED_USER_KEY from "../constants/AuthConstants"; +import { Children, ChildrenDetails } from "../types/ChildTypes"; +import { getLocalStorageObjProperty } from "../utils/LocalStorageUtils"; + +const post = async ({ + newChild, + intakeId, +}: { + newChild: ChildrenDetails; + intakeId: number; +}): Promise => { + const bearerToken = `Bearer ${getLocalStorageObjProperty( + AUTHENTICATED_USER_KEY, + "access_token", + )}`; + try { + const { data } = await baseAPIClient.post( + `/children/${intakeId}`, + newChild, + { + headers: { Authorization: bearerToken }, + }, + ); + return data; + } catch (error) { + return error; + } +}; + +const get = async (intakeId: number): Promise => { + const bearerToken = `Bearer ${getLocalStorageObjProperty( + AUTHENTICATED_USER_KEY, + "access_token", + )}`; + try { + const { data } = await baseAPIClient.get("/children", { + headers: { Authorization: bearerToken }, + params: { + intake_id: intakeId, + }, + }); + + return data; + } catch (error) { + return error; + } +}; + +const put = async ({ + updatedChild, + intakeId, +}: { + updatedChild: ChildrenDetails; + intakeId: number; +}): Promise => { + const bearerToken = `Bearer ${getLocalStorageObjProperty( + AUTHENTICATED_USER_KEY, + "access_token", + )}`; + try { + const { data } = await baseAPIClient.put( + `/children/${intakeId}`, + updatedChild, + { + headers: { Authorization: bearerToken }, + }, + ); + return data; + } catch (error) { + return error; + } +}; + +export default { post, put, get }; diff --git a/frontend/src/components/intake/IndividualDetails.tsx b/frontend/src/components/intake/IndividualDetails.tsx index dff35a50b..616cf6397 100644 --- a/frontend/src/components/intake/IndividualDetails.tsx +++ b/frontend/src/components/intake/IndividualDetails.tsx @@ -1,8 +1,8 @@ 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"; +import { Children } from "../../types/ChildTypes"; type IndividualDetailsProps = { childrenDetails: Children; diff --git a/frontend/src/components/intake/IndividualDetailsEntry.tsx b/frontend/src/components/intake/IndividualDetailsEntry.tsx index 3d6f19581..579256fdd 100644 --- a/frontend/src/components/intake/IndividualDetailsEntry.tsx +++ b/frontend/src/components/intake/IndividualDetailsEntry.tsx @@ -1,12 +1,12 @@ 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"; +import { Children } from "../../types/ChildTypes"; import ChildrenForm from "./indivDetails/ChildrenForm"; import CaregiverForm from "./indivDetails/CaregiverProviderForm"; -import { Children } from "./child-information/AddChildPage"; export type IndividualDetailsEntryProp = { nextStep: () => void; diff --git a/frontend/src/components/intake/NewCaregiverModal.tsx b/frontend/src/components/intake/NewCaregiverModal.tsx index 2f4d799c4..26ec1ed93 100644 --- a/frontend/src/components/intake/NewCaregiverModal.tsx +++ b/frontend/src/components/intake/NewCaregiverModal.tsx @@ -5,19 +5,9 @@ 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 = { isOpen: boolean; @@ -33,14 +23,28 @@ const NewCaregiverModal = ({ 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 +58,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 +78,21 @@ 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.ADOPTIVE_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, + }; + return ( - + ADDITIONAL CONTACT NOTES } value={ - relationshipChanged ? relationship : caregiver.relationship + relationshipChanged + ? RelationshipToChild[relationship] + : caregiver.relationship } defaultValue={caregiver ? caregiver.relationship : ""} setValue={setRelationship} @@ -231,8 +253,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 +274,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( + 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); + } handleClose(); }} isOpen={isOpen} diff --git a/frontend/src/components/intake/NewProviderModal.tsx b/frontend/src/components/intake/NewProviderModal.tsx index 74807fd08..ab30aaaf3 100644 --- a/frontend/src/components/intake/NewProviderModal.tsx +++ b/frontend/src/components/intake/NewProviderModal.tsx @@ -14,6 +14,7 @@ export type ProviderDetails = { contactNotes?: string; address: string; relationship: string; + status: string; }; export type Providers = ProviderDetails[]; @@ -234,6 +235,7 @@ const NewProviderModal = ({ relationship: relationshipChanged ? relationship : provider.relationship, + status: "", }; onClick(newProvider); handleClose(); diff --git a/frontend/src/components/intake/ReviewCaseForm.tsx b/frontend/src/components/intake/ReviewCaseForm.tsx index 394e9e038..8cd475f29 100644 --- a/frontend/src/components/intake/ReviewCaseForm.tsx +++ b/frontend/src/components/intake/ReviewCaseForm.tsx @@ -5,10 +5,10 @@ import IndividualDetails from "./IndividualDetails"; import ReferralForm, { ReferralDetails } from "./ReferralForm"; import CourtInformationForm, { CourtDetails } from "./CourtInformationForm"; import ProgramForm, { ProgramDetails } from "./ProgramForm"; +import { Children } from "../../types/ChildTypes"; 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/child-information/AddChildPage.tsx b/frontend/src/components/intake/child-information/AddChildPage.tsx index 79d941e78..5542fdedf 100644 --- a/frontend/src/components/intake/child-information/AddChildPage.tsx +++ b/frontend/src/components/intake/child-information/AddChildPage.tsx @@ -4,10 +4,13 @@ import { ArrowLeft } from "react-feather"; import IntakeHeader from "../IntakeHeader"; import IntakeSteps from "../intakeSteps"; import { Providers } from "../NewProviderModal"; +import OverviewSection from "../../../types/OverviewSection"; import ChildInformationForm, { ChildDetails } from "./ChildInformationForm"; import ChildProviderForm from "./ChildProviderForm"; import FormSelector from "./FormSelector"; import SchoolDaycareForm, { SchoolDetails } from "./SchoolDaycareForm"; +import childAPIClient from "../../../APIClients/ChildAPIClient"; +import { Children, ChildrenDetails } from "../../../types/ChildTypes"; enum AddChildSteps { CHILD_INFORMATION_FORM, @@ -17,20 +20,18 @@ enum AddChildSteps { type AddChildProps = { allProviders: Providers; - setAllProviders: React.Dispatch>; + setAllProviders: ( + newProviders: Providers, + ) => void | React.Dispatch>; setStep: React.Dispatch>; childrens: Children; - setChildren: React.Dispatch>; + setChildren: (newChildren: Children) => void; selectedIndexChild: number; + setSelectedIndexChild: React.Dispatch>; + referrer: string; + caseNumber?: number; }; -export type ChildrenDetails = { - childDetails: ChildDetails; - schoolDetails: SchoolDetails; - providers: Providers; -}; -export type Children = ChildrenDetails[]; - const AddChild = ({ allProviders, setAllProviders, @@ -38,6 +39,9 @@ const AddChild = ({ childrens, setChildren, selectedIndexChild, + setSelectedIndexChild, + referrer, + caseNumber, }: AddChildProps): React.ReactElement => { const [activeFormIndex, setActiveFormIndex] = useState(0); @@ -48,12 +52,14 @@ const AddChild = ({ workerName: "", specialNeeds: "", childBehaviours: "", + childId: "", }); const [schoolDetails, setSchoolDetails] = useState({ schoolName: "", schoolPhoneNo: "", schoolAddress: "", dismissalTime: "", + schoolId: "", }); const [providers, setProviders] = useState([]); @@ -61,24 +67,37 @@ const AddChild = ({ !childDetails.childName || !childDetails.cpinFileNumber || !childDetails.dateOfBirth; - // TODO: Check other required fields const childFormSubmitHandler = () => { - const updatedChild = { + const child: ChildrenDetails = { childDetails: { ...childDetails }, schoolDetails: { ...schoolDetails }, providers: [...providers], }; if (selectedIndexChild >= 0) { - childrens.splice(selectedIndexChild, 1, updatedChild); + childrens.splice(selectedIndexChild, 1, child); + if (caseNumber) { + childAPIClient.put({ + updatedChild: child, + intakeId: caseNumber, + }); + } } else { - childrens.push(updatedChild); + childrens.push(child); + if (caseNumber) { + childAPIClient.post({ newChild: child, intakeId: caseNumber }); + } } setChildren([...childrens]); - setStep(IntakeSteps.INDIVIDUAL_DETAILS); + if (referrer === "intake") { + setStep(IntakeSteps.INDIVIDUAL_DETAILS); + } else if (referrer === "caseOverview") { + setSelectedIndexChild(-1); + setStep(OverviewSection.MAIN_SECTION); + } }; useEffect(() => { @@ -86,6 +105,24 @@ const AddChild = ({ setChildDetails(childrens[selectedIndexChild].childDetails); setSchoolDetails(childrens[selectedIndexChild].schoolDetails); setProviders(childrens[selectedIndexChild].providers); + } else { + setChildDetails({ + childName: "", + cpinFileNumber: "", + dateOfBirth: "", + workerName: "", + specialNeeds: "", + childBehaviours: "", + childId: "", + }); + setSchoolDetails({ + schoolName: "", + schoolPhoneNo: "", + schoolAddress: "", + dismissalTime: "", + schoolId: "", + }); + setProviders([]); } }, [childrens, selectedIndexChild]); @@ -122,7 +159,7 @@ const AddChild = ({ return ( <> } onClick={() => { - setStep(IntakeSteps.INDIVIDUAL_DETAILS); + if (referrer === "intake") { + setStep(IntakeSteps.INDIVIDUAL_DETAILS); + } else if (referrer === "caseOverview") { + setSelectedIndexChild(-1); + setStep(OverviewSection.MAIN_SECTION); + } }} variant="tertiary" > diff --git a/frontend/src/components/intake/child-information/ChildInformationForm.tsx b/frontend/src/components/intake/child-information/ChildInformationForm.tsx index cff61a6c6..8a87546d5 100644 --- a/frontend/src/components/intake/child-information/ChildInformationForm.tsx +++ b/frontend/src/components/intake/child-information/ChildInformationForm.tsx @@ -18,6 +18,7 @@ export type ChildDetails = { workerName: string; specialNeeds: string; childBehaviours: string; + childId: string; }; type ChildFormProps = { diff --git a/frontend/src/components/intake/child-information/ChildProviderForm.tsx b/frontend/src/components/intake/child-information/ChildProviderForm.tsx index 2bd0b2ee6..72fe51a99 100644 --- a/frontend/src/components/intake/child-information/ChildProviderForm.tsx +++ b/frontend/src/components/intake/child-information/ChildProviderForm.tsx @@ -12,7 +12,9 @@ export type ChildProviderFormProps = { providers: Providers; setProviders: React.Dispatch>; allProviders: Providers; - setAllProviders: React.Dispatch>; + setAllProviders: ( + newProviders: Providers, + ) => void | React.Dispatch>; }; const ChildProviderForm = ({ @@ -36,11 +38,14 @@ const ChildProviderForm = ({ } = useDisclosure(); const onClickNewProvider = (newProvider: ProviderDetails) => { + const provider: ProviderDetails = newProvider; if (selectedIndex >= 0) { - providers.splice(selectedIndex, 1, newProvider); + provider.status = "edited"; + providers.splice(selectedIndex, 1, provider); } else { - providers.push(newProvider); - allProviders.push(newProvider); + provider.status = "created"; + providers.push(provider); + allProviders.push(provider); } setProviders(providers); setAllProviders(allProviders); @@ -78,6 +83,7 @@ const ChildProviderForm = ({ contactNotes: "", address: "", relationship: "", + status: "", }; return ( diff --git a/frontend/src/components/intake/child-information/SchoolDaycareForm.tsx b/frontend/src/components/intake/child-information/SchoolDaycareForm.tsx index b583be69a..7d81c8eee 100644 --- a/frontend/src/components/intake/child-information/SchoolDaycareForm.tsx +++ b/frontend/src/components/intake/child-information/SchoolDaycareForm.tsx @@ -16,6 +16,7 @@ export type SchoolDetails = { schoolPhoneNo: string; schoolAddress: string; dismissalTime: string; + schoolId: string; }; type SchoolDaycareFormProps = { diff --git a/frontend/src/components/intake/indivDetails/CaregiverProviderForm.tsx b/frontend/src/components/intake/indivDetails/CaregiverProviderForm.tsx index 5e492045e..f4944f27a 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: "", diff --git a/frontend/src/components/intake/indivDetails/ChildrenForm.tsx b/frontend/src/components/intake/indivDetails/ChildrenForm.tsx index c2162b81d..ce3c458d1 100644 --- a/frontend/src/components/intake/indivDetails/ChildrenForm.tsx +++ b/frontend/src/components/intake/indivDetails/ChildrenForm.tsx @@ -1,9 +1,9 @@ import React, { useState } from "react"; import { UserPlus } from "react-feather"; import { Icon } from "@chakra-ui/react"; -import { Children } from "../child-information/AddChildPage"; import PromptBox, { IndividualDetailsOverview } from "../PromptBox"; import IntakeSteps from "../intakeSteps"; +import { Children } from "../../../types/ChildTypes"; type ChildrenFormProps = { childrens: Children; diff --git a/frontend/src/components/overview/CasePromptBox.tsx b/frontend/src/components/overview/CasePromptBox.tsx new file mode 100644 index 000000000..0b12242d5 --- /dev/null +++ b/frontend/src/components/overview/CasePromptBox.tsx @@ -0,0 +1,101 @@ +import React, { ReactElement } from "react"; +import { Button, Divider, HStack, Icon, Text, VStack } from "@chakra-ui/react"; +import { ArrowRight } from "react-feather"; + +export type IndividualDetailsOverview = { + name: string; +}; + +export type CasePromptBoxProps = { + descriptionText: string; + buttonText: string; + buttonIcon?: ReactElement; + onButtonClick: () => void; + secondaryButtonText?: string; + secondaryButtonIcon?: ReactElement; + secondaryOnButtonClick?: () => void; + individualDetails?: IndividualDetailsOverview[]; + setSelectedIndex?: React.Dispatch>; + useSecondaryOnClick?: boolean; +}; + +const PromptBox = ({ + descriptionText, + buttonText, + buttonIcon, + onButtonClick, + secondaryOnButtonClick, + individualDetails, + setSelectedIndex, + useSecondaryOnClick, +}: CasePromptBoxProps): React.ReactElement => { + return ( + + + {individualDetails && individualDetails.length > 0 + ? individualDetails.map((indiv, i) => ( + + + + + {indiv.name} + + + + + + ); +}; + +export default PromptBox; diff --git a/frontend/src/components/pages/CaseOverview.tsx b/frontend/src/components/pages/CaseOverview.tsx index e1fa5dadc..c715dc14f 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, { useState, useEffect } from "react"; import { useHistory, useParams, useLocation } from "react-router-dom"; import { Box, Button, Flex, + Icon, Input, Text, useDisclosure, @@ -14,17 +15,66 @@ import CaseOverviewFooter from "../overview/CaseOverviewFooter"; import colors from "../../theme/colors"; import VisitCadenceModal from "../dashboard/VisitCadenceModal"; import intakeAPIClient from "../../APIClients/IntakeAPIClient"; +import AddChild from "../intake/child-information/AddChildPage"; +import { Providers } from "../intake/NewProviderModal"; +import childAPIClient from "../../APIClients/ChildAPIClient"; +import NewCaregiverModal from "../intake/NewCaregiverModal"; +import { Children } from "../../types/ChildTypes"; +import CaregiverAPIClient from "../../APIClients/CaregiverAPIClient"; +import { Caregivers, CaregiverDetails } from "../../types/CaregiverDetailTypes"; +import CasePromptBox, { + IndividualDetailsOverview, +} from "../overview/CasePromptBox"; +import OverviewSection from "../../types/OverviewSection"; -const CaseOverviewBody = (): React.ReactElement => { +interface CaseOverviewData { + caregiversList: Caregivers; + childrenList: Children; + providerList: Providers; +} + +type OverviewBodyProps = { + setSectionIndex: React.Dispatch>; + caseData: CaseOverviewData; + setCaseData: React.Dispatch>; + selectedIndex: number; + setSelectedIndex: React.Dispatch>; + caseNumber: number; +}; + +const CaseOverviewBody = ({ + setSectionIndex, + caseData, + setCaseData, + selectedIndex, + setSelectedIndex, + caseNumber, +}: OverviewBodyProps): React.ReactElement => { const history = useHistory(); - const { id } = useParams<{ id: string }>(); - const caseNumber: number = parseInt(id, 10); const { state } = useLocation<{ caseLead: string }>(); const { caseLead } = state; const [leadName, setLeadName] = useState(caseLead); + 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, @@ -39,6 +89,7 @@ const CaseOverviewBody = (): React.ReactElement => { const goToHomepage = () => { history.push("/"); }; + const changeLead = async () => { const intakeID = caseNumber; const changedData: Record = { @@ -52,6 +103,48 @@ const CaseOverviewBody = (): React.ReactElement => { } }; + const mapCaregiversToCaregiverDetailsOverview = ( + caregivers: Caregivers, + ): IndividualDetailsOverview[] => { + if (caregivers.length > 0) { + return caregivers.map((caregiver) => ({ + name: caregiver.caregiverName, + })); + } + return []; + }; + + const onClickNewCaregiver = (newCaregiver: CaregiverDetails) => { + const list = caseData.caregiversList; + if (selectedIndex >= 0) { + list.splice(selectedIndex, 1, newCaregiver); + setCaseData({ + ...caseData, + caregiversList: [...list], + }); + } else { + setCaseData({ + ...caseData, + caregiversList: [...list, newCaregiver], + }); + } + }; + + const mapChildrenToChildrenDetailsOverview = ( + childrens: Children, + ): IndividualDetailsOverview[] => { + if (childrens.length > 0) { + return childrens.map((child) => ({ + name: child.childDetails.childName, + })); + } + return []; + }; + + const onClickChildren = () => { + setSectionIndex(OverviewSection.CHILD_SECTION); + }; + return ( - - + } + onButtonClick={onClickChildren} + individualDetails={mapChildrenToChildrenDetailsOverview( + caseData.childrenList, + )} + setSelectedIndex={setSelectedIndex} + /> Visiting Family - - - There are no visiting families under this case - - - - + } + onButtonClick={onOpenAddCaregiver} + individualDetails={mapCaregiversToCaregiverDetailsOverview( + caseData.caregiversList, + )} + setSelectedIndex={setSelectedIndex} + /> @@ -332,28 +367,110 @@ const CaseOverviewBody = (): React.ReactElement => { goToIntake={goToIntake} childName="Anne Chovy" /> + = 0 + ? caseData.caregiversList[selectedIndex] + : dummyCaregiver + } + /> ); }; const CaseOverview = (): React.ReactElement => { + const [sectionIndex, setSectionIndex] = useState(0); + const { id } = useParams<{ id: string }>(); + const caseNumber: number = parseInt(id, 10); - return ( - - - -
- -
-
- -
- ); + const [selectedIndex, setSelectedIndex] = useState(-1); + + const [caseData, setCaseData] = useState({ + caregiversList: [], + childrenList: [], + providerList: [], + }); + + const setProviderList = (newProviders: Providers) => { + setCaseData({ ...caseData, providerList: newProviders }); + }; + + const setChildrenList = (newChildren: Children) => { + setCaseData({ ...caseData, childrenList: newChildren }); + }; + + useEffect(() => { + const fetchData = async () => { + const childrenData = await childAPIClient.get(caseNumber); + const caregivers = await CaregiverAPIClient.getById(caseNumber); + + const childProviders: Providers = []; + + for (let i = 0; i < childrenData.length; i += 1) { + for (let x = 0; x < childrenData[i].providers.length; x += 1) { + childProviders.push(childrenData[i].providers[x]); + } + } + + setCaseData((data: CaseOverviewData) => ({ + ...data, + caregiversList: caregivers, + childrenList: childrenData, + providerList: childProviders, + })); + }; + + fetchData(); + }, [caseNumber]); + + switch (sectionIndex) { + case OverviewSection.MAIN_SECTION: { + return ( + + + +
+ +
+
+ +
+ ); + } + case OverviewSection.CHILD_SECTION: { + return ( + + ); + } + default: { + return Error; + } + } }; export default CaseOverview; diff --git a/frontend/src/components/pages/IntakePage.tsx b/frontend/src/components/pages/IntakePage.tsx index 7737aab30..a8eda8b24 100644 --- a/frontend/src/components/pages/IntakePage.tsx +++ b/frontend/src/components/pages/IntakePage.tsx @@ -18,12 +18,13 @@ 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"; import UnsavedProgressModal from "../intake/UnsavedProgressModal"; -import AddChild, { Children } from "../intake/child-information/AddChildPage"; +import AddChild from "../intake/child-information/AddChildPage"; +import { Children } from "../../types/ChildTypes"; import IntakeFooter from "../intake/IntakeFormFooter"; import { Providers } from "../intake/NewProviderModal"; import { useStepValueContext } from "../../contexts/IntakeValueContext"; @@ -190,6 +191,8 @@ const Intake = (): React.ReactElement => { childrens={children} setChildren={setChildren} selectedIndexChild={selectedIndexChild} + setSelectedIndexChild={setSelectedIndexChild} + referrer="intake" /> ) : ( <> 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; diff --git a/frontend/src/types/ChildTypes.ts b/frontend/src/types/ChildTypes.ts new file mode 100644 index 000000000..7451a6d0c --- /dev/null +++ b/frontend/src/types/ChildTypes.ts @@ -0,0 +1,11 @@ +import { Providers } from "../components/intake/NewProviderModal"; +import { ChildDetails } from "../components/intake/child-information/ChildInformationForm"; +import { SchoolDetails } from "../components/intake/child-information/SchoolDaycareForm"; + +export type ChildrenDetails = { + childDetails: ChildDetails; + schoolDetails: SchoolDetails; + providers: Providers; +}; + +export type Children = ChildrenDetails[]; diff --git a/frontend/src/types/OverviewSection.ts b/frontend/src/types/OverviewSection.ts new file mode 100644 index 000000000..cd154ced6 --- /dev/null +++ b/frontend/src/types/OverviewSection.ts @@ -0,0 +1,6 @@ +enum OverviewSection { + MAIN_SECTION, + CHILD_SECTION, +} + +export default OverviewSection;