Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion backend/typescript/services/implementations/authService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ class AuthService implements IAuthService {
const user = await this.userService.getUserByEmail(email);
return { ...token, ...user };
} catch (error) {
Logger.error(`Failed to generate token for user with email ${email}`);
Logger.error(
`Failed to generate token for user with email ${email}, error ${error}`,
);
throw error;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import {
useToast,
} from "@chakra-ui/react";
import React, { useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
import TaskTemplateAPIClient from "../../../APIClients/TaskTemplateAPIClient";
import Button from "../../../components/common/Button";
import TaskCategoryBadge from "../../../components/common/TaskCategoryBadge";
import { EDIT_TASK_TEMPLATE_PAGE } from "../../../constants/Routes";
import { Task } from "../../../types/TaskTypes";

interface TaskDetailsModelsProps {
Expand All @@ -30,6 +32,7 @@ const TaskDetailsModal = ({
onClose,
}: TaskDetailsModelsProps): React.ReactElement => {
const toast = useToast();
const history = useHistory();
const [taskTemplateData, setTaskTemplateData] = useState<Task | null>(null);

useEffect(() => {
Expand Down Expand Up @@ -118,13 +121,7 @@ const TaskDetailsModal = ({
variant="blue-outline"
width="100%"
onClick={() => {
toast({
title: "Edit Task",
description: "Edit task functionality not implemented yet",
status: "info",
duration: 3000,
isClosable: true,
});
history.push(`${EDIT_TASK_TEMPLATE_PAGE}/${taskTemplateId}`);
}}
>
Edit Task
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import TaskManagementTableSection from "./TaskManagementTableSection";
interface TaskManagementTableProps {
tasks: Task[];
clearFilters: () => void;
onTaskClick: (task: Task) => void;
}

const TaskManagementTable = ({
tasks,
clearFilters,
onTaskClick,
}: TaskManagementTableProps): React.ReactElement => {
return (
<Flex width="100%" overflowX="auto">
Expand Down Expand Up @@ -48,7 +50,7 @@ const TaskManagementTable = ({
</Tr>
</Tbody>
) : (
<TaskManagementTableSection tasks={tasks} />
<TaskManagementTableSection tasks={tasks} onTaskClick={onTaskClick} />
)}
</Table>
</Flex>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,22 @@ import { Task } from "../../../types/TaskTypes";

interface TaskManagementTableSectionProps {
tasks: Task[];
onTaskClick: (task: Task) => void;
}

const TaskManagementTableSection = ({
tasks,
onTaskClick,
}: TaskManagementTableSectionProps): React.ReactElement => {
return (
<Tbody>
{tasks.map((task) => (
<Tr key={task.id}>
<Tr
key={task.id}
onClick={() => onTaskClick(task)}
cursor="pointer"
_hover={{ bg: "gray.100" }}
>
<Td width="20%" py="1rem" px="2.5rem">
<Text textStyle="body" m={0} noOfLines={1}>
{task.name}
Expand Down
40 changes: 21 additions & 19 deletions frontend/src/features/task-management/pages/AddTaskTemplatePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,8 @@ import { ReactComponent as GamesIcon } from "../../../assets/icons/games.svg";
import { ReactComponent as TrainingIcon } from "../../../assets/icons/training.svg";
import { ReactComponent as WalkIcon } from "../../../assets/icons/walk.svg";
import { ReactComponent as MiscIcon } from "../../../assets/icons/misc.svg";

interface TaskTemplateForm {
taskName: string;
taskCategory: TaskCategory | null;
taskInstructions: string;
}
import TaskTemplateAPIClient from "../../../APIClients/TaskTemplateAPIClient";
import { type CreateTaskDTO } from "../../../types/TaskTypes";

interface FormErrors {
taskName?: string;
Expand All @@ -39,10 +35,10 @@ interface FormErrors {

const AddTaskTemplatePage = (): React.ReactElement => {
const history = useHistory();
const [formData, setFormData] = useState<TaskTemplateForm>({
const [formData, setFormData] = useState<CreateTaskDTO>({
taskName: "",
taskCategory: null,
taskInstructions: "",
category: TaskCategory.MISC,
instructions: "",
});
const [errors, setErrors] = useState<FormErrors>({});
const [isSubmitting, setIsSubmitting] = useState(false);
Expand Down Expand Up @@ -93,7 +89,7 @@ const AddTaskTemplatePage = (): React.ReactElement => {
};

const handleCategoryChange = (category: TaskCategory) => {
setFormData({ ...formData, taskCategory: category });
setFormData({ ...formData, category });
setHasChanges(true);
if (errors.taskCategory) {
setErrors({ ...errors, taskCategory: undefined });
Expand All @@ -103,7 +99,7 @@ const AddTaskTemplatePage = (): React.ReactElement => {
const handleInstructionsChange = (
event: React.ChangeEvent<HTMLTextAreaElement>,
) => {
setFormData({ ...formData, taskInstructions: event.target.value });
setFormData({ ...formData, instructions: event.target.value });
setHasChanges(true);
if (errors.taskInstructions) {
setErrors({ ...errors, taskInstructions: undefined });
Expand All @@ -117,11 +113,11 @@ const AddTaskTemplatePage = (): React.ReactElement => {
newErrors.taskName = "Field is required.";
}

if (!formData.taskCategory) {
if (!formData.category) {
newErrors.taskCategory = "Please select an option from the dropdown.";
}

if (!formData.taskInstructions.trim()) {
if (!formData.instructions?.trim()) {
newErrors.taskInstructions = "Information must not exceed 10,000 words.";
}

Expand All @@ -144,12 +140,18 @@ const AddTaskTemplatePage = (): React.ReactElement => {
/* eslint-disable-next-line no-console */
console.log({
taskName: formData.taskName,
taskCategory: formData.taskCategory,
taskInstructions: formData.taskInstructions,
taskCategory: formData.category,
taskInstructions: formData.instructions,
});

// Simulate API call delay
await new Promise((resolve) => setTimeout(resolve, 1000));
// Call the API endpoint
try {
await TaskTemplateAPIClient.createTaskTemplate(formData);
} catch (error) {
// TODO: deprecate console use in frontend
/* eslint-disable-next-line no-console */
console.error("Could not post task template: ", error);
}

// Navigate back to task management page
history.push(TASK_MANAGEMENT_PAGE);
Expand Down Expand Up @@ -217,7 +219,7 @@ const AddTaskTemplatePage = (): React.ReactElement => {
<SingleSelect
label="Task Category"
values={taskCategories}
selected={formData.taskCategory}
selected={formData.category}
onSelect={handleCategoryChange}
placeholder="Click for options"
icons={taskCategoryIconsArray}
Expand All @@ -233,7 +235,7 @@ const AddTaskTemplatePage = (): React.ReactElement => {

<TextArea
label="Instructions"
value={formData.taskInstructions}
value={formData.instructions || ""}
onChange={handleInstructionsChange}
placeholder="Write task instructions here"
error={errors.taskInstructions}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const EditTaskTemplatePage = (): React.ReactElement => {
);

useEffect(() => {
const fetchUser = async () => {
const fetchTaskTemplate = async () => {
if (!taskTemplateId) return;

try {
Expand All @@ -91,15 +91,15 @@ const EditTaskTemplatePage = (): React.ReactElement => {
} catch (error) {
toast({
title: "Error",
description: "Failed to fetch user data",
description: "Failed to fetch task template data",
status: "error",
duration: 3000,
isClosable: true,
});
}
};

fetchUser();
fetchTaskTemplate();
}, [reset, taskTemplateId, toast]);

const handleBackClick = () => {
Expand All @@ -122,7 +122,7 @@ const EditTaskTemplatePage = (): React.ReactElement => {
const handleDeleteTaskTemplate = () => {
// TODO: Open delete task template modal and remove toast
toast({
title: "Delete User",
title: "Delete Task Template",
description: "Delete functionality not implemented yet",
status: "info",
duration: 3000,
Expand Down Expand Up @@ -275,7 +275,7 @@ const EditTaskTemplatePage = (): React.ReactElement => {
onClick={handleDeleteTaskTemplate}
type="button"
>
Delete User
Delete Task Template
</Button>
<Button
type="submit"
Expand Down
78 changes: 70 additions & 8 deletions frontend/src/features/task-management/pages/TaskManagementPage.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
import React, { useState, useMemo } from "react";
import { useDisclosure } from "@chakra-ui/react";
import React, { useEffect, useMemo, useState } from "react";
import { useHistory } from "react-router-dom";
import TaskManagementTable from "../components/TaskManagementTable";
import TaskDetailsModal from "../components/TaskDetailsModal";
import { TableWrapper } from "../../../components/common/table";
import { mockTasks } from "../../../types/TaskTypes";
import { type Task } from "../../../types/TaskTypes";
import Button from "../../../components/common/Button";
import { ADD_TASK_TEMPLATE_PAGE } from "../../../constants/Routes";
import TaskTemplateAPIClient from "../../../APIClients/TaskTemplateAPIClient";
import Pagination from "../../../components/common/Pagination";

const TaskManagementPage = (): React.ReactElement => {
const history = useHistory();
const [tasks, setTasks] = useState<Task[]>([]);
const [filters, setFilters] = useState<Record<string, string[]>>({});
const [search, setSearch] = useState<string>("");
const { isOpen, onOpen, onClose } = useDisclosure();
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
const [page, setPage] = useState<number>(1);
const numTasksPerPage = 10;

const handleTaskClick = (task: Task) => {
setSelectedTask(task);
onOpen();
};

const handleClearFilters = () => {
setFilters({});
Expand All @@ -29,20 +43,49 @@ const TaskManagementPage = (): React.ReactElement => {
};

const filteredTasks = useMemo(() => {
return mockTasks
.filter((task) => {
const hasActiveFilters = Object.values(filters).some(
(vals) => vals && vals.length > 0,
);
const hasSearch = search.trim() !== "";

// If no filters and no search, just return everything
if (!hasActiveFilters && !hasSearch) return tasks;

return tasks
.filter((task: Task) => {
return Object.keys(filters).every((key) => {
const filterVals = filters[key];
if (!filterVals || filterVals.length === 0) return true;
return filterVals.includes(task[key as keyof typeof task] as string);
});
})
.filter(
(task) =>
(task: Task) =>
task.name.toLowerCase().includes(search.toLowerCase()) ||
task.instructions.toLowerCase().includes(search.toLowerCase()),
task.instructions?.toLowerCase().includes(search.toLowerCase()),
);
}, [filters, search]);
}, [filters, search, tasks]);

const filteredTasksLength = filteredTasks.length;

const getTasks = async () => {
try {
const fetchedTasks = await TaskTemplateAPIClient.getAllTaskTemplates();

if (fetchedTasks != null) {
setTasks(fetchedTasks);
}
} catch (error) {
setTasks([]);
// TODO: deprecate console use in frontend
/* eslint-disable-next-line no-console */
console.error("Could not fetch tasks");
}
};

useEffect(() => {
getTasks();
}, []);

return (
<TableWrapper
Expand All @@ -63,11 +106,30 @@ const TaskManagementPage = (): React.ReactElement => {
</Button>
),
}}
bottomContent={
<Pagination
value={page}
onChange={(newPage) => setPage(newPage)}
numberOfItems={filteredTasksLength}
itemsPerPage={numTasksPerPage}
/>
}
>
<TaskManagementTable
tasks={filteredTasks}
tasks={filteredTasks.slice(
(page - 1) * numTasksPerPage,
page * numTasksPerPage,
)}
clearFilters={handleClearFilters}
onTaskClick={handleTaskClick}
/>
{selectedTask && (
<TaskDetailsModal
isOpen={isOpen}
onClose={onClose}
taskTemplateId={selectedTask.id}
/>
)}
</TableWrapper>
);
};
Expand Down
Loading