diff --git a/frontend/src/api/taskAPI.tsx b/frontend/src/api/taskAPI.tsx index 0b28d94..ff4a7ba 100644 --- a/frontend/src/api/taskAPI.tsx +++ b/frontend/src/api/taskAPI.tsx @@ -21,6 +21,25 @@ export default class TaskAPI { return response.data as KanbanTask; } + static createTaskObservable( + boardId: number, + taskData: Partial + ): Observable { + return from(TaskAPI.createTask(boardId)).pipe( + switchMap((task) => { + return from(TaskAPI.updateTask(boardId, task.id, taskData)).pipe( + catchError((error) => { + throw new TaskApiError( + `Error creating task with boardId ${boardId}`, + task, + error + ); + }) + ); + }) + ); + } + static async deleteTask(boardId: number, taskId: number): Promise { const token = localStorage.getItem("token"); @@ -58,24 +77,7 @@ export default class TaskAPI { return response.data as KanbanTask; } - static createTaskObservable( - boardId: number, - taskData: Partial - ): Observable { - return from(TaskAPI.createTask(boardId)).pipe( - switchMap((task) => { - return from(TaskAPI.updateTask(boardId, task.id, taskData)).pipe( - catchError((error) => { - throw new TaskApiError( - `Error creating task with boardId ${boardId}`, - task, - error - ); - }) - ); - }) - ); - } + static updateTaskObservable( boardId: number, diff --git a/frontend/src/components/forms/notification-popup.tsx b/frontend/src/components/forms/notification-popup.tsx new file mode 100644 index 0000000..ba92f60 --- /dev/null +++ b/frontend/src/components/forms/notification-popup.tsx @@ -0,0 +1,45 @@ +import { useEffect } from "react"; + +type NotificationType = "success" | "error" | "info" | undefined; + +interface NotificationPopupProps { + message: string; + isVisible: boolean; + onClose: () => void; + type?: NotificationType; +} + + + +const NotificationPopup = ({ + message, + isVisible, + onClose, + type = "info", +}: NotificationPopupProps) => { + useEffect(() => { + if (isVisible) { + const timer = setTimeout(onClose, 3000); // 1 second auto-close + return () => clearTimeout(timer); + } + }, [isVisible, onClose]); + + if (!isVisible) return null; + + const typeStyles = { + success: "bg-green-500 text-white", + error: "bg-red-500 text-white", + info: "bg-blue-500 text-white", + }; + + return ( +
+ {message} +
+ ); +}; + +export default NotificationPopup; diff --git a/frontend/src/components/kanbans/kanban-display.tsx b/frontend/src/components/kanbans/kanban-display.tsx index 4ac317b..dd34878 100644 --- a/frontend/src/components/kanbans/kanban-display.tsx +++ b/frontend/src/components/kanbans/kanban-display.tsx @@ -8,8 +8,9 @@ import { kanbanColumns } from "../../utilities/kanban-category-mapping"; import { Droppable, DragDropContext } from "react-beautiful-dnd"; import UserAPI from "../../api/userAPI"; import ConfirmationModal from "../forms/confirmation-form"; +import NotificationPopup from "../forms/notification-popup"; import { KanbanDisplayTutorialNoTask } from "../tutorials/kanbanDisplayTutorial"; - + export default function KanbanDisplay({ kanban, setKanban, @@ -24,6 +25,12 @@ export default function KanbanDisplay({ const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false); const [taskToDelete, setTaskToDelete] = useState(null); const [addTaskError, setAddTaskError] = useState(null); + const [notification, setNotification] = useState<{ message: string; isVisible: boolean; type: "success" | "error" | "info" }>({ + message: "", + isVisible: false, + type: "info" + }); + useEffect(() => { const subscription = UserAPI.getUserObservable().subscribe({ @@ -40,6 +47,12 @@ export default function KanbanDisplay({ }; }, []); + + const showNotification = (message: string, type: "success" | "error" | "info" = "info") => { + setNotification({ message, isVisible: true, type }); + setTimeout(() => setNotification({ message: "", isVisible: false, type }), 3000); + }; + const handleDeleteTask = (taskId: number) => { setTaskToDelete(taskId); setIsConfirmModalOpen(true); @@ -55,9 +68,11 @@ export default function KanbanDisplay({ }); setSelectedTask(null); setIsConfirmModalOpen(false); + showNotification("Task deleted successfully", "success"); }) .catch((error) => { console.error("Error deleting task:", error); + }); } }; @@ -106,6 +121,7 @@ export default function KanbanDisplay({ next: (task) => { console.log("Task created:", task); setIsTaskModalOpen(false); + showNotification("Task created successfully", "success"); }, error: (error) => { setAddTaskError(error.error.response.data); @@ -139,6 +155,7 @@ export default function KanbanDisplay({ next: (updatedTask) => { console.log("Task updated:", updatedTask); setKanban({ ...kanban, tasks: updatedTasks }); + showNotification("Task updated successfully", "success"); }, error: (error) => { console.error("Error updating task:", error); @@ -146,8 +163,18 @@ export default function KanbanDisplay({ }); }; + return ( +
+ {/* Notification Popup */} + setNotification({ ...notification, isVisible: false })} + type={notification.type || "info"} + /> + {kanban.tasks.length === 0 && }

@@ -170,6 +197,7 @@ export default function KanbanDisplay({

+