Merge pull request #67 from TurTaskProject/main

Deploy better Kanban
This commit is contained in:
Sirin Puenggun 2023-11-23 21:02:43 +07:00 committed by GitHub
commit f46ce0a4c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 239 additions and 287 deletions

View File

@ -8,8 +8,10 @@ from users.models import CustomUser
def create_default_board(sender, instance, created, **kwargs): def create_default_board(sender, instance, created, **kwargs):
"""Signal handler to automatically create a default Board for a user upon creation.""" """Signal handler to automatically create a default Board for a user upon creation."""
if created: if created:
board = Board.objects.create(user=instance, name="My Default Board") # Create unique board by user id
user_id = instance.id
ListBoard.objects.create(board=board, name="Todo", position=1) board = Board.objects.create(user=instance, name=f"Board of #{user_id}")
ListBoard.objects.create(board=board, name="In Progress", position=2) ListBoard.objects.create(board=board, name="Backlog", position=1)
ListBoard.objects.create(board=board, name="Done", position=3) 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)

View File

@ -27,15 +27,63 @@ def update_priority(sender, instance, **kwargs):
instance.priority = Todo.EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT instance.priority = Todo.EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT
@receiver(post_save, sender=Todo) # @receiver(post_save, sender=Todo)
def assign_todo_to_listboard(sender, instance, created, **kwargs): # 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.""" # """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: if created:
user_board = instance.user.board_set.first() list_board_position = instance.position
if user_board: if list_board_position == 1:
first_list_board = user_board.listboard_set.order_by('position').first() 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: for task_data in placeholder_tasks:
instance.list_board = first_list_board Todo.objects.create(
instance.save() 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(),
)

View File

@ -8,14 +8,7 @@ export function FloatingParticles() {
}, []); }, []);
return ( return (
<div <div style={{ width: "0%", height: "100vh" }}>
style={{
position: "absolute",
width: "100%",
height: "100vh",
zIndex: 0,
backgroundColor: "#EBF2FA",
}}>
<Particles <Particles
id="particles" id="particles"
init={particlesInit} init={particlesInit}

View File

@ -65,8 +65,15 @@ export function LoginPage() {
return ( return (
<div> <div>
<NavPreLogin text="Don't have account?" btn_text="Sign Up" link="/signup" /> <NavPreLogin
<div className="flex flex-row bg-neutral-400"> text="Don't have account?"
btn_text="Sign Up"
link="/signup"
/>
<div className="h-screen flex items-center justify-center bg-gradient-to-r from-zinc-100 via-gray-200 to-zinc-100">
{/* Particles Container */}
<FloatingParticles />
{/* Login Box */} {/* Login Box */}
<div className="flex items-center justify-center flex-1 z-50"> <div className="flex items-center justify-center flex-1 z-50">
<div className="w-100 bg-white border-solid rounded-lg p-8 shadow space-y-4"> <div className="w-100 bg-white border-solid rounded-lg p-8 shadow space-y-4">
@ -78,7 +85,8 @@ export function LoginPage() {
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
className="stroke-current shrink-0 h-6 w-6" className="stroke-current shrink-0 h-6 w-6"
fill="none" fill="none"
viewBox="0 0 24 24"> viewBox="0 0 24 24"
>
<path <path
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
@ -122,20 +130,23 @@ export function LoginPage() {
/> />
</div> </div>
{/* Login Button */} {/* Login Button */}
<button className="btn bg-blue-700 hover:bg-blue-900 w-full text-white font-bold" onClick={handleSubmit}> <button
className="btn bg-blue-700 hover:bg-blue-900 w-full text-white font-bold"
onClick={handleSubmit}
>
Login Login
</button> </button>
<div className="divider">OR</div> <div className="divider">OR</div>
{/* Login with Google Button */} {/* Login with Google Button */}
<button className="btn bg-gray-200 btn-outline w-full " onClick={() => googleLoginImplicit()}> <button
className="btn bg-gray-200 btn-outline w-full "
onClick={() => googleLoginImplicit()}
>
<FcGoogle className="rounded-full bg-white" /> <FcGoogle className="rounded-full bg-white" />
Login with Google Login with Google
</button> </button>
</div> </div>
</div> </div>
<div className="basis-1/2 bg-#ebf2fa h-screen z-0">
<FloatingParticles />
</div>
</div> </div>
</div> </div>
); );

View File

@ -5,6 +5,7 @@ import { useGoogleLogin } from "@react-oauth/google";
import { NavPreLogin } from "../navigations/NavPreLogin"; import { NavPreLogin } from "../navigations/NavPreLogin";
import { useAuth } from "src/hooks/AuthHooks"; import { useAuth } from "src/hooks/AuthHooks";
import { createUser, googleLogin } from "src/api/AuthenticationApi"; import { createUser, googleLogin } from "src/api/AuthenticationApi";
import { FloatingParticles } from "../FlaotingParticles";
export function SignUp() { export function SignUp() {
const Navigate = useNavigate(); const Navigate = useNavigate();
@ -55,6 +56,8 @@ export function SignUp() {
const googleLoginImplicit = useGoogleLogin({ const googleLoginImplicit = useGoogleLogin({
flow: "auth-code", flow: "auth-code",
redirect_uri: "postmessage", 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) => { onSuccess: async (response) => {
try { try {
const loginResponse = await googleLogin(response.code); const loginResponse = await googleLogin(response.code);
@ -76,9 +79,13 @@ export function SignUp() {
return ( return (
<div> <div>
<NavPreLogin text="Already have an account?" btn_text="Log In" link="/login" /> <NavPreLogin
text="Already have an account?"
btn_text="Log In"
link="/login"
/>
<div className="h-screen flex items-center justify-center bg-gradient-to-r from-zinc-100 via-gray-200 to-zinc-100"> <div className="h-screen flex items-center justify-center bg-gradient-to-r from-zinc-100 via-gray-200 to-zinc-100">
{/* ... (other code) */} <FloatingParticles />
<div className="w-1/4 h-1 flex items-center justify-center z-10"> <div className="w-1/4 h-1 flex items-center justify-center z-10">
<div className="w-96 bg-white rounded-lg p-8 space-y-4 z-10"> <div className="w-96 bg-white rounded-lg p-8 space-y-4 z-10">
{/* Register Form */} {/* Register Form */}
@ -136,7 +143,10 @@ export function SignUp() {
</button> </button>
<div className="divider">OR</div> <div className="divider">OR</div>
{/* Login with Google Button */} {/* Login with Google Button */}
<button className="btn btn-outline btn-secondary w-full " onClick={() => googleLoginImplicit()}> <button
className="btn btn-outline btn-secondary w-full "
onClick={() => googleLoginImplicit()}
>
<FcGoogle className="rounded-full bg-white" /> <FcGoogle className="rounded-full bg-white" />
Login with Google Login with Google
</button> </button>

View File

@ -1,53 +1,16 @@
import { SortableContext, useSortable } from "@dnd-kit/sortable"; import { SortableContext, useSortable } from "@dnd-kit/sortable";
import { BsFillTrashFill } from "react-icons/bs";
import { AiOutlinePlusCircle } from "react-icons/ai"; import { AiOutlinePlusCircle } from "react-icons/ai";
import { CSS } from "@dnd-kit/utilities"; import { useMemo } from "react";
import { useMemo, useState } from "react";
import { TaskCard } from "./taskCard"; import { TaskCard } from "./taskCard";
export function ColumnContainer({ column, deleteColumn, updateColumn, createTask, tasks, deleteTask, updateTask }) { export function ColumnContainer({ column, createTask, tasks, deleteTask, updateTask }) {
const [editMode, setEditMode] = useState(false); // Memoize task IDs to prevent unnecessary recalculations
const tasksIds = useMemo(() => { const tasksIds = useMemo(() => {
return tasks.map((task) => task.id); return tasks.map((task) => task.id);
}, [tasks]); }, [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 (
<div
ref={setNodeRef}
style={style}
className="
opacity-40
border-2
border-blue-500
w-[350px]
max-h-[400px]
rounded-md
flex
flex-col
"></div>
);
}
return ( return (
<div <div
ref={setNodeRef}
style={style}
className=" className="
bg-[#f1f2f4] bg-[#f1f2f4]
w-[280px] w-[280px]
@ -58,62 +21,35 @@ export function ColumnContainer({ column, deleteColumn, updateColumn, createTask
"> ">
{/* Column title */} {/* Column title */}
<div <div
{...attributes}
{...listeners}
onClick={() => {
setEditMode(true);
}}
className=" className="
ml-3 ml-3
text-md text-md
cursor-grab
font-bold font-bold
flex flex
items-center items-center
justify-between justify-between
"> ">
<div className="flex gap-2"> <div className="flex gap-2">{column.title}</div>
{!editMode && column.title}
{editMode && (
<input
className="bg-gray-200 focus:border-blue-500 border rounded-md outline-none px-2"
value={column.title}
onChange={(e) => updateColumn(column.id, e.target.value)}
autoFocus
onBlur={() => {
setEditMode(false);
}}
onKeyDown={(e) => {
if (e.key !== "Enter") return;
setEditMode(false);
}}
/>
)}
</div>
<button
onClick={() => {
deleteColumn(column.id);
}}
className="
stroke-gray-500
hover:stroke-white
hover:bg-columnBackgroundColor
rounded
px-1
py-2
">
<BsFillTrashFill />
</button>
</div> </div>
{/* Column task container */} {/* Column task container */}
<div className="flex flex-grow flex-col gap-2 p-1 overflow-x-hidden overflow-y-auto"> <div className="flex flex-grow flex-col gap-2 p-1 overflow-x-hidden overflow-y-auto">
{/* Provide a SortableContext for the tasks within the column */}
<SortableContext items={tasksIds}> <SortableContext items={tasksIds}>
{/* Render TaskCard for each task in the column */}
{tasks.map((task) => ( {tasks.map((task) => (
<TaskCard key={task.id} task={task} deleteTask={deleteTask} updateTask={updateTask} /> <TaskCard
key={task.id}
task={task}
deleteTask={deleteTask}
updateTask={updateTask}
// Adjust the useSortable hook for tasks to enable dragging
useSortable={(props) => useSortable({ ...props, disabled: false })}
/>
))} ))}
</SortableContext> </SortableContext>
</div> </div>
{/* Column footer */} {/* Column footer */}
<button <button
className="flex gap-2 items-center rounded-md p-2 my-2 hover:bg-zinc-200 active:bg-zinc-400" className="flex gap-2 items-center rounded-md p-2 my-2 hover:bg-zinc-200 active:bg-zinc-400"

View File

@ -1,12 +1,10 @@
import { ColumnContainer } from "./columnContainer"; import { ColumnContainer } from "./columnContainer";
export function ColumnContainerCard({ column, deleteColumn, updateColumn, createTask, tasks, deleteTask, updateTask }) { export function ColumnContainerCard({ column, createTask, tasks, deleteTask, updateTask }) {
return ( return (
<div className="card bg-[#f1f2f4] shadow p-1 my-2 border-2"> <div className="card bg-[#f1f2f4] shadow border p-1 my-2">
<ColumnContainer <ColumnContainer
column={column} column={column}
deleteColumn={deleteColumn}
updateColumn={updateColumn}
createTask={createTask} createTask={createTask}
tasks={tasks} tasks={tasks}
deleteTask={deleteTask} deleteTask={deleteTask}

View File

@ -8,14 +8,13 @@ import { axiosInstance } from "src/api/AxiosConfig";
export function KanbanBoard() { export function KanbanBoard() {
const [columns, setColumns] = useState([]); const [columns, setColumns] = useState([]);
const columnsId = useMemo(() => columns.map((col) => col.id), [columns]);
const [boardId, setBoardData] = useState(); const [boardId, setBoardData] = useState();
const [isLoading, setLoading] = useState(false);
const [tasks, setTasks] = useState([]); const [tasks, setTasks] = useState([]);
const [activeColumn, setActiveColumn] = useState(null);
const [activeTask, setActiveTask] = useState(null); const [activeTask, setActiveTask] = useState(null);
const columnsId = useMemo(() => columns.map((col) => col.id), [columns]);
// ---------------- END STATE INITIATE ----------------
const sensors = useSensors( const sensors = useSensors(
useSensor(PointerSensor, { 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(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
try { try {
@ -74,17 +142,22 @@ export function KanbanBoard() {
useEffect(() => { useEffect(() => {
const fetchBoardData = async () => { const fetchBoardData = async () => {
try { try {
setLoading(true);
const response = await axiosInstance.get("boards/"); const response = await axiosInstance.get("boards/");
if (response.data && response.data.length > 0) { if (response.data && response.data.length > 0) {
setBoardData(response.data[0]); setBoardData(response.data[0]);
} }
} catch (error) { } catch (error) {
console.error("Error fetching board data:", error); console.error("Error fetching board data:", error);
setLoading(false);
} }
setLoading(false);
}; };
fetchBoardData(); fetchBoardData();
}, []); }, []);
// ---------------- END Fetch Data ----------------
return ( return (
<div <div
className=" className="
@ -92,43 +165,36 @@ export function KanbanBoard() {
flex flex
w-full w-full
items-center items-center
justify-center
overflow-x-auto overflow-x-auto
overflow-y-hidden overflow-y-hidden
"> ">
<DndContext sensors={sensors} onDragStart={onDragStart} onDragEnd={onDragEnd} onDragOver={onDragOver}> <DndContext sensors={sensors} onDragStart={onDragStart} onDragEnd={onDragEnd} onDragOver={onDragOver}>
<div className="ml-2 flex gap-4"> <div className="flex gap-4">
<div className="flex gap-4"> <div className="flex gap-4">
<SortableContext items={columnsId}> {!isLoading ? (
{columns.map((col) => ( <SortableContext items={columnsId}>
<ColumnContainerCard {columns.map((col) => (
key={col.id} <ColumnContainerCard
column={col} key={col.id}
deleteColumn={deleteColumn} column={col}
updateColumn={updateColumn} createTask={createTask}
createTask={createTask} deleteTask={deleteTask}
deleteTask={deleteTask} updateTask={updateTask}
updateTask={updateTask} tasks={(tasks || []).filter((task) => task.columnId === col.id)}
tasks={tasks.filter((task) => task.columnId === col.id)} />
/> ))}{" "}
))} </SortableContext>
</SortableContext> ) : (
<span className="loading loading-dots loading-lg"></span>
)}
</div> </div>
</div> </div>
{createPortal( {createPortal(
<DragOverlay className="bg-white" dropAnimation={null} zIndex={20}> <DragOverlay className="bg-white" dropAnimation={null} zIndex={20}>
{activeColumn && ( {/* Render the active task as a draggable overlay */}
<ColumnContainerCard <TaskCard task={activeTask} deleteTask={deleteTask} updateTask={updateTask} />
column={activeColumn}
deleteColumn={deleteColumn}
updateColumn={updateColumn}
createTask={createTask}
deleteTask={deleteTask}
updateTask={updateTask}
tasks={tasks.filter((task) => task.columnId === activeColumn.id)}
/>
)}
{activeTask && <TaskCard task={activeTask} deleteTask={deleteTask} updateTask={updateTask} />}
</DragOverlay>, </DragOverlay>,
document.body document.body
)} )}
@ -136,149 +202,31 @@ export function KanbanBoard() {
</div> </div>
); );
function createTask(columnId, setTasks) { // Handle the start of a drag event
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);
});
}
function onDragStart(event) { function onDragStart(event) {
if (event.active.data.current?.type === "Column") { // Check if the dragged item is a Task
setActiveColumn(event.active.data.current.column);
return;
}
if (event.active.data.current?.type === "Task") { if (event.active.data.current?.type === "Task") {
setActiveTask(event.active.data.current.task); setActiveTask(event.active.data.current.task);
return; return;
} }
} }
// Handle the end of a drag event
function onDragEnd(event) { function onDragEnd(event) {
setActiveColumn(null); // Reset active column and task after the drag ends
setActiveTask(null); setActiveTask(null);
const { active, over } = event; const { active, over } = event;
if (!over) return; if (!over) return; // If not dropped over anything, exit
const activeId = active.id; const activeId = active.id;
const overId = over.id; const overId = over.id;
const isActiveAColumn = active.data.current?.type === "Column";
const isActiveATask = active.data.current?.type === "Task"; const isActiveATask = active.data.current?.type === "Task";
const isOverAColumn = over.data.current?.type === "Column"; const isOverAColumn = over.data.current?.type === "Column";
const isOverATask = over.data.current?.type === "Task"; const isOverATask = over.data.current?.type === "Task";
// Reorder columns if the dragged item is a column // Reorder logic for Tasks within the same 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) { if (isActiveATask && isOverATask) {
setTasks((tasks) => { setTasks((tasks) => {
const activeIndex = tasks.findIndex((t) => t.id === activeId); const activeIndex = tasks.findIndex((t) => t.id === activeId);
@ -297,6 +245,7 @@ export function KanbanBoard() {
tasks[activeIndex].columnId = overId; tasks[activeIndex].columnId = overId;
// API call to update task's columnId
axiosInstance 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: overId, new_index: 0 })
.then((response) => {}) .then((response) => {})
@ -309,25 +258,28 @@ export function KanbanBoard() {
} }
} }
// Handle the drag-over event
function onDragOver(event) { function onDragOver(event) {
const { active, over } = event; const { active, over } = event;
if (!over) return; if (!over) return; // If not over anything, exit
const activeId = active.id; const activeId = active.id;
const overId = over.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 isActiveATask = active.data.current?.type === "Task";
const isOverATask = over.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) { if (isActiveATask && isOverATask) {
setTasks((tasks) => { setTasks((tasks) => {
const activeIndex = tasks.findIndex((t) => t.id === activeId); const activeIndex = tasks.findIndex((t) => t.id === activeId);
const overIndex = tasks.findIndex((t) => t.id === overId); const overIndex = tasks.findIndex((t) => t.id === overId);
// If moving to a different column, update columnId
if (tasks[activeIndex].columnId !== tasks[overIndex].columnId) { if (tasks[activeIndex].columnId !== tasks[overIndex].columnId) {
tasks[activeIndex].columnId = tasks[overIndex].columnId; tasks[activeIndex].columnId = tasks[overIndex].columnId;
return arrayMove(tasks, activeIndex, overIndex - 1); return arrayMove(tasks, activeIndex, overIndex - 1);
@ -338,8 +290,8 @@ export function KanbanBoard() {
} }
const isOverAColumn = over.data.current?.type === "Column"; const isOverAColumn = over.data.current?.type === "Column";
// Move the Task to a different column and update columnId
if (isActiveATask && isOverAColumn) { if (isActiveATask && isOverAColumn && tasks.some((task) => task.columnId !== overId)) {
setTasks((tasks) => { setTasks((tasks) => {
const activeIndex = tasks.findIndex((t) => t.id === activeId); const activeIndex = tasks.findIndex((t) => t.id === activeId);

View File

@ -29,7 +29,6 @@ export const KanbanPage = () => {
</div> </div>
</div> </div>
<KanbanBoard /> <KanbanBoard />
<div className="flex justify-center border-2 "></div>
</div> </div>
); );
}; };

View File

@ -2,13 +2,10 @@ import { FloatingParticles } from "../FlaotingParticles";
export function LandingPage() { export function LandingPage() {
return ( return (
<div> <div className="h-screen flex items-center justify-center bg-gradient-to-r from-zinc-100 via-gray-200 to-zinc-100">
{/* Particles Container */} {/* Particles Container */}
<FloatingParticles /> <FloatingParticles />
{/* Navbar */} {/* Navbar */}
<div className="navbar bg-white z-10">
<div className="navbar-end space-x-3 z-10"></div>
</div>
<div className="relative" id="home"> <div className="relative" id="home">
<div className="max-w-7xl mx-auto px-6 md:px-12 xl:px-6"> <div className="max-w-7xl mx-auto px-6 md:px-12 xl:px-6">
<div className="relative pt-36 ml-auto"> <div className="relative pt-36 ml-auto">
@ -24,12 +21,18 @@ export function LandingPage() {
</label> </label>
</span> </span>
</h1> </h1>
<p className="mt-8 text-#143D6C">Unleash productivity with our personal task and project management.</p> <p className="mt-8 text-#143D6C">
Unleash productivity with our personal task and project
management.
</p>
<div className="mt-8 flex flex-wrap justify-center gap-y-4 gap-x-6"> <div className="mt-8 flex flex-wrap justify-center gap-y-4 gap-x-6">
<a <a
href="/login" href="/login"
className="relative flex h-11 w-full items-center justify-center px-6 before:absolute before:inset-0 before:rounded-full before:bg-primary before:transition before:duration-300 hover:before:scale-105 active:duration-75 active:before:scale-95 sm:w-max"> className="relative flex h-11 w-full items-center justify-center px-6 before:absolute before:inset-0 before:rounded-full before:bg-primary before:transition before:duration-300 hover:before:scale-105 active:duration-75 active:before:scale-95 sm:w-max"
<span className="relative text-base font-semibold text-white">Get started</span> >
<span className="relative text-base font-semibold text-white">
Get started
</span>
</a> </a>
</div> </div>
</div> </div>

View File

@ -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(); const navigate = useNavigate();
return ( return (
<motion.button <motion.button
className="p-3 text-xl bg-slate-800 hover-bg-slate-700 rounded-md transition-colors relative" className="p-3 text-xl text-white bg-slate-800 hover-bg-slate-700 rounded-md transition-colors relative"
onClick={() => { onClick={() => {
setSelected(id); setSelected(id);
navigate(path); navigate(path);