diff --git a/tasker-client/src/app/(components)/Header/index.tsx b/tasker-client/src/app/(components)/Header/index.tsx
new file mode 100644
index 0000000..1c7cd9d
--- /dev/null
+++ b/tasker-client/src/app/(components)/Header/index.tsx
@@ -0,0 +1,23 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import React from "react";
+
+type Props = {
+ name: string;
+ buttonComponent?: any;
+ isSmallText?: boolean;
+};
+
+const Header = ({ name, buttonComponent, isSmallText = false }: Props) => {
+ return (
+
+
+ {name}
+
+ {buttonComponent}
+
+ );
+};
+
+export default Header;
diff --git a/tasker-client/src/app/globals.css b/tasker-client/src/app/globals.css
index 01e9907..ab91f01 100644
--- a/tasker-client/src/app/globals.css
+++ b/tasker-client/src/app/globals.css
@@ -15,6 +15,6 @@ body,
height: 100%;
width: 100%;
@apply text-sm;
- @apply bg-gray-500;
- @apply text-gray-900;
+ @apply bg-white;
+ @apply dark:bg-black;
}
diff --git a/tasker-client/src/app/projects/BoardView/index.tsx b/tasker-client/src/app/projects/BoardView/index.tsx
new file mode 100644
index 0000000..679ed3d
--- /dev/null
+++ b/tasker-client/src/app/projects/BoardView/index.tsx
@@ -0,0 +1,263 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import React from "react";
+import { useGetTasksQuery, useUpdateTaskStatusMutation } from "@/state/api";
+import { DndProvider, useDrag, useDrop } from "react-dnd";
+import { HTML5Backend } from "react-dnd-html5-backend";
+import { Task as TaskType } from "@/state/api";
+import { EllipsisVertical, MessageSquareMore, Plus } from "lucide-react";
+import { format } from "date-fns";
+import Image from "next/image";
+
+type BoardProps = {
+ id: string;
+ setIsModalNewTaskOpen: (isOpen: boolean) => void;
+};
+
+const taskStatuses = ["Backlog", "In Progress", "Test/Review", "Done"];
+
+function BoardView({ id, setIsModalNewTaskOpen }: BoardProps) {
+ const {
+ data: tasks,
+ isLoading,
+ error,
+ } = useGetTasksQuery({ projectId: Number(id) });
+
+ const [updateTaskStatus] = useUpdateTaskStatusMutation();
+
+ const moveTask = (taskId: number, status: string) => {
+ updateTaskStatus({ taskId, status });
+ };
+
+ if (isLoading) return Loading...
;
+ if (error) return Error fetching tasks
;
+
+ return (
+
+
+ {taskStatuses.map((status) => (
+
+ ))}
+
+
+ );
+}
+
+type TaskColumnProps = {
+ status: string;
+ tasks: TaskType[];
+ moveTask: (taskId: number, toStatus: string) => void;
+ setIsModalNewTaskOpen: (isOpen: boolean) => void;
+};
+
+const TaskColumn = ({
+ status,
+ tasks,
+ moveTask,
+ setIsModalNewTaskOpen,
+}: TaskColumnProps) => {
+ const [{ isOver }, drop] = useDrop(() => ({
+ accept: "task",
+ drop: (item: { id: number }) => {
+ moveTask(item.id, status);
+ },
+ collect: (monitor) => ({
+ isOver: !!monitor.isOver(),
+ }),
+ }));
+
+ const tasksCount = tasks.filter((task) => task.status === status).length;
+
+ const statusColors: any = {
+ Backlog: "#800000",
+ "In Progress": "#efcc00",
+ "Test/Review": "#00008b",
+ Done: "#006400",
+ };
+
+ return (
+ {
+ drop(instance);
+ }}
+ className={`sl:py-4 rounded-lg py-2 xl:px-2 ${isOver ? "bg-blue-100 dark:bg-neutral-950" : ""}`}
+ >
+
+
+
+
+ {status}{" "}
+
+ {tasksCount}
+
+
+
+
+
+
+
+
+
+ {tasks
+ .filter((task) => task.status === status)
+ .map((task) => (
+
+ ))}
+
+ );
+};
+
+type TaskProps = {
+ task: TaskType;
+};
+
+const Task = ({ task }: TaskProps) => {
+ const [{ isDragging }, drag] = useDrag(() => ({
+ type: "task",
+ item: { id: task.id },
+ collect: (monitor: any) => ({
+ isDragging: !!monitor.isDragging(),
+ }),
+ }));
+
+ const taskTagsSplit = task.tags ? task.tags.split(",") : [];
+
+ const formattedStartDate = task.startDate
+ ? format(new Date(task.startDate), "P")
+ : "";
+ const formattedDueDate = task.dueDate
+ ? format(new Date(task.dueDate), "P")
+ : "";
+
+ const numberOfComments = (task.comments && task.comments.length) || 0;
+
+ const PriorityTag = ({ priority }: { priority: TaskType["priority"] }) => (
+
+ {priority}
+
+ );
+
+ return (
+ {
+ drag(instance);
+ }}
+ className={`mb-4 rounded-md bg-white shadow dark:bg-dark-secondary ${
+ isDragging ? "opacity-50" : "opacity-100"
+ }`}
+ >
+ {task.attachments && task.attachments.length > 0 && (
+
+ )}
+
+
+
+ {task.priority &&
}
+
+ {taskTagsSplit.map((tag) => (
+
+ {" "}
+ {tag}
+
+ ))}
+
+
+
+
+
+
+
{task.title}
+ {typeof task.points === "number" && (
+
+ {task.points} pts
+
+ )}
+
+
+
+ {formattedStartDate && {formattedStartDate} - }
+ {formattedDueDate && {formattedDueDate}}
+
+
+ {task.description}
+
+
+
+ {/* Users */}
+
+
+ {task.assignee && (
+
+ )}
+ {task.author && (
+
+ )}
+
+
+
+
+ {numberOfComments}
+
+
+
+
+
+ );
+};
+
+export default BoardView;
diff --git a/tasker-client/src/app/projects/ProjectHeader.tsx b/tasker-client/src/app/projects/ProjectHeader.tsx
new file mode 100644
index 0000000..d555f59
--- /dev/null
+++ b/tasker-client/src/app/projects/ProjectHeader.tsx
@@ -0,0 +1,109 @@
+import {
+ Clock,
+ Filter,
+ Grid3x3,
+ List,
+ PlusSquare,
+ Share2,
+ Table,
+} from "lucide-react";
+import React, { useState } from "react";
+import Header from "../(components)/Header";
+
+type Props = {
+ activeTab: string;
+ setActiveTab: (tab: string) => void;
+};
+
+const ProjectHeader = ({ activeTab, setActiveTab }: Props) => {
+ const [isModalNewProjectOpen, setIsModalNewProjectOpen] = useState(false);
+
+ return (
+
+
+
setIsModalNewProjectOpen(true)}
+ >
+ New Boards
+
+ }
+ />
+
+
+ {/* TABS */}
+
+
+ }
+ setActiveTab={setActiveTab}
+ activeTab={activeTab}
+ />
+ }
+ setActiveTab={setActiveTab}
+ activeTab={activeTab}
+ />
+ }
+ setActiveTab={setActiveTab}
+ activeTab={activeTab}
+ />
+ }
+ setActiveTab={setActiveTab}
+ activeTab={activeTab}
+ />
+
+
+
+
+ );
+};
+
+type TabButtonProps = {
+ name: string;
+ icon: React.ReactNode;
+ setActiveTab: (tabName: string) => void;
+ activeTab: string;
+};
+
+const TabButton = ({ name, icon, setActiveTab, activeTab }: TabButtonProps) => {
+ const isActive = activeTab === name;
+
+ return (
+
+ );
+};
+
+export default ProjectHeader;
diff --git a/tasker-client/src/app/projects/[id]/page.tsx b/tasker-client/src/app/projects/[id]/page.tsx
new file mode 100644
index 0000000..c43a37f
--- /dev/null
+++ b/tasker-client/src/app/projects/[id]/page.tsx
@@ -0,0 +1,42 @@
+"use client";
+
+import React, { useState } from "react";
+import ProjectHeader from "@/app/projects/ProjectHeader";
+import BoardView from "@/app/projects/BoardView";
+
+type Props = {
+ params: { id: string };
+};
+
+const Project = ({ params }: Props) => {
+ const { id } = params;
+ const [activeTab, setActiveTab] = useState("Board");
+ const [isModalNewTaskOpen, setIsModalNewTaskOpen] = useState(false);
+
+ return (
+
+ {/*
setIsModalNewTaskOpen(false)}
+ id={id}
+ /> */}
+
+ {
+ activeTab === "Board" && (
+
+ )
+ // {activeTab === "List" && (
+ //
+ // )}
+ // {activeTab === "Timeline" && (
+ //
+ // )}
+ // {activeTab === "Table" && (
+ //
+ // )}
+ }
+
+ );
+};
+
+export default Project;
diff --git a/tasker-client/src/state/api.ts b/tasker-client/src/state/api.ts
index 87d580d..2e30af5 100644
--- a/tasker-client/src/state/api.ts
+++ b/tasker-client/src/state/api.ts
@@ -1,10 +1,151 @@
-import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query";
+import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
+
+export interface Project {
+ id: number;
+ name: string;
+ description?: string;
+ startDate?: string;
+ endDate?: string;
+}
+
+export enum Priority {
+ Urgent = "Urgent",
+ High = "High",
+ Medium = "Medium",
+ Low = "Low",
+ Backlog = "Backlog",
+}
+
+export enum Status {
+ Backlog = "Backlog",
+ InProgress = "In Progress",
+ TestReview = "Test/Review",
+ Done = "Done",
+}
+
+export interface User {
+ userId?: number;
+ username: string;
+ email: string;
+ profilePictureUrl?: string;
+ cognitoId?: string;
+ teamId?: number;
+}
+
+export interface Attachment {
+ id: number;
+ fileURL: string;
+ fileName: string;
+ taskId: number;
+ uploadedById: number;
+}
+
+export interface Task {
+ id: number;
+ title: string;
+ description?: string;
+ status?: Status;
+ priority?: Priority;
+ tags?: string;
+ startDate?: string;
+ dueDate?: string;
+ points?: number;
+ projectId: number;
+ authorUserId?: number;
+ assignedUserId?: number;
+
+ author?: User;
+ assignee?: User;
+ comments?: Comment[];
+ attachments?: Attachment[];
+}
+
+export interface SearchResults {
+ tasks?: Task[];
+ projects?: Project[];
+ users?: User[];
+}
+
+export interface Team {
+ teamId: number;
+ teamName: string;
+ productOwnerUserId?: number;
+ projectManagerUserId?: number;
+}
export const api = createApi({
- baseQuery: fetchBaseQuery({ baseUrl: process.env.NEXT_PUBLIC_API_BASE_URL }),
+ baseQuery: fetchBaseQuery({
+ baseUrl: process.env.NEXT_PUBLIC_API_BASE_URL,
+ }),
reducerPath: "api",
- tagTypes: ["Task"],
- endpoints: (builder) => ({}),
+ tagTypes: ["Projects", "Tasks", "Users", "Teams"],
+ endpoints: (build) => ({
+ getProjects: build.query({
+ query: () => "projects",
+ providesTags: ["Projects"],
+ }),
+ createProject: build.mutation>({
+ query: (project) => ({
+ url: "projects",
+ method: "POST",
+ body: project,
+ }),
+ invalidatesTags: ["Projects"],
+ }),
+ getTasks: build.query({
+ query: ({ projectId }) => `tasks?projectId=${projectId}`,
+ providesTags: (result) =>
+ result
+ ? result.map(({ id }) => ({ type: "Tasks" as const, id }))
+ : [{ type: "Tasks" as const }],
+ }),
+ getTasksByUser: build.query({
+ query: (userId) => `tasks/user/${userId}`,
+ providesTags: (result, error, userId) =>
+ result
+ ? result.map(({ id }) => ({ type: "Tasks", id }))
+ : [{ type: "Tasks", id: userId }],
+ }),
+ createTask: build.mutation>({
+ query: (task) => ({
+ url: "tasks",
+ method: "POST",
+ body: task,
+ }),
+ invalidatesTags: ["Tasks"],
+ }),
+ updateTaskStatus: build.mutation({
+ query: ({ taskId, status }) => ({
+ url: `tasks/${taskId}/status`,
+ method: "PATCH",
+ body: { status },
+ }),
+ invalidatesTags: (result, error, { taskId }) => [
+ { type: "Tasks", id: taskId },
+ ],
+ }),
+ getUsers: build.query({
+ query: () => "users",
+ providesTags: ["Users"],
+ }),
+ getTeams: build.query({
+ query: () => "teams",
+ providesTags: ["Teams"],
+ }),
+ search: build.query({
+ query: (query) => `search?query=${query}`,
+ }),
+ }),
});
-export const {} = api;
+export const {
+ useGetProjectsQuery,
+ useCreateProjectMutation,
+ useGetTasksQuery,
+ useCreateTaskMutation,
+ useUpdateTaskStatusMutation,
+ useSearchQuery,
+ useGetUsersQuery,
+ useGetTeamsQuery,
+ useGetTasksByUserQuery,
+} = api;