diff --git a/backend/python/app/resources/visit_dto.py b/backend/python/app/resources/visit_dto.py index 23e780901..58a4d5ee4 100644 --- a/backend/python/app/resources/visit_dto.py +++ b/backend/python/app/resources/visit_dto.py @@ -1,12 +1,26 @@ class VisitDTO(object): def __init__(self, **kwargs): self.user_id = kwargs.get("user_id") - self.childInformation = kwargs.get("childInformation") - self.visitTimestamp = kwargs.get("visitTimestamp") - self.attendance = kwargs.get("attendance") - self.transportation = kwargs.get("transportation") - self.notes = kwargs.get("notes") - self.childAndFamilySupportWorker = kwargs.get("childAndFamilySupportWorker") + self.case_id = kwargs.get("case_id") + self.child_details = kwargs.get("child_details") + self.visit_details = kwargs.get("visit_details") + self.attendance = kwargs.get("attendance_entries") + self.transportation = kwargs.get("transportation_entries") + self.notes = kwargs.get("visit_notes") + + +class CreateVisitDTO(VisitDTO): + def __init__(self, **kwargs): + super().__init__(**kwargs) def validate(self): - pass + error_list = [] + + if not self.user_id or not isinstance(self.user_id, int): + error_list.append("user_id is invalid") + if not self.child_details: + error_list.append("childInformation is invalid") + if not self.attendance: + error_list.append("attendance is invalid") + + return error_list diff --git a/backend/python/app/rest/visit_routes.py b/backend/python/app/rest/visit_routes.py index a5915421d..78e1f781a 100644 --- a/backend/python/app/rest/visit_routes.py +++ b/backend/python/app/rest/visit_routes.py @@ -2,9 +2,7 @@ from flask import Blueprint, current_app, jsonify, request -from ..middlewares.auth import require_authorization_by_role -from ..middlewares.validate import validate_request -from ..resources.visit_dto import VisitDTO +from ..resources.visit_dto import CreateVisitDTO from ..services.implementations.visit_service import VisitService # define instance of VisitService @@ -15,7 +13,37 @@ @blueprint.route("/", methods=["POST"], strict_slashes=False) -@require_authorization_by_role({"User", "Admin"}) -@validate_request("VisitDTO") def create_visit(): - pass + data = request.json + + visit_data = { + "user_id": int(data.get("user_id")), + "case_id": data.get("case_id"), + "child_details": data.get("child_details"), + "visit_details": data.get("visit_details"), + "attendance_entries": data.get("attendance_entries"), + "transportation_entries": data.get("transportation_entries"), + "visit_notes": data.get("visit_notes"), + } + + visit = CreateVisitDTO(**visit_data) + errors = visit.validate() + if errors: + return {"error": errors}, 400 + try: + visit_service.create_visit(visit) + return jsonify({"message": "Visit created successfully"}), 201 + except Exception as e: + return jsonify({"error": str(e)}), 500 + + +@blueprint.route("/", methods=["GET"], strict_slashes=False) +def get_visit_by_user_id(user_id): + result = visit_service.get_visit_by_user_id(user_id) + merged_dict = {} + for dto in result: + merged_dict.update(dto.__dict__) + return ( + jsonify(merged_dict), + 200, + ) diff --git a/backend/python/app/services/implementations/visit_service.py b/backend/python/app/services/implementations/visit_service.py index a8f14e0e7..5d47ee7b0 100644 --- a/backend/python/app/services/implementations/visit_service.py +++ b/backend/python/app/services/implementations/visit_service.py @@ -1,10 +1,95 @@ from ...models import db from ..interfaces.visit_service import IVisitService +from ...resources.visit_dto import VisitDTO +from ...resources.attendance_sheet_dto import AttendanceSheetDTO +from ...resources.attendance_records_dto import AttendanceRecordsDTO +from ...models.attendance_sheets import AttendanceSheets +from ...models.attendance_records import AttendanceRecords +from ...models.attendance_records import VisitingMember +from ...models.attendance_records import Transportation +from ...models.transportation_method import TransportationMethod class VisitService(IVisitService): def __init__(self, logger): self.logger = logger - def create_visit(): - pass + def create_visit(self, visit: VisitDTO): + try: + attendance_sheet = AttendanceSheets( + family_name=visit.child_details["family_name"], + csw=visit.child_details["child_service_worker"], + cpw=visit.child_details["child_protection_worker"], + fcc=visit.child_details["foster_care_coordinator"], + ) + db.session.add(attendance_sheet) + db.session.flush() + + attendance_record = AttendanceRecords( + attendance_sheet_id=attendance_sheet.id, + visit_date=visit.visit_details["visit_date"], + visit_day=visit.visit_details["visit_day"], + visit_supervision=visit.visit_details["visit_supervision"].upper(), + start_time=visit.visit_details["start_time"], + end_time=visit.visit_details["end_time"], + location=visit.visit_details["location"], + notes=visit.notes, + ) + db.session.add(attendance_record) + db.session.flush() + + for visiting_member in visit.attendance["entries"]: + member = VisitingMember( + attendance_record_id=attendance_record.id, + visitor_relationship=visiting_member["visitor_relationship"], + visiting_member_name=visiting_member["visiting_member_name"], + description=visiting_member["description"], + visit_attendance=visiting_member["visit_attendance"], + reason_for_absence=visiting_member["absence_reason"], + ) + db.session.add(member) + + for transport in visit.transportation["entries"]: + transportation = Transportation( + attendance_record_id=attendance_record.id, + guardian=transport["guardian"], + name=transport["name"], + duration=transport["duration"], + ) + db.session.add(transportation) + + # TODO: Add a reference key to transportation method for the visit + # transportation_entry = visit.transportation["entries"][0] + # transportation_method_name = transportation_entry["name"] + # attendance_record.notes += ( + # f" Transportation Method: {transportation_method_name}" + # ) + + db.session.commit() + + return {"message": "Visit created successfully"} + except Exception as error: + db.session.rollback() + self.logger.error(f"Error creating visit: {error}") + raise error + + def get_visit_by_user_id(self, userID): + try: + attendance_sheets = AttendanceSheets.query.filter_by(id=userID) + attendance_sheets_dto = [ + AttendanceSheetDTO(**attendance_sheet.to_dict()) + for attendance_sheet in attendance_sheets + ] + + attendance_records = AttendanceRecords.query.filter_by( + attendance_sheet_id=userID + ) + attendance_records_dto = [ + AttendanceRecordsDTO(**attendance_record.to_dict()) + for attendance_record in attendance_records + ] + + return attendance_sheets_dto + attendance_records_dto + except Exception as error: + self.logger.error(str(error)) + raise error diff --git a/backend/python/app/services/interfaces/visit_service.py b/backend/python/app/services/interfaces/visit_service.py index 04c0c7a0e..f082f1d0f 100644 --- a/backend/python/app/services/interfaces/visit_service.py +++ b/backend/python/app/services/interfaces/visit_service.py @@ -2,6 +2,11 @@ class IVisitService(ABC): + """ + A class to handle CRUD functionality for visits + """ + @abstractmethod - def create_visit(self): + def create_visit(self, intake): + # TODO: Create docstrings pass diff --git a/backend/python/tools/db_seed.py b/backend/python/tools/db_seed.py index 357e2e483..fd1a1b980 100644 --- a/backend/python/tools/db_seed.py +++ b/backend/python/tools/db_seed.py @@ -121,7 +121,7 @@ def insert_test_data(): insert_values(db, "providers", ("name", "file_number", "primary_phone_number", "secondary_phone_number", "email", "address", "relationship_to_child", "additional_contact_notes", "child_id"), value) # Attendance Sheets - values = [(1, 'Zhang', 'csw', 'cpw', 'fcc'), (2, 2, 'Wang', 'a', 'b', 'c')] + values = [(1, 'Zhang', 'csw', 'cpw', 'fcc'), (2, 'Wang', 'a', 'b', 'c')] for value in values: insert_values(db, "attendance_sheets", ("intake_id", "family_name", "csw", "cpw", "fcc"), value) diff --git a/frontend/src/APIClients/VisitAPIClient.ts b/frontend/src/APIClients/VisitAPIClient.ts new file mode 100644 index 000000000..fcc8164ed --- /dev/null +++ b/frontend/src/APIClients/VisitAPIClient.ts @@ -0,0 +1,81 @@ +import baseAPIClient from "./BaseAPIClient"; +import AUTHENTICATED_USER_KEY from "../constants/AuthConstants"; +import { getLocalStorageObjProperty } from "../utils/LocalStorageUtils"; +import { Case } from "../types/CasesContextTypes"; + +interface Visit { + user_id: number; + case_id: number; + childDetails: { + familyName: string; + children: string[]; + childServiceWorker: string; + childProtectionWorker: string; + fosterCareCoordinator: string; + }; + visitDetails: { + visitDate: string; + visitDay: string; + visitSupervision: string; + startTime: string; + endTime: string; + location: string; + }; + attendanceEntries: { + visitingMembers: string; + visitorRelationship: string; + description: string; + visitingMemberName: string; + visitAttendance: string; + absenceReason: string; + }[]; + transportationEntries: { + guardian: string; + name: string; + duration: string; + notes: string; + }[]; +} + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any +const post = async (formData: any): Promise => { + const bearerToken = `Bearer ${getLocalStorageObjProperty( + AUTHENTICATED_USER_KEY, + "access_token", + )}`; + try { + const { data } = await baseAPIClient.post("/visits", formData, { + headers: { Authorization: bearerToken }, + }); + return data; + } catch (error) { + return error; + } +}; + +const put = async ({ + changedData, + intakeID, +}: { + changedData: Record; + intakeID: number; +}): Promise => { + const bearerToken = `Bearer ${getLocalStorageObjProperty( + AUTHENTICATED_USER_KEY, + "access_token", + )}`; + try { + const { data } = await baseAPIClient.put( + `/visit/${intakeID}`, + changedData, + { + headers: { Authorization: bearerToken }, + }, + ); + return data; + } catch (error) { + return error; + } +}; + +export default { post, put }; \ No newline at end of file diff --git a/frontend/src/components/pages/CaseOverview.tsx b/frontend/src/components/pages/CaseOverview.tsx index 46a9803f1..61368186c 100644 --- a/frontend/src/components/pages/CaseOverview.tsx +++ b/frontend/src/components/pages/CaseOverview.tsx @@ -73,6 +73,10 @@ const CaseOverviewBody = (): React.ReactElement => { history.push("/intake"); }; + const goToVisitation = () => { + history.push(`/visit/${caseNumber}`) + } + const goToHomepage = () => { history.push("/"); }; @@ -382,6 +386,7 @@ const CaseOverviewBody = (): React.ReactElement => { right="4" borderColor={colors.blue[300]} backgroundColor={colors.blue[100]} + onClick={goToVisitation} >
diff --git a/frontend/src/components/pages/VisitPage.tsx b/frontend/src/components/pages/VisitPage.tsx index 1dc11dcaa..612239f01 100644 --- a/frontend/src/components/pages/VisitPage.tsx +++ b/frontend/src/components/pages/VisitPage.tsx @@ -1,4 +1,5 @@ import React, { useState } from "react"; +import {useParams, useHistory} from 'react-router-dom' import { Box, Button, @@ -20,8 +21,30 @@ import OptionalLabel from "../intake/OptionalLabel"; import VisitFormFooter from "../visit/VisitFormFooter"; const Visit = (): React.ReactElement => { - // url is /visit/caseId/visitId, commented for now to avoid lint - // const params = useParams(); + const { caseId } = useParams<{ caseId: string}>(); + const history = useHistory() + const caseNumber: number = parseInt(caseId, 10); + + const navigateToPrimary = () => history.goBack(); + + // Visting Member + + const DEFAULT_TRANSPORTATION_DETAILS = { + entries: [ + { + guardian: "", + name: "", + duration: "" + }, + ], + }; + + + const [visitNotes, setVisitNotes] = useState(''); + const [transportationEntries, setTransportationEntries] = + useState(DEFAULT_TRANSPORTATION_DETAILS); + + const DEFAULT_CHILD_DETAILS = { familyName: "", @@ -31,6 +54,8 @@ const Visit = (): React.ReactElement => { fosterCareCoordinator: "", }; + // Attendance Sheet + const DEFAULT_VISIT_DETAILS = { visitDate: "", visitDay: "", @@ -40,6 +65,8 @@ const Visit = (): React.ReactElement => { location: "", }; + // Attendance Records + const DEFAULT_ATTENDANCE_DETAILS = { entries: [ { @@ -53,16 +80,7 @@ const Visit = (): React.ReactElement => { ], }; - const DEDAULT_TRANSPORTATION_DETAILS = { - entries: [ - { - gaurdian: "", - name: "", - duration: "", - }, - ], - }; - + // Transportation const [childDetails, setChildDetails] = useState( DEFAULT_CHILD_DETAILS, ); @@ -75,9 +93,6 @@ const Visit = (): React.ReactElement => { DEFAULT_ATTENDANCE_DETAILS, ); - const [transportationEntries, setTransportationEntries] = - useState(DEDAULT_TRANSPORTATION_DETAILS); - const scrollToHeader = (headerId: string) => { const headerElement = document.getElementById(headerId); @@ -86,6 +101,23 @@ const Visit = (): React.ReactElement => { } }; + const handleNotesChange = (event:any) => { + const currentVisitNotes = event.target.value + setVisitNotes(currentVisitNotes) + + const updatedEntries = [...transportationEntries.entries]; + + if (updatedEntries.length > 0) { + updatedEntries[0] = { ...updatedEntries[0]}; + } + + setTransportationEntries({ ...transportationEntries, entries: updatedEntries }); + }; + + const updateChildServiceWorker = (event:any) => { + setChildDetails({...childDetails, childServiceWorker: event.target.value}) + } + return ( <> @@ -127,8 +159,8 @@ const Visit = (): React.ReactElement => { display="flex" gap="8px" > -