diff --git a/backend/boards/signals.py b/backend/boards/signals.py
index c416de9..2c44daa 100644
--- a/backend/boards/signals.py
+++ b/backend/boards/signals.py
@@ -8,8 +8,10 @@ from users.models import CustomUser
def create_default_board(sender, instance, created, **kwargs):
"""Signal handler to automatically create a default Board for a user upon creation."""
if created:
- board = Board.objects.create(user=instance, name="My Default Board")
-
- ListBoard.objects.create(board=board, name="Todo", position=1)
- ListBoard.objects.create(board=board, name="In Progress", position=2)
- ListBoard.objects.create(board=board, name="Done", position=3)
\ No newline at end of file
+ # Create unique board by user id
+ user_id = instance.id
+ board = Board.objects.create(user=instance, name=f"Board of #{user_id}")
+ ListBoard.objects.create(board=board, name="Backlog", position=1)
+ ListBoard.objects.create(board=board, name="Doing", position=2)
+ ListBoard.objects.create(board=board, name="Review", position=3)
+ ListBoard.objects.create(board=board, name="Done", position=4)
\ No newline at end of file
diff --git a/backend/boards/views.py b/backend/boards/views.py
index 8b3fd47..d405c87 100644
--- a/backend/boards/views.py
+++ b/backend/boards/views.py
@@ -1,11 +1,13 @@
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import status
+from rest_framework.permissions import IsAuthenticated
from boards.models import Board, ListBoard
from boards.serializers import BoardSerializer, ListBoardSerializer
class BoardViewSet(viewsets.ModelViewSet):
+ permission_classes = (IsAuthenticated,)
queryset = Board.objects.all()
serializer_class = BoardSerializer
http_method_names = ['get']
@@ -16,6 +18,7 @@ class BoardViewSet(viewsets.ModelViewSet):
class ListBoardViewSet(viewsets.ModelViewSet):
+ permission_classes = (IsAuthenticated,)
serializer_class = ListBoardSerializer
def get_queryset(self):
diff --git a/backend/tasks/signals.py b/backend/tasks/signals.py
index 063292e..4c9e6f2 100644
--- a/backend/tasks/signals.py
+++ b/backend/tasks/signals.py
@@ -27,15 +27,63 @@ def update_priority(sender, instance, **kwargs):
instance.priority = Todo.EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT
-@receiver(post_save, sender=Todo)
-def assign_todo_to_listboard(sender, instance, created, **kwargs):
- """Signal handler to automatically assign a Todo to the first ListBoard in the user's Board upon creation."""
+# @receiver(post_save, sender=Todo)
+# def assign_todo_to_listboard(sender, instance, created, **kwargs):
+# """Signal handler to automatically assign a Todo to the first ListBoard in the user's Board upon creation."""
+# if created:
+# user_board = instance.user.board_set.first()
+
+# if user_board:
+# first_list_board = user_board.listboard_set.order_by('position').first()
+
+# if first_list_board:
+# instance.list_board = first_list_board
+# instance.save()
+
+
+@receiver(post_save, sender=ListBoard)
+def create_placeholder_tasks(sender, instance, created, **kwargs):
+ """
+ Signal handler to create placeholder tasks for each ListBoard.
+ """
if created:
- user_board = instance.user.board_set.first()
+ list_board_position = instance.position
- if user_board:
- first_list_board = user_board.listboard_set.order_by('position').first()
+ if list_board_position == 1:
+ placeholder_tasks = [
+ {"title": "Normal Task Example"},
+ {"title": "Task with Extra Information Example", "description": "Description for Task 2"},
+ ]
+ elif list_board_position == 2:
+ placeholder_tasks = [
+ {"title": "Time Task Example #1", "description": "Description for Task 2",
+ "start_event": timezone.now(), "end_event": timezone.now() + timezone.timedelta(days=5)},
+ ]
+ elif list_board_position == 3:
+ placeholder_tasks = [
+ {"title": "Time Task Example #2", "description": "Description for Task 2",
+ "start_event": timezone.now(), "end_event": timezone.now() + timezone.timedelta(days=30)},
+ ]
+ elif list_board_position == 4:
+ placeholder_tasks = [
+ {"title": "Completed Task Example", "description": "Description for Task 2",
+ "start_event": timezone.now(), "completed": True},
+ ]
+ else:
+ placeholder_tasks = [
+ {"title": "Default Task Example"},
+ ]
- if first_list_board:
- instance.list_board = first_list_board
- instance.save()
\ No newline at end of file
+ for task_data in placeholder_tasks:
+ Todo.objects.create(
+ list_board=instance,
+ user=instance.board.user,
+ title=task_data["title"],
+ notes=task_data.get("description", ""),
+ is_active=True,
+ start_event=task_data.get("start_event"),
+ end_event=task_data.get("end_event"),
+ completed=task_data.get("completed", False),
+ creation_date=timezone.now(),
+ last_update=timezone.now(),
+ )
\ No newline at end of file
diff --git a/frontend/src/components/authentication/SignUpPage.jsx b/frontend/src/components/authentication/SignUpPage.jsx
index 0492412..6fcd086 100644
--- a/frontend/src/components/authentication/SignUpPage.jsx
+++ b/frontend/src/components/authentication/SignUpPage.jsx
@@ -56,6 +56,8 @@ export function SignUp() {
const googleLoginImplicit = useGoogleLogin({
flow: "auth-code",
redirect_uri: "postmessage",
+ scope:
+ "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/calendar.acls.readonly https://www.googleapis.com/auth/calendar.events.readonly",
onSuccess: async (response) => {
try {
const loginResponse = await googleLogin(response.code);
diff --git a/frontend/src/components/calendar/calendar.jsx b/frontend/src/components/calendar/calendar.jsx
index a85ea2d..79dae97 100644
--- a/frontend/src/components/calendar/calendar.jsx
+++ b/frontend/src/components/calendar/calendar.jsx
@@ -1,11 +1,10 @@
-import React from "react";
+import React, { useState } from "react";
import { formatDate } from "@fullcalendar/core";
import FullCalendar from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import interactionPlugin from "@fullcalendar/interaction";
import { getEvents, createEventId } from "./TaskDataHandler";
-import { axiosInstance } from "src/api/AxiosConfig";
export class Calendar extends React.Component {
state = {
@@ -62,10 +61,13 @@ export class Calendar extends React.Component {
type="checkbox"
checked={this.state.weekendsVisible}
onChange={this.handleWeekendsToggle}
- className="mr-2"
+ className="mr-2 mb-4"
/>
Toggle weekends
+ alert("Commit soon🥺")}>
+ Load Data from Google
+
{/* Show all task */}
diff --git a/frontend/src/components/kanbanBoard/columnContainer.jsx b/frontend/src/components/kanbanBoard/columnContainer.jsx
index 819a980..d6f4565 100644
--- a/frontend/src/components/kanbanBoard/columnContainer.jsx
+++ b/frontend/src/components/kanbanBoard/columnContainer.jsx
@@ -1,53 +1,16 @@
import { SortableContext, useSortable } from "@dnd-kit/sortable";
-import { BsFillTrashFill } from "react-icons/bs";
import { AiOutlinePlusCircle } from "react-icons/ai";
-import { CSS } from "@dnd-kit/utilities";
-import { useMemo, useState } from "react";
+import { useMemo } from "react";
import { TaskCard } from "./taskCard";
-export function ColumnContainer({ column, deleteColumn, updateColumn, createTask, tasks, deleteTask, updateTask }) {
- const [editMode, setEditMode] = useState(false);
-
+export function ColumnContainer({ column, createTask, tasks, deleteTask, updateTask }) {
+ // Memoize task IDs to prevent unnecessary recalculations
const tasksIds = useMemo(() => {
return tasks.map((task) => task.id);
}, [tasks]);
- const { setNodeRef, attributes, listeners, transform, transition, isDragging } = useSortable({
- id: column.id,
- data: {
- type: "Column",
- column,
- },
- disabled: editMode,
- });
-
- const style = {
- transition,
- transform: CSS.Transform.toString(transform),
- };
-
- if (isDragging) {
- return (
-
- );
- }
-
return (
{/* Column title */}
{
- setEditMode(true);
- }}
className="
ml-3
text-md
- cursor-grab
font-bold
flex
items-center
justify-between
">
-
- {!editMode && column.title}
- {editMode && (
- updateColumn(column.id, e.target.value)}
- autoFocus
- onBlur={() => {
- setEditMode(false);
- }}
- onKeyDown={(e) => {
- if (e.key !== "Enter") return;
- setEditMode(false);
- }}
- />
- )}
-
-
{
- deleteColumn(column.id);
- }}
- className="
- stroke-gray-500
- hover:stroke-white
- hover:bg-columnBackgroundColor
- rounded
- px-1
- py-2
- ">
-
-
+
{column.title}
{/* Column task container */}
+ {/* Provide a SortableContext for the tasks within the column */}
+ {/* Render TaskCard for each task in the column */}
{tasks.map((task) => (
-
+ useSortable({ ...props, disabled: false })}
+ />
))}
+
{/* Column footer */}
+
columns.map((col) => col.id), [columns]);
const [boardId, setBoardData] = useState();
-
+ const [isLoading, setLoading] = useState(false);
const [tasks, setTasks] = useState([]);
-
- const [activeColumn, setActiveColumn] = useState(null);
-
const [activeTask, setActiveTask] = useState(null);
+ const columnsId = useMemo(() => columns.map((col) => col.id), [columns]);
+
+ // ---------------- END STATE INITIATE ----------------
const sensors = useSensors(
useSensor(PointerSensor, {
@@ -25,6 +24,75 @@ export function KanbanBoard() {
})
);
+ // ---------------- Task Handlers ----------------
+ const handleTaskUpdate = (tasks, updatedTask) => {
+ const updatedTasks = tasks.map((task) => (task.id === updatedTask.id ? updatedTask : task));
+ setTasks(updatedTasks);
+ };
+
+ const handleApiError = (error, action) => {
+ console.error(`Error ${action}:`, error);
+ };
+
+ const createTask = async (columnId) => {
+ try {
+ const response = await axiosInstance.post("todo/", {
+ title: `New Task`,
+ importance: 1,
+ difficulty: 1,
+ challenge: false,
+ fromSystem: false,
+ is_active: false,
+ is_full_day_event: false,
+ completed: false,
+ priority: 1,
+ list_board: columnId,
+ });
+ const newTask = {
+ id: response.data.id,
+ columnId,
+ content: response.data.title,
+ };
+
+ setTasks((prevTasks) => [...prevTasks, newTask]);
+ } catch (error) {
+ handleApiError(error, "creating task");
+ }
+ };
+
+ const deleteTask = async (id) => {
+ try {
+ await axiosInstance.delete(`todo/${id}/`);
+ const newTasks = tasks.filter((task) => task.id !== id);
+ setTasks(newTasks);
+ } catch (error) {
+ handleApiError(error, "deleting task");
+ }
+ };
+
+ const updateTask = async (id, content, tasks) => {
+ try {
+ if (content === "") {
+ await deleteTask(id);
+ } else {
+ const response = await axiosInstance.put(`todo/${id}/`, { content });
+
+ const updatedTask = {
+ id,
+ columnId: response.data.list_board,
+ content: response.data.title,
+ };
+
+ handleTaskUpdate(tasks, updatedTask);
+ }
+ } catch (error) {
+ handleApiError(error, "updating task");
+ }
+ };
+
+ // ---------------- END Task Handlers ----------------
+
+ // ---------------- Fetch Data ----------------
useEffect(() => {
const fetchData = async () => {
try {
@@ -74,17 +142,22 @@ export function KanbanBoard() {
useEffect(() => {
const fetchBoardData = async () => {
try {
+ setLoading(true);
const response = await axiosInstance.get("boards/");
if (response.data && response.data.length > 0) {
setBoardData(response.data[0]);
}
} catch (error) {
console.error("Error fetching board data:", error);
+ setLoading(false);
}
+ setLoading(false);
};
fetchBoardData();
}, []);
+ // ---------------- END Fetch Data ----------------
+
return (
-
+
-
- {columns.map((col) => (
- task.columnId === col.id)}
- />
- ))}
-
+ {!isLoading ? (
+
+ {columns.map((col) => (
+ task.columnId === col.id)}
+ />
+ ))}{" "}
+
+ ) : (
+
+ )}
{createPortal(
- {activeColumn && (
- task.columnId === activeColumn.id)}
- />
- )}
- {activeTask && }
+ {/* Render the active task as a draggable overlay */}
+
,
document.body
)}
@@ -136,169 +202,46 @@ export function KanbanBoard() {
);
- function createTask(columnId, setTasks) {
- const newTaskData = {
- title: `Task ${tasks.length + 1}`,
- importance: 1,
- difficulty: 1,
- challenge: false,
- fromSystem: false,
- is_active: false,
- is_full_day_event: false,
- completed: false,
- priority: 1,
- list_board: columnId,
- };
-
- axiosInstance
- .post("todo/", newTaskData)
- .then((response) => {
- const newTask = {
- id: response.data.id,
- columnId,
- content: response.data.title,
- };
- })
- .catch((error) => {
- console.error("Error creating task:", error);
- });
- setTasks((tasks) => [...tasks, newTask]);
- }
-
- function deleteTask(id) {
- const newTasks = tasks.filter((task) => task.id !== id);
- axiosInstance
- .delete(`todo/${id}/`)
- .then((response) => {
- setTasks(newTasks);
- })
- .catch((error) => {
- console.error("Error deleting Task:", error);
- });
- setTasks(newTasks);
- }
-
- function updateTask(id, content) {
- const newTasks = tasks.map((task) => {
- if (task.id !== id) return task;
- return { ...task, content };
- });
- if (content === "") return deleteTask(id);
- setTasks(newTasks);
- }
-
- function createNewColumn() {
- axiosInstance
- .post("lists/", { name: `Column ${columns.length + 1}`, position: 1, board: boardId.id })
- .then((response) => {
- const newColumn = {
- id: response.data.id,
- title: response.data.name,
- };
-
- setColumns((prevColumns) => [...prevColumns, newColumn]);
- })
- .catch((error) => {
- console.error("Error creating ListBoard:", error);
- });
- }
-
- function deleteColumn(id) {
- axiosInstance
- .delete(`lists/${id}/`)
- .then((response) => {
- setColumns((prevColumns) => prevColumns.filter((col) => col.id !== id));
- })
- .catch((error) => {
- console.error("Error deleting ListBoard:", error);
- });
-
- const tasksToDelete = tasks.filter((t) => t.columnId === id);
-
- tasksToDelete.forEach((task) => {
- axiosInstance
- .delete(`todo/${task.id}/`)
- .then((response) => {
- setTasks((prevTasks) => prevTasks.filter((t) => t.id !== task.id));
- })
- .catch((error) => {
- console.error("Error deleting Task:", error);
- });
- });
- }
-
- function updateColumn(id, title) {
- // Update the column
- axiosInstance
- .patch(`lists/${id}/`, { name: title }) // Adjust the payload based on your API requirements
- .then((response) => {
- setColumns((prevColumns) => prevColumns.map((col) => (col.id === id ? { ...col, title } : col)));
- })
- .catch((error) => {
- console.error("Error updating ListBoard:", error);
- });
- }
-
+ // Handle the start of a drag event
function onDragStart(event) {
- if (event.active.data.current?.type === "Column") {
- setActiveColumn(event.active.data.current.column);
- return;
- }
-
+ // Check if the dragged item is a Task
if (event.active.data.current?.type === "Task") {
setActiveTask(event.active.data.current.task);
return;
}
}
+ // Handle the end of a drag event
function onDragEnd(event) {
- setActiveColumn(null);
+ // Reset active column and task after the drag ends
setActiveTask(null);
const { active, over } = event;
- if (!over) return;
+ if (!over) return; // If not dropped over anything, exit
const activeId = active.id;
const overId = over.id;
- const isActiveAColumn = active.data.current?.type === "Column";
const isActiveATask = active.data.current?.type === "Task";
const isOverAColumn = over.data.current?.type === "Column";
- const isOverATask = over.data.current?.type === "Task";
-
- // Reorder columns if the dragged item is a column
- if (isActiveAColumn && isOverAColumn) {
- setColumns((columns) => {
- const activeColumnIndex = columns.findIndex((col) => col.id === activeId);
- const overColumnIndex = columns.findIndex((col) => col.id === overId);
-
- const reorderedColumns = arrayMove(columns, activeColumnIndex, overColumnIndex);
-
- return reorderedColumns;
- });
- }
-
- // Reorder tasks within the same column
- if (isActiveATask && isOverATask) {
- setTasks((tasks) => {
- const activeIndex = tasks.findIndex((t) => t.id === activeId);
- const overIndex = tasks.findIndex((t) => t.id === overId);
-
- const reorderedTasks = arrayMove(tasks, activeIndex, overIndex);
-
- return reorderedTasks;
- });
- }
// Move tasks between columns and update columnId
if (isActiveATask && isOverAColumn) {
setTasks((tasks) => {
const activeIndex = tasks.findIndex((t) => t.id === activeId);
- tasks[activeIndex].columnId = overId;
+ // Extract the column ID from overId
+ const columnId = extractColumnId(overId);
+ tasks[activeIndex].columnId = columnId;
+
+ // API call to update task's columnId
axiosInstance
- .put(`todo/change_task_list_board/`, { todo_id: activeId, new_list_board_id: overId, new_index: 0 })
+ .put(`todo/change_task_list_board/`, {
+ todo_id: activeId,
+ new_list_board_id: over.data.current.task.columnId,
+ new_index: 0,
+ })
.then((response) => {})
.catch((error) => {
console.error("Error updating task columnId:", error);
@@ -309,40 +252,69 @@ export function KanbanBoard() {
}
}
+ // Helper function to extract the column ID from the element ID
+ function extractColumnId(elementId) {
+ // Implement logic to extract the column ID from elementId
+ // For example, if elementId is in the format "column-123", you can do:
+ const parts = elementId.split("-");
+ return parts.length === 2 ? parseInt(parts[1], 10) : null;
+ }
+
+ // Handle the drag-over event
function onDragOver(event) {
const { active, over } = event;
- if (!over) return;
+ if (!over) return; // If not over anything, exit
const activeId = active.id;
const overId = over.id;
- if (activeId === overId) return;
+ if (activeId === overId) return; // If over the same element, exit
const isActiveATask = active.data.current?.type === "Task";
const isOverATask = over.data.current?.type === "Task";
- if (!isActiveATask) return;
+ if (!isActiveATask) return; // If not dragging a Task, exit
+ // Reorder logic for Tasks within the same column
if (isActiveATask && isOverATask) {
setTasks((tasks) => {
const activeIndex = tasks.findIndex((t) => t.id === activeId);
const overIndex = tasks.findIndex((t) => t.id === overId);
+ // If moving to a different column, update columnId
if (tasks[activeIndex].columnId !== tasks[overIndex].columnId) {
tasks[activeIndex].columnId = tasks[overIndex].columnId;
return arrayMove(tasks, activeIndex, overIndex - 1);
}
-
+ axiosInstance
+ .put(`todo/change_task_list_board/`, {
+ todo_id: activeId,
+ new_list_board_id: over.data.current.task.columnId,
+ new_index: 0,
+ })
+ .then((response) => {})
+ .catch((error) => {
+ console.error("Error updating task columnId:", error);
+ });
return arrayMove(tasks, activeIndex, overIndex);
});
}
const isOverAColumn = over.data.current?.type === "Column";
-
- if (isActiveATask && isOverAColumn) {
+ // Move the Task to a different column and update columnId
+ if (isActiveATask && isOverAColumn && tasks.some((task) => task.columnId !== overId)) {
setTasks((tasks) => {
const activeIndex = tasks.findIndex((t) => t.id === activeId);
-
+ axiosInstance
+ .put(`todo/change_task_list_board/`, {
+ todo_id: activeId,
+ new_list_board_id: over.data.current.task.columnId,
+ new_index: 0,
+ })
+ .then((response) => {})
+ .catch((error) => {
+ console.error("Error updating task columnId:", error);
+ });
tasks[activeIndex].columnId = overId;
return arrayMove(tasks, activeIndex, activeIndex);
});
diff --git a/frontend/src/components/kanbanBoard/kanbanPage.jsx b/frontend/src/components/kanbanBoard/kanbanPage.jsx
index 9225ae7..92a8536 100644
--- a/frontend/src/components/kanbanBoard/kanbanPage.jsx
+++ b/frontend/src/components/kanbanBoard/kanbanPage.jsx
@@ -19,17 +19,16 @@ export const KanbanPage = () => {
onClick={() => handleTabClick("kanban")}>
Kanban
- handleTabClick("table")}>
Table
-
+ */}
-
);
};
diff --git a/frontend/src/components/navigations/IconSideNav.jsx b/frontend/src/components/navigations/IconSideNav.jsx
index 02cc1a0..9f9ad4b 100644
--- a/frontend/src/components/navigations/IconSideNav.jsx
+++ b/frontend/src/components/navigations/IconSideNav.jsx
@@ -9,9 +9,9 @@ const menuItems = [
{ id: 0, path: "/", icon: },
{ id: 1, path: "/tasks", icon: },
{ id: 2, path: "/calendar", icon: },
- { id: 3, path: "/settings", icon: },
- { id: 4, path: "/priority", icon: },
+ { id: 3, path: "/priority", icon: },
];
+// { id: 3, path: "/settings", icon: },
export const SideNav = () => {
const [selected, setSelected] = useState(0);
@@ -32,12 +32,12 @@ export const SideNav = () => {
);
};
-const NavItem = ({ icon, selected, id, setSelected, logo, path }) => {
+const NavItem = ({ icon, selected, id, setSelected, path }) => {
const navigate = useNavigate();
return (
{
setSelected(id);
navigate(path);
diff --git a/frontend/src/components/navigations/Navbar.jsx b/frontend/src/components/navigations/Navbar.jsx
index 46dd45e..22120b3 100644
--- a/frontend/src/components/navigations/Navbar.jsx
+++ b/frontend/src/components/navigations/Navbar.jsx
@@ -25,14 +25,14 @@ export function NavBar() {
-
-
-
+ {/*
+ Sync Data
+
*/}
{isAuthenticated ? (
-
+