Skip to content

Commit

Permalink
Edit and Delete Resident (#143)
Browse files Browse the repository at this point in the history
* initial changes

* changes

* merge

* changes with edit

* edit logic

* added delete functionality

* edit resident working with api

* clean up for PR

* clean up for pr

* fixed api to throw 400

* fix

* Fixed errs

* Now renders toasts WOOOOOOOOOOOOOO

* finished toast messages

* addressed PR comments

* change date to datetime for residents, fix utc conversion error

* fixed resetting dates

* fixed rendering issue

* formatting

* fix date comparison issue with date left and date joined

* resolve Connor's comments

* address remaining bugs

---------

Co-authored-by: Kevin Pierce <[email protected]>
Co-authored-by: Safewaan <[email protected]>
Co-authored-by: Connor Bechthold <[email protected]>
  • Loading branch information
4 people authored Aug 9, 2023
1 parent c8b7ee6 commit f2a030b
Show file tree
Hide file tree
Showing 11 changed files with 579 additions and 50 deletions.
4 changes: 2 additions & 2 deletions backend/app/models/residents.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.Date, nullable=False)
date_left = db.Column(db.Date, nullable=True)
date_joined = db.Column(db.DateTime(timezone=True), nullable=False)
date_left = db.Column(db.DateTime(timezone=True), nullable=True)
building = db.Column(db.Enum("144", "402", "362", name="buildings"), nullable=False)

resident_id = db.column_property(initial + cast(room_num, String))
Expand Down
23 changes: 6 additions & 17 deletions backend/app/rest/residents_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,14 @@
residents_service = ResidentsService(current_app.logger)
blueprint = Blueprint("residents", __name__, url_prefix="/residents")


def is_date_left_invalid_resident(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.strptime(resident["date_joined"], "%Y-%m-%d")
date_left = datetime.strptime(resident["date_left"], "%Y-%m-%d")
if date_left < date_joined:
return True

return False


@blueprint.route("/", methods=["POST"], strict_slashes=False)
@require_authorization_by_role({"Admin"})
def add_resident():
"""
Add a resident
"""
resident = request.json
if is_date_left_invalid_resident(resident):
if residents_service.is_date_left_invalid_resident(resident):
return (
jsonify({"date_left_error": "date_left cannot be less than date_joined"}),
400,
Expand All @@ -49,7 +35,7 @@ def update_resident(resident_id):
Update an existing resident record based on the id
"""
updated_resident = request.json
if is_date_left_invalid_resident(updated_resident):
if residents_service.is_date_left_invalid_resident(updated_resident):
return (
jsonify({"date_left_error": "date_left cannot be less than date_joined"}),
400,
Expand Down Expand Up @@ -94,7 +80,10 @@ def delete_resident(resident_id):
)
except Exception as e:
error_message = getattr(e, "message", None)
return jsonify({"error": (error_message if error_message else str(e))}), 500
if ("has existing log records" in str(e)):
return jsonify({"existing_log_record_error": "Resident has existing log records"}), 400
else:
return jsonify({"error": (error_message if error_message else str(e))}), 500


@blueprint.route("/", methods=["GET"], strict_slashes=False)
Expand Down
48 changes: 35 additions & 13 deletions backend/app/services/implementations/residents_service.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from ..interfaces.residents_service import IResidentsService
from ...models.residents import Residents
from ...models.log_records import LogRecords
from ...models import db
from datetime import datetime
from sqlalchemy import select, cast, Date
Expand All @@ -20,6 +21,19 @@ def __init__(self, logger):
"""
self.logger = logger

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_left < date_joined:
return True

return False

def add_resident(self, resident):
new_resident = resident
try:
Expand All @@ -32,20 +46,20 @@ def add_resident(self, resident):

def update_resident(self, resident_id, updated_resident):
if "date_left" in updated_resident:
Residents.query.filter_by(id=resident_id).update(
create_update_resident = Residents.query.filter_by(id=resident_id).update(
{
Residents.date_left: updated_resident["date_left"],
**updated_resident,
}
)
updated_resident = Residents.query.filter_by(id=resident_id).update(
{
Residents.initial: updated_resident["initial"],
Residents.room_num: updated_resident["room_num"],
Residents.date_joined: updated_resident["date_joined"],
Residents.building: updated_resident["building"],
}
)
if not updated_resident:
else:
create_update_resident = Residents.query.filter_by(id=resident_id).update(
{
Residents.date_left: None,
**updated_resident
}
)
if not create_update_resident:
raise Exception(
"Resident with id {resident_id} not found".format(
resident_id=resident_id
Expand All @@ -54,10 +68,18 @@ def update_resident(self, resident_id, updated_resident):
db.session.commit()

def delete_resident(self, resident_id):
deleted_resident = Residents.query.filter_by(id=resident_id).delete()
if not deleted_resident:
resident_log_records = LogRecords.query.filter_by(resident_id=resident_id).count()
if resident_log_records == 0:
deleted_resident = Residents.query.filter_by(id=resident_id).delete()
if not deleted_resident:
raise Exception(
"Resident with id {resident_id} not found".format(
resident_id=resident_id
)
)
else:
raise Exception(
"Resident with id {resident_id} not found".format(
"Resident with id {resident_id} has existing log records".format(
resident_id=resident_id
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ def upgrade():
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("initial", sa.String(), nullable=False),
sa.Column("room_num", sa.Integer(), nullable=False),
sa.Column("date_joined", sa.Date(), nullable=False),
sa.Column("date_left", sa.Date(), nullable=True),
sa.Column("date_joined", sa.DateTime(timezone=True), nullable=False),
sa.Column("date_left", sa.DateTime(timezone=True), nullable=True),
sa.Column(
"building", sa.Enum("144", "402", "362", name="buildings"), nullable=False
),
Expand Down
58 changes: 58 additions & 0 deletions frontend/src/APIClients/ResidentAPIClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import axios, { AxiosError } from "axios";

Check warning on line 1 in frontend/src/APIClients/ResidentAPIClient.ts

View workflow job for this annotation

GitHub Actions / run-lint

'axios' is defined but never used
import AUTHENTICATED_USER_KEY from "../constants/AuthConstants";
import {
Resident,
GetResidentsReponse,
CountResidentsResponse,
CreateResidentParams,
Expand Down Expand Up @@ -74,8 +76,64 @@ const createResident = async ({
}
};

const deleteResident = async (
residentId: number,
): Promise<{ statusCode: number; message: string }> => {
try {
const bearerToken = `Bearer ${getLocalStorageObjProperty(
AUTHENTICATED_USER_KEY,
"accessToken",
)}`;
await baseAPIClient.delete(`/residents/${residentId}`, {
headers: { Authorization: bearerToken },
});
return {
statusCode: 200,
message: "Success",
};
} catch (error: any) {
const axiosErr = (error as any) as AxiosError;
if (axiosErr.response) {
return {
statusCode: axiosErr.response.status,
message: error.message,
};
}
return {
statusCode: 404,
message: "Error deleting resident",
};
}
};

const editResident = async ({
id,
initial,
roomNum,
dateJoined,
building,
dateLeft,
}: Resident): Promise<boolean> => {
try {
const bearerToken = `Bearer ${getLocalStorageObjProperty(
AUTHENTICATED_USER_KEY,
"accessToken",
)}`;
await baseAPIClient.put(
`/residents/${id}`,
{ initial, roomNum, dateJoined, building, dateLeft },
{ headers: { Authorization: bearerToken } },
);
return true;
} catch (error) {
return false;
}
};

export default {
getResidents,
countResidents,
createResident,
editResident,
deleteResident,
};
77 changes: 77 additions & 0 deletions frontend/src/components/common/DeleteResidentConfirmation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React, { useState } from "react";
import {
Box,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalBody,
ModalFooter,
Button,
Text,
} from "@chakra-ui/react";

type Props = {
itemName: string;
itemId: number;
resId: string | undefined;
isOpen: boolean;
toggleClose: () => void;
deleteAPI: (itemId: number) => void;
};

const DeleteResidentConfirmation = ({
itemName,
itemId,
resId,
isOpen,
toggleClose,
deleteAPI,
}: Props): React.ReactElement => {
const ITEM_NAME = itemName.toLowerCase();
const RES_ID = resId ? resId.toUpperCase() : "";

const handleSubmit = async () => {
deleteAPI(itemId);
toggleClose();
};

const MODAL_HEADER = `This is a permanent action. Are you sure you want to delete Resident ${RES_ID}`;
const MODAL_TEXT = `Residents can only be deleted if there are no log records associated with them.`;

return (
<>
<Box>
<Modal isOpen={isOpen} onClose={toggleClose} size="xl">
<ModalOverlay />
<ModalContent>
<ModalHeader>
{MODAL_HEADER}
</ModalHeader>
<ModalBody>
<Box marginBottom="12px">
<Text>
{MODAL_TEXT}
</Text>
</Box>
</ModalBody>
<ModalFooter>
<Button
onClick={toggleClose}
variant="tertiary"
marginRight="8px"
>
Cancel
</Button>
<Button onClick={handleSubmit} variant="primary" type="submit">
Yes, delete
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</Box>
</>
);
};

export default DeleteResidentConfirmation;
13 changes: 6 additions & 7 deletions frontend/src/components/forms/CreateLog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ const getCurUserSelectOption = () => {
AUTHENTICATED_USER_KEY,
);
if (curUser && curUser.firstName && curUser.id) {
const userId = curUser.id
return { label: curUser.firstName, value: userId }
const userId = curUser.id;
return { label: curUser.firstName, value: userId };
}
return { label: "", value: -1 };
};
Expand Down Expand Up @@ -301,15 +301,14 @@ const CreateLog = ({ getRecords, countRecords, setUserPageNum }: Props) => {
tags,
building,
attnTo: attentionTo,
})
});
if (res != null) {
setAlertData(ALERT_DATA.SUCCESS)
setAlertData(ALERT_DATA.SUCCESS);
countRecords();
getRecords(1);
setUserPageNum(1);
}
else {
setAlertData(ALERT_DATA.ERROR)
} else {
setAlertData(ALERT_DATA.ERROR);
}
setShowAlert(true);
};
Expand Down
7 changes: 3 additions & 4 deletions frontend/src/components/forms/CreateResident.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,12 @@ const CreateResident = (): React.ReactElement => {
const [isOpen, setIsOpen] = useState(false);
const [showAlert, setShowAlert] = useState(false);

const ROOM_ERROR_TEXT = `Room Number is required and must only contain numbers.`;
const addResident = async () => {
await ResidentAPIClient.createResident({
initial: initials.toUpperCase(),
roomNum: parseInt(roomNumber, 10),
dateJoined: moveInDate,
dateJoined: moveInDate.toISOString(),
building,
});
};
Expand Down Expand Up @@ -184,9 +185,7 @@ const CreateResident = (): React.ReactElement => {
onChange={handleRoomNumberChange}
type="number"
/>
<FormErrorMessage>
Room Number is required and must only contain numbers.
</FormErrorMessage>
<FormErrorMessage>{ROOM_ERROR_TEXT}</FormErrorMessage>
</FormControl>
</Col>
</Row>
Expand Down
Loading

0 comments on commit f2a030b

Please sign in to comment.