From 91d7519d1169e8b893f2b3eceaf7402423a224d7 Mon Sep 17 00:00:00 2001 From: Connor Bechthold Date: Wed, 27 Sep 2023 01:06:17 -0400 Subject: [PATCH] Resident Date Error (#167) * fix bug * lint fixes --- backend/app/models/residents.py | 9 ++-- .../implementations/residents_service.py | 21 ++++---- ...4fad25f60e3_update_resident_date_fields.py | 54 +++++++++++++++++++ .../src/components/forms/CreateResident.tsx | 3 +- frontend/src/components/forms/EditLog.tsx | 22 +++++--- .../src/components/forms/EditResident.tsx | 17 +++--- .../ResidentDirectoryTable.tsx | 5 +- frontend/src/helper/dateHelpers.ts | 40 ++++++++++++++ 8 files changed, 142 insertions(+), 29 deletions(-) create mode 100644 backend/migrations/versions/24fad25f60e3_update_resident_date_fields.py create mode 100644 frontend/src/helper/dateHelpers.ts diff --git a/backend/app/models/residents.py b/backend/app/models/residents.py index 8a4a726a..5b1b5466 100644 --- a/backend/app/models/residents.py +++ b/backend/app/models/residents.py @@ -8,8 +8,8 @@ class Residents(db.Model): id = db.Column(db.Integer, primary_key=True, nullable=False) initial = db.Column(db.String, nullable=False) room_num = db.Column(db.Integer, nullable=False) - date_joined = db.Column(db.DateTime(timezone=True), nullable=False) - date_left = db.Column(db.DateTime(timezone=True), nullable=True) + date_joined = db.Column(db.Date, nullable=False) + date_left = db.Column(db.Date, nullable=True) building = db.Column(db.Enum("144", "402", "362", name="buildings"), nullable=False) resident_id = db.column_property(initial + cast(room_num, String)) @@ -31,7 +31,10 @@ def to_dict(self, include_relationships=False): attr = getattr(self, field) # if it's a regular column, extract the value if isinstance(column, ColumnProperty): - formatted[field] = attr + if (field == "date_joined" or field == "date_left") and attr: + formatted[field] = attr.strftime("%Y-%m-%d") + else: + formatted[field] = attr # otherwise, it's a relationship field # (currently not applicable, but may be useful for entity groups) elif include_relationships: diff --git a/backend/app/services/implementations/residents_service.py b/backend/app/services/implementations/residents_service.py index 9897c5c8..3fab5e71 100644 --- a/backend/app/services/implementations/residents_service.py +++ b/backend/app/services/implementations/residents_service.py @@ -21,27 +21,28 @@ def __init__(self, logger): """ self.logger = logger + def convert_to_date_obj(self, date): + return datetime.strptime(date, "%Y-%m-%d") + def is_date_left_invalid_resident(self, resident): """ Validates if date_left is greater than date_joined given a payload for a resident """ - if "date_joined" in resident and "date_left" in resident: - date_joined = datetime.fromisoformat( - resident["date_joined"].replace("Z", "+00:00") - ) - date_left = datetime.fromisoformat( - resident["date_left"].replace("Z", "+00:00") - ) + if "date_joined" in resident: + resident["date_joined"] = self.convert_to_date_obj(resident["date_joined"]) + + if "date_left" in resident: + resident["date_left"] = self.convert_to_date_obj(resident["date_left"]) - if date_left < date_joined: + if "date_joined" in resident and "date_left" in resident: + if resident["date_left"] < resident["date_joined"]: return True return False def add_resident(self, resident): - new_resident = resident try: - new_resident = Residents(**new_resident) + new_resident = Residents(**resident) db.session.add(new_resident) db.session.commit() return resident diff --git a/backend/migrations/versions/24fad25f60e3_update_resident_date_fields.py b/backend/migrations/versions/24fad25f60e3_update_resident_date_fields.py new file mode 100644 index 00000000..4fd36fed --- /dev/null +++ b/backend/migrations/versions/24fad25f60e3_update_resident_date_fields.py @@ -0,0 +1,54 @@ +"""update_resident_date_fields + +Revision ID: 24fad25f60e3 +Revises: a5d22b31faab +Create Date: 2023-09-26 19:33:15.491168 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "24fad25f60e3" +down_revision = "a5d22b31faab" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("residents", schema=None) as batch_op: + batch_op.alter_column( + "date_joined", + existing_type=postgresql.TIMESTAMP(timezone=True), + type_=sa.Date(), + existing_nullable=False, + ) + batch_op.alter_column( + "date_left", + existing_type=postgresql.TIMESTAMP(timezone=True), + type_=sa.Date(), + existing_nullable=True, + ) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("residents", schema=None) as batch_op: + batch_op.alter_column( + "date_left", + existing_type=sa.Date(), + type_=postgresql.TIMESTAMP(timezone=True), + existing_nullable=True, + ) + batch_op.alter_column( + "date_joined", + existing_type=sa.Date(), + type_=postgresql.TIMESTAMP(timezone=True), + existing_nullable=False, + ) + + # ### end Alembic commands ### diff --git a/frontend/src/components/forms/CreateResident.tsx b/frontend/src/components/forms/CreateResident.tsx index 43fc6334..b0e77fff 100644 --- a/frontend/src/components/forms/CreateResident.tsx +++ b/frontend/src/components/forms/CreateResident.tsx @@ -28,6 +28,7 @@ import { Col, Row } from "react-bootstrap"; import selectStyle from "../../theme/forms/selectStyles"; import { singleDatePickerStyle } from "../../theme/forms/datePickerStyles"; import ResidentAPIClient from "../../APIClients/ResidentAPIClient"; +import { convertToString } from "../../helper/dateHelpers"; // TODO: Connect to Buidings table const BUILDINGS = [ @@ -55,7 +56,7 @@ const CreateResident = (): React.ReactElement => { await ResidentAPIClient.createResident({ initial: initials.toUpperCase(), roomNum: parseInt(roomNumber, 10), - dateJoined: moveInDate.toISOString(), + dateJoined: convertToString(moveInDate), building, }); }; diff --git a/frontend/src/components/forms/EditLog.tsx b/frontend/src/components/forms/EditLog.tsx index e7ae4c5c..a1fe7a2b 100644 --- a/frontend/src/components/forms/EditLog.tsx +++ b/frontend/src/components/forms/EditLog.tsx @@ -35,7 +35,7 @@ import selectStyle from "../../theme/forms/selectStyles"; import { singleDatePickerStyle } from "../../theme/forms/datePickerStyles"; import { UserLabel } from "../../types/UserTypes"; import { LogRecord } from "../../types/LogRecordTypes"; -import combineDateTime from "../../helper/combineDateTime"; +import { combineDateTime } from "../../helper/dateHelpers"; type Props = { logRecord: LogRecord; @@ -199,7 +199,7 @@ const EditLog = ({ }; const initializeValues = () => { - // set state variables + // set state variables setEmployee(getCurUserSelectOption()); setDate(new Date(logRecord.datetime)); setTime( @@ -210,7 +210,9 @@ const EditLog = ({ }), ); setBuilding(logRecord.building); - const residentId = residentOptions.find((item) => item.label === logRecord.residentId)?.value; + const residentId = residentOptions.find( + (item) => item.label === logRecord.residentId, + )?.value; setResident(residentId !== undefined ? residentId : -1); setTags(logRecord.tags); setAttnTo(logRecord.attnTo !== undefined ? logRecord.attnTo : -1); @@ -257,7 +259,7 @@ const EditLog = ({ tags, building, attnTo: attnTo === -1 ? undefined : attnTo, - }) + }); if (res) { setAlertData(ALERT_DATA.SUCCESS); countRecords(); @@ -344,7 +346,9 @@ const EditLog = ({ placeholder="Building No." onChange={handleBuildingChange} styles={selectStyle} - defaultValue={BUILDINGS.find((item) => item.value === building)} + defaultValue={BUILDINGS.find( + (item) => item.value === building, + )} /> Building is required. @@ -357,7 +361,9 @@ const EditLog = ({ placeholder="Select Resident" onChange={handleResidentChange} styles={selectStyle} - defaultValue={residentOptions.find((item) => item.label === logRecord.residentId)} + defaultValue={residentOptions.find( + (item) => item.label === logRecord.residentId, + )} /> Resident is required. @@ -389,7 +395,9 @@ const EditLog = ({ placeholder="Select Employee" onChange={handleAttnToChange} styles={selectStyle} - defaultValue={employeeOptions.find((item) => item.value === logRecord.attnTo)} + defaultValue={employeeOptions.find( + (item) => item.value === logRecord.attnTo, + )} /> diff --git a/frontend/src/components/forms/EditResident.tsx b/frontend/src/components/forms/EditResident.tsx index 3142dc05..d626e98c 100644 --- a/frontend/src/components/forms/EditResident.tsx +++ b/frontend/src/components/forms/EditResident.tsx @@ -33,6 +33,7 @@ import { Resident } from "../../types/ResidentTypes"; import selectStyle from "../../theme/forms/selectStyles"; import { singleDatePickerStyle } from "../../theme/forms/datePickerStyles"; import CreateToast from "../common/Toasts"; +import { convertToDate, convertToString } from "../../helper/dateHelpers"; // TODO: Connect to Buidings table const BUILDINGS = [ @@ -67,9 +68,9 @@ const EditResident = ({ resident, isOpen, toggleClose }: Props) => { residentId: resident.residentId, initial: initials.toUpperCase(), roomNum: roomNumber, - dateJoined: moveInDate.toISOString(), + dateJoined: convertToString(moveInDate), building: userBuilding, - dateLeft: moveOutDate?.toISOString(), + dateLeft: moveOutDate ? convertToString(moveOutDate) : undefined, }); if (res != null) { @@ -135,9 +136,11 @@ const EditResident = ({ resident, isOpen, toggleClose }: Props) => { setInitials(resident.initial); setRoomNumber(resident.roomNum); - setMoveInDate(new Date(resident.dateJoined)); + setMoveInDate(convertToDate(resident.dateJoined)); setUserBuilding(resident.building); - setMoveOutDate(resident.dateLeft ? new Date(resident.dateLeft) : undefined); + setMoveOutDate( + resident.dateLeft ? convertToDate(resident.dateLeft) : undefined, + ); setInitialsError(false); setRoomNumberError(false); @@ -174,9 +177,11 @@ const EditResident = ({ resident, isOpen, toggleClose }: Props) => { useEffect(() => { setInitials(resident.initial); setRoomNumber(resident.roomNum); - setMoveInDate(new Date(resident.dateJoined)); + setMoveInDate(convertToDate(resident.dateJoined)); setUserBuilding(resident.building); - setMoveOutDate(resident.dateLeft ? new Date(resident.dateLeft) : undefined); + setMoveOutDate( + resident.dateLeft ? convertToDate(resident.dateLeft) : undefined, + ); }, [resident]); return ( diff --git a/frontend/src/components/pages/ResidentDirectory/ResidentDirectoryTable.tsx b/frontend/src/components/pages/ResidentDirectory/ResidentDirectoryTable.tsx index 5e12735c..b0164426 100644 --- a/frontend/src/components/pages/ResidentDirectory/ResidentDirectoryTable.tsx +++ b/frontend/src/components/pages/ResidentDirectory/ResidentDirectoryTable.tsx @@ -22,6 +22,7 @@ import getFormattedDateAndTime from "../../../utils/DateUtils"; import AuthContext from "../../../contexts/AuthContext"; import CreateToast from "../../common/Toasts"; import ConfirmationModal from "../../common/ConfirmationModal"; +import { convertToDate } from "../../../helper/dateHelpers"; type Props = { residents: Resident[]; @@ -29,12 +30,12 @@ type Props = { }; const getFormattedDatesAndStatus = (resident: Resident) => { - const startDateObj = new Date(resident.dateJoined); + const startDateObj = convertToDate(resident.dateJoined); const startDate = getFormattedDateAndTime(startDateObj, true); let endDate; if (resident.dateLeft != null) { - const endDateObj = new Date(resident.dateLeft); + const endDateObj = convertToDate(resident.dateLeft); endDate = getFormattedDateAndTime(endDateObj, true); } const status = diff --git a/frontend/src/helper/dateHelpers.ts b/frontend/src/helper/dateHelpers.ts new file mode 100644 index 00000000..4e97f418 --- /dev/null +++ b/frontend/src/helper/dateHelpers.ts @@ -0,0 +1,40 @@ +// Combine date and time +export const combineDateTime = (dateObj: Date, timeStr: string): Date => { + // Extract time components from timeStr + const [hours, minutes] = timeStr.split(":").map(Number); + + // Create a new Date object with the combined date and time + const newDateObj = new Date(dateObj); + newDateObj.setHours(hours); + newDateObj.setMinutes(minutes); + + return newDateObj; +}; + +/** + * + * @param dateString yyyy-mm-dd format + */ +export const convertToDate = (dateString: string): Date => { + // Split the date string into its components + const dateComponents = dateString.split("-"); + const year = parseInt(dateComponents[0], 10); + const month = parseInt(dateComponents[1], 10) - 1; // Months are zero-based (0-11) + const day = parseInt(dateComponents[2], 10); + + // Create a Date object using the components + return new Date(year, month, day); +}; + +/** + * + * @returns date string in yyyy-mm-dd format + */ +export const convertToString = (date: Date): string => { + // Get the year, month, and day components + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, "0"); // Months are zero-based, so add 1 + const day = String(date.getDate()).padStart(2, "0"); + + return `${year}-${month}-${day}`; +};