Skip to content

Commit

Permalink
Connecting tags when creating, filtering and editing log record (#203)
Browse files Browse the repository at this point in the history
* initial changes

* change edit log

* change filter and update it to use id rather than name

* small fixes and lint

* reorder lines

* remove tag status

* remove tag status field
  • Loading branch information
braydonwang authored Nov 30, 2023
1 parent 137f173 commit 59f41e0
Show file tree
Hide file tree
Showing 11 changed files with 142 additions and 58 deletions.
1 change: 0 additions & 1 deletion backend/app/models/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ class Tag(db.Model):

tag_id = db.Column(db.Integer, primary_key=True, nullable=False)
name = db.Column(db.String, unique=True, nullable=False)
status = db.Column(db.Enum("Deleted", "Active", name="status"), nullable=False)
last_modified = db.Column(
db.DateTime, server_default=db.func.now(), onupdate=db.func.now(), nullable=False
)
Expand Down
24 changes: 13 additions & 11 deletions backend/app/services/implementations/log_records_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,17 @@ def __init__(self, logger):

def add_record(self, log_record):
new_log_record = log_record.copy()

residents = new_log_record["residents"]
tag_names = new_log_record["tags"]
tags = new_log_record["tags"]

del new_log_record["residents"]
del new_log_record["tags"]

try:
new_log_record = LogRecords(**new_log_record)
self.construct_residents(new_log_record, residents)
self.construct_tags(new_log_record, tag_names)
self.construct_tags(new_log_record, tags)

db.session.add(new_log_record)
db.session.commit()
Expand All @@ -51,12 +52,13 @@ def construct_residents(self, log_record, residents):
raise Exception(f"Resident with id {resident_id} does not exist")
log_record.residents.append(resident)

def construct_tags(self, log_record, tag_names):
for tag_name in tag_names:
tag = Tag.query.filter_by(name=tag_name).first()
def construct_tags(self, log_record, tags):
tags = list(set(tags))
for tag_id in tags:
tag = Tag.query.filter_by(tag_id=tag_id).first()

if not tag:
raise Exception(f"Tag with name {tag_name} does not exist")
raise Exception(f"Tag with id {tag_id} does not exist")
log_record.tags.append(tag)

def to_json_list(self, logs):
Expand Down Expand Up @@ -146,12 +148,12 @@ def filter_by_date_range(self, date_range):
return sql

def filter_by_tags(self, tags):
if len(tags) >= 1:
sql_statement = f"\n'{tags[0]}'=ANY (tag_names)"
if type(tags) == list:
sql_statement = f"\n'{tags[0]}'=ANY (tag_ids)"
for i in range(1, len(tags)):
sql_statement = sql_statement + f"\nAND '{tags[i]}'=ANY (tag_names)"
sql_statement = sql_statement + f"\nAND '{tags[i]}'=ANY (tag_ids)"
return sql_statement
return f"\n'{tags}'=ANY (tag_names)"
return f"\n'{tags}'=ANY (tag_ids)"

def filter_by_flagged(self, flagged):
print(flagged)
Expand Down Expand Up @@ -192,7 +194,7 @@ def join_resident_attributes(self):

def join_tag_attributes(self):
return "\nLEFT JOIN\n \
(SELECT logs.log_id, ARRAY_AGG(tags.name) AS tag_names FROM log_records logs\n \
(SELECT logs.log_id, ARRAY_AGG(tags.tag_id) AS tag_ids, ARRAY_AGG(tags.name) AS tag_names FROM log_records logs\n \
JOIN log_record_tag lrt ON logs.log_id = lrt.log_record_id\n \
JOIN tags ON lrt.tag_id = tags.tag_id\n \
GROUP BY logs.log_id \n \
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/APIClients/TagAPIClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { AxiosError } from "axios";
import AUTHENTICATED_USER_KEY from "../constants/AuthConstants";
import { getLocalStorageObjProperty } from "../utils/LocalStorageUtils";
import baseAPIClient from "./BaseAPIClient";
import { GetTagsResponse } from "../types/TagTypes";

const getTags = async (): Promise<GetTagsResponse> => {
try {
const bearerToken = `Bearer ${getLocalStorageObjProperty(
AUTHENTICATED_USER_KEY,
"accessToken",
)}`;
const { data } = await baseAPIClient.get<GetTagsResponse>(`/tags`, {
headers: { Authorization: bearerToken },
});
return data;
} catch (error) {
return null;
}
};

export default {
getTags,
};
35 changes: 25 additions & 10 deletions frontend/src/components/forms/CreateLog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { Col, Row } from "react-bootstrap";
import { AuthenticatedUser } from "../../types/AuthTypes";
import UserAPIClient from "../../APIClients/UserAPIClient";
import ResidentAPIClient from "../../APIClients/ResidentAPIClient";
import TagAPIClient from "../../APIClients/TagAPIClient";
import { getLocalStorageObj } from "../../utils/LocalStorageUtils";
import AUTHENTICATED_USER_KEY from "../../constants/AuthConstants";
import LogRecordAPIClient from "../../APIClients/LogRecordAPIClient";
Expand All @@ -39,7 +40,8 @@ import { BuildingLabel } from "../../types/BuildingTypes";
import selectStyle from "../../theme/forms/selectStyles";
import { singleDatePickerStyle } from "../../theme/forms/datePickerStyles";
import { UserLabel } from "../../types/UserTypes";
import { ResidentLabel } from "../../types/ResidentTypes";
import { Resident, ResidentLabel } from "../../types/ResidentTypes";
import { TagLabel } from "../../types/TagTypes";
import combineDateTime from "../../helper/combineDateTime";

type Props = {
Expand Down Expand Up @@ -118,14 +120,15 @@ const CreateLog = ({ getRecords, countRecords, setUserPageNum }: Props) => {
);
const [buildingId, setBuildingId] = useState<number>(-1);
const [residents, setResidents] = useState<number[]>([]);
const [tags, setTags] = useState<string[]>([]);
const [tags, setTags] = useState<number[]>([]);
const [attnTo, setAttnTo] = useState(-1);
const [notes, setNotes] = useState("");
const [flagged, setFlagged] = useState(false);

const [employeeOptions, setEmployeeOptions] = useState<UserLabel[]>([]);
const [residentOptions, setResidentOptions] = useState<UserLabel[]>([]);
const [residentOptions, setResidentOptions] = useState<ResidentLabel[]>([]);
const [buildingOptions, setBuildingOptions] = useState<BuildingLabel[]>([]);
const [tagOptions, setTagOptions] = useState<TagLabel[]>([]);

const [isCreateOpen, setCreateOpen] = React.useState(false);

Expand Down Expand Up @@ -184,10 +187,14 @@ const CreateLog = ({ getRecords, countRecords, setUserPageNum }: Props) => {
};

const handleTagsChange = (
selectedTags: MultiValue<{ label: string; value: string }>,
selectedTags: MultiValue<TagLabel>,
) => {
const newTagsList = selectedTags.map((tag) => tag.value);
setTags(newTagsList);
const mutableSelectedTags: TagLabel[] = Array.from(
selectedTags,
);
if (mutableSelectedTags !== null) {
setTags(mutableSelectedTags.map((tagLabel) => tagLabel.value));
}
};

const handleAttnToChange = (
Expand All @@ -206,7 +213,7 @@ const CreateLog = ({ getRecords, countRecords, setUserPageNum }: Props) => {
setNotesError(inputValue === "");
};

// fetch resident + employee data for log creation
// fetch resident + employee + tag data for log creation
const getLogEntryOptions = async () => {
const buildingsData = await BuildingAPIClient.getBuildings();

Expand Down Expand Up @@ -239,6 +246,16 @@ const CreateLog = ({ getRecords, countRecords, setUserPageNum }: Props) => {
}));
setEmployeeOptions(userLabels);
}

const tagsData = await TagAPIClient.getTags();
if (tagsData && tagsData.tags.length !== 0) {
const tagLabels: TagLabel[] = tagsData.tags
.map((tag) => ({
label: tag.name,
value: tag.tagId,
}));
setTagOptions(tagLabels);
}
};

const handleCreateOpen = () => {
Expand Down Expand Up @@ -426,9 +443,7 @@ const CreateLog = ({ getRecords, countRecords, setUserPageNum }: Props) => {
<FormControl mt={4}>
<FormLabel>Tags</FormLabel>
<Select
// TODO: Integrate actual tags once implemented
isDisabled
options={TAGS}
options={tagOptions}
isMulti
closeMenuOnSelect={false}
placeholder="Select Tags"
Expand Down
31 changes: 21 additions & 10 deletions frontend/src/components/forms/EditLog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ import AUTHENTICATED_USER_KEY from "../../constants/AuthConstants";
import LogRecordAPIClient from "../../APIClients/LogRecordAPIClient";
import selectStyle from "../../theme/forms/selectStyles";
import { singleDatePickerStyle } from "../../theme/forms/datePickerStyles";
import { ResidentLabel } from "../../types/ResidentTypes";
import { BuildingLabel } from "../../types/BuildingTypes";
import { TagLabel } from "../../types/TagTypes";
import { UserLabel } from "../../types/UserTypes";
import { ResidentLabel } from "../../types/ResidentTypes";
import { LogRecord } from "../../types/LogRecordTypes";
import { combineDateTime } from "../../helper/dateHelpers";

Expand All @@ -45,7 +46,8 @@ type Props = {
isOpen: boolean;
toggleClose: () => void;
employeeOptions: UserLabel[];
residentOptions: UserLabel[];
residentOptions: ResidentLabel[];
tagOptions: TagLabel[];
getRecords: (pageNumber: number) => Promise<void>;
countRecords: () => Promise<void>;
setUserPageNum: React.Dispatch<React.SetStateAction<number>>;
Expand Down Expand Up @@ -102,6 +104,7 @@ const EditLog = ({
toggleClose,
employeeOptions,
residentOptions,
tagOptions,
getRecords,
countRecords,
setUserPageNum,
Expand All @@ -119,7 +122,7 @@ const EditLog = ({
);
const [buildingId, setBuildingId] = useState<number>(-1);
const [residents, setResidents] = useState<number[]>([]);
const [tags, setTags] = useState<string[]>([]);
const [tags, setTags] = useState<number[]>([]);
const [attnTo, setAttnTo] = useState<number>(-1);
const [notes, setNotes] = useState("");
const [flagged, setFlagged] = useState(false);
Expand Down Expand Up @@ -179,10 +182,14 @@ const EditLog = ({
};

const handleTagsChange = (
selectedTags: MultiValue<{ label: string; value: string }>,
selectedTags: MultiValue<TagLabel>,
) => {
const newTagsList = selectedTags.map((tag) => tag.value);
setTags(newTagsList);
const mutableSelectedTags: TagLabel[] = Array.from(
selectedTags,
);
if (mutableSelectedTags !== null) {
setTags(mutableSelectedTags.map((tagLabel) => tagLabel.value));
}
};

const handleAttnToChange = (
Expand Down Expand Up @@ -217,7 +224,10 @@ const EditLog = ({
(item) => logRecord.residents.includes(item.label),
).map((item) => item.value);
setResidents(residentIds);
setTags(logRecord.tags);
const tagIds = tagOptions.filter(
(item) => logRecord.tags.includes(item.label),
).map((item) => item.value);
setTags(tagIds);
setAttnTo(logRecord.attnTo ? logRecord.attnTo.id : -1);
setNotes(logRecord.note);
setFlagged(logRecord.flagged);
Expand Down Expand Up @@ -379,14 +389,15 @@ const EditLog = ({
<FormControl mt={4}>
<FormLabel>Tags</FormLabel>
<Select
// TODO: Integrate actual tags once implemented
isDisabled
options={TAGS}
options={tagOptions}
isMulti
closeMenuOnSelect={false}
placeholder="Select Tags"
onChange={handleTagsChange}
styles={selectStyle}
defaultValue={tagOptions.filter(
(item) => logRecord.tags.includes(item.label),
)}
/>
</FormControl>
</Col>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/pages/HomePage/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import SearchAndFilters from "./SearchAndFilters";
import ExportCSVButton from "../../common/ExportCSVButton";
import { BuildingLabel } from "../../../types/BuildingTypes";
import { ResidentLabel } from "../../../types/ResidentTypes";
import { Tag } from "../../../types/TagsTypes";
import { TagLabel } from "../../../types/TagTypes";
import { UserLabel } from "../../../types/UserTypes";
import LogRecordAPIClient from "../../../APIClients/LogRecordAPIClient";

Expand All @@ -30,7 +30,7 @@ const HomePage = (): React.ReactElement => {
const [employees, setEmployees] = useState<UserLabel[]>([]);
const [startDate, setStartDate] = useState<Date | undefined>();
const [endDate, setEndDate] = useState<Date | undefined>();
const [tags, setTags] = useState<Tag[]>([]);
const [tags, setTags] = useState<TagLabel[]>([]);
const [attentionTos, setAttentionTos] = useState<UserLabel[]>([]);
const [buildings, setBuildings] = useState<BuildingLabel[]>([]);
const [flagged, setFlagged] = useState(false);
Expand Down
19 changes: 17 additions & 2 deletions frontend/src/components/pages/HomePage/LogRecordsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ import AuthContext from "../../../contexts/AuthContext";
import EditLog from "../../forms/EditLog";
import LogRecordAPIClient from "../../../APIClients/LogRecordAPIClient";
import ResidentAPIClient from "../../../APIClients/ResidentAPIClient";
import TagAPIClient from "../../../APIClients/TagAPIClient";
import UserAPIClient from "../../../APIClients/UserAPIClient";
import BuildingAPIClient from "../../../APIClients/BuildingAPIClient";
import { ResidentLabel } from "../../../types/ResidentTypes";
import { TagLabel } from "../../../types/TagTypes";
import { UserLabel } from "../../../types/UserTypes";
import { BuildingLabel } from "../../../types/BuildingTypes";
import ConfirmationModal from "../../common/ConfirmationModal";
Expand Down Expand Up @@ -71,7 +74,8 @@ const LogRecordsTable = ({

// Dropdown option states
const [employeeOptions, setEmployeeOptions] = useState<UserLabel[]>([]);
const [residentOptions, setResidentOptions] = useState<UserLabel[]>([]);
const [residentOptions, setResidentOptions] = useState<ResidentLabel[]>([]);
const [tagOptions, setTagOptions] = useState<TagLabel[]>([]);

// Handle delete confirmation toggle
const handleDeleteToggle = (logId: number) => {
Expand All @@ -89,7 +93,7 @@ const LogRecordsTable = ({
}));
};

// fetch resident + employee data for log creation
// fetch resident + employee + tag data for log creation
const getLogEntryOptions = async () => {
const residentsData = await ResidentAPIClient.getResidents({
returnAll: true,
Expand Down Expand Up @@ -123,6 +127,16 @@ const LogRecordsTable = ({
}));
setEmployeeOptions(userLabels);
}

const tagsData = await TagAPIClient.getTags();
if (tagsData && tagsData.tags.length !== 0) {
const tagLabels: TagLabel[] = tagsData.tags
.map((tag) => ({
label: tag.name,
value: tag.tagId,
}));
setTagOptions(tagLabels);
}
};

const deleteLogRecord = async (itemId: number) => {
Expand Down Expand Up @@ -232,6 +246,7 @@ const LogRecordsTable = ({
toggleClose={() => handleEditToggle(record.logId)}
employeeOptions={employeeOptions}
residentOptions={residentOptions}
tagOptions={tagOptions}
getRecords={getRecords}
countRecords={countRecords}
setUserPageNum={setUserPageNum}
Expand Down
Loading

0 comments on commit 59f41e0

Please sign in to comment.