Repository: SashenJayathilaka/ChatGPT-Clone
Branch: master
Commit: 630988faa7ee
Files: 160
Total size: 266.0 KB
Directory structure:
gitextract__q2wgi47/
├── README.md
├── aws-bucket-policy.json
├── client/
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── .prettierrc
│ ├── README.md
│ ├── app/
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ ├── loading.tsx
│ │ ├── page.tsx
│ │ ├── priority/
│ │ │ ├── backlog/
│ │ │ │ └── page.tsx
│ │ │ ├── high/
│ │ │ │ └── page.tsx
│ │ │ ├── low/
│ │ │ │ └── page.tsx
│ │ │ ├── medium/
│ │ │ │ └── page.tsx
│ │ │ └── urgent/
│ │ │ └── page.tsx
│ │ ├── projects/
│ │ │ └── [id]/
│ │ │ └── page.tsx
│ │ ├── search/
│ │ │ └── page.tsx
│ │ ├── settings/
│ │ │ └── page.tsx
│ │ ├── teams/
│ │ │ └── page.tsx
│ │ ├── timeline/
│ │ │ └── page.tsx
│ │ └── users/
│ │ └── page.tsx
│ ├── components/
│ │ ├── Header/
│ │ │ └── index.tsx
│ │ ├── data/
│ │ │ └── columns.tsx
│ │ ├── global/
│ │ │ ├── auth-provider/
│ │ │ │ └── index.tsx
│ │ │ ├── border-view/
│ │ │ │ └── index.tsx
│ │ │ ├── loader/
│ │ │ │ └── spinner.tsx
│ │ │ ├── project-header/
│ │ │ │ └── index.tsx
│ │ │ ├── tab-button/
│ │ │ │ └── index.tsx
│ │ │ ├── task/
│ │ │ │ └── index.tsx
│ │ │ ├── task-card/
│ │ │ │ └── index.tsx
│ │ │ └── task-column/
│ │ │ └── index.tsx
│ │ ├── home-page/
│ │ │ └── index.tsx
│ │ ├── list-view/
│ │ │ └── index.tsx
│ │ ├── modal/
│ │ │ ├── index.tsx
│ │ │ ├── modal-new-project/
│ │ │ │ └── index.tsx
│ │ │ └── model-new-task/
│ │ │ └── index.tsx
│ │ ├── navbar/
│ │ │ └── index.tsx
│ │ ├── priorityPage/
│ │ │ └── index.tsx
│ │ ├── project-card/
│ │ │ └── index.tsx
│ │ ├── project-page/
│ │ │ └── index.tsx
│ │ ├── search-page/
│ │ │ └── index.tsx
│ │ ├── settings-page/
│ │ │ └── index.tsx
│ │ ├── sidebar/
│ │ │ └── index.tsx
│ │ ├── table-view/
│ │ │ └── index.tsx
│ │ ├── team-page/
│ │ │ └── index.tsx
│ │ ├── timeline/
│ │ │ └── index.tsx
│ │ ├── timeline-view/
│ │ │ └── index.tsx
│ │ ├── user-card/
│ │ │ └── index.tsx
│ │ ├── user-page/
│ │ │ └── index.tsx
│ │ └── wrapper/
│ │ ├── dashboardWrapper.tsx
│ │ └── redux.tsx
│ ├── lib/
│ │ └── utils.ts
│ ├── next.config.mjs
│ ├── package.json
│ ├── postcss.config.mjs
│ ├── state/
│ │ ├── api.ts
│ │ └── index.ts
│ ├── tailwind.config.ts
│ ├── tsconfig.json
│ └── types/
│ └── type.ts
├── lamda_trigger.mjs
├── production-level-application/
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── README.md
│ ├── actions/
│ │ └── index.ts
│ ├── app/
│ │ ├── (auth)/
│ │ │ ├── layout.tsx
│ │ │ ├── sign-in/
│ │ │ │ └── [[...sign-in]]/
│ │ │ │ └── page.tsx
│ │ │ └── sign-up/
│ │ │ └── [[...sign-up]]/
│ │ │ └── page.tsx
│ │ ├── api/
│ │ │ ├── move/
│ │ │ │ └── [taskId]/
│ │ │ │ └── route.tsx
│ │ │ ├── projects/
│ │ │ │ ├── [projectId]/
│ │ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ │ ├── tasks/
│ │ │ │ ├── [projectId]/
│ │ │ │ │ └── route.ts
│ │ │ │ ├── route.ts
│ │ │ │ └── user/
│ │ │ │ └── [userId]/
│ │ │ │ └── route.ts
│ │ │ └── user/
│ │ │ ├── [userId]/
│ │ │ │ └── route.ts
│ │ │ └── route.ts
│ │ ├── dashboard/
│ │ │ ├── layout.tsx
│ │ │ ├── loading.tsx
│ │ │ ├── page.tsx
│ │ │ ├── priority/
│ │ │ │ ├── backlog/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── high/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── low/
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── medium/
│ │ │ │ │ └── page.tsx
│ │ │ │ └── urgent/
│ │ │ │ └── page.tsx
│ │ │ ├── projects/
│ │ │ │ └── [id]/
│ │ │ │ └── page.tsx
│ │ │ └── timeline/
│ │ │ └── page.tsx
│ │ ├── globals.css
│ │ ├── layout.tsx
│ │ ├── loading.tsx
│ │ └── page.tsx
│ ├── components/
│ │ ├── data/
│ │ │ └── columns.tsx
│ │ ├── global/
│ │ │ ├── border-view/
│ │ │ │ └── index.tsx
│ │ │ ├── client-only/
│ │ │ │ └── index.tsx
│ │ │ ├── image-card/
│ │ │ │ └── index.tsx
│ │ │ ├── image-upload/
│ │ │ │ └── index.tsx
│ │ │ ├── list-view/
│ │ │ │ └── index.tsx
│ │ │ ├── loader/
│ │ │ │ ├── buttonLoader.tsx
│ │ │ │ └── spinner.tsx
│ │ │ ├── project-header/
│ │ │ │ └── index.tsx
│ │ │ ├── project-page/
│ │ │ │ └── index.tsx
│ │ │ ├── tab-button/
│ │ │ │ └── index.tsx
│ │ │ ├── table-view/
│ │ │ │ └── index.tsx
│ │ │ ├── task-card/
│ │ │ │ └── index.tsx
│ │ │ ├── task-column/
│ │ │ │ └── index.tsx
│ │ │ └── timeline-view/
│ │ │ └── index.tsx
│ │ ├── header/
│ │ │ └── index.tsx
│ │ ├── home-page/
│ │ │ ├── index.tsx
│ │ │ └── root-home-page/
│ │ │ └── index.tsx
│ │ ├── modal/
│ │ │ ├── index.tsx
│ │ │ ├── modal-new-project/
│ │ │ │ └── index.tsx
│ │ │ ├── model-new-task/
│ │ │ │ └── index.tsx
│ │ │ └── model-share-project/
│ │ │ └── index.tsx
│ │ ├── navbar/
│ │ │ ├── home/
│ │ │ │ └── index.tsx
│ │ │ └── index.tsx
│ │ ├── priority-page/
│ │ │ └── index.tsx
│ │ ├── sidebar/
│ │ │ └── index.tsx
│ │ ├── task/
│ │ │ └── index.tsx
│ │ ├── timeline/
│ │ │ └── index.tsx
│ │ └── wrapper/
│ │ ├── dashboardWrapper.tsx
│ │ └── redux.tsx
│ ├── lib/
│ │ ├── prismadb.ts
│ │ └── utils.ts
│ ├── middleware.ts
│ ├── next.config.mjs
│ ├── package.json
│ ├── postcss.config.mjs
│ ├── prisma/
│ │ └── schema.prisma
│ ├── provider/
│ │ └── reduxProvider.tsx
│ ├── state/
│ │ ├── api.ts
│ │ └── index.ts
│ ├── tailwind.config.ts
│ ├── tsconfig.json
│ └── types/
│ └── type.ts
└── server/
├── .gitignore
├── ecosystem.config.js
├── package.json
├── prisma/
│ ├── migrations/
│ │ ├── 20250103002536_init/
│ │ │ └── migration.sql
│ │ └── migration_lock.toml
│ ├── schema.prisma
│ ├── seed.ts
│ └── seedData/
│ ├── attachment.json
│ ├── comment.json
│ ├── project.json
│ ├── projectTeam.json
│ ├── task.json
│ ├── taskAssignment.json
│ ├── team.json
│ └── user.json
├── src/
│ ├── controllers/
│ │ ├── projectControllers.ts
│ │ ├── searchControllers.ts
│ │ ├── taskControllers.ts
│ │ ├── teamControllers.ts
│ │ └── usercontrollers.ts
│ ├── index.ts
│ └── routes/
│ ├── projectRoutes.ts
│ ├── searchRoutes.ts
│ ├── taskRoute.ts
│ ├── teamRoutes.ts
│ └── userRoutes.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: README.md
================================================
# 🗂️ Project Management App (Full Stack + AWS)
This is a **Project Management Dashboard** built using modern web technologies. It includes a powerful frontend, a backend server, and secure cloud integration with AWS. Perfect for managing tasks, tracking projects, and working with teams.
---
### 🚀 Live Demo
👉 [View the App Live](https://pm-app-tan.vercel.app/)
---
### 📚 Table of Contents
* [About](#about)
* [Features](#features)
* [Tech Stack](#tech-stack)
* [Screenshots](#screenshots)
* [Getting Started](#getting-started)
* [Requirements](#requirements)
* [Installation](#installation)
* [Running Locally](#running-locally)
* [Environment Variables](#environment-variables)
* [Deployment](#deployment)
* [Contact](#contact)
---
## 📌 About
This app is a **full-stack project management tool**. It helps teams stay organized with features like task tracking, user authentication, cloud hosting, and more.
---
## ✨ Features
✅ Create and manage projects
✅ Add tasks and assign team members
✅ User authentication (with AWS Cognito)
✅ Cloud functions using AWS Lambda
✅ Beautiful UI with charts and data tables
---
## 🛠 Tech Stack
### Frontend
* Next.js
* TypeScript
* React.js
* Tailwind CSS
* Material UI Data Grid
### Backend
* Node.js
* Express.js
* PostgreSQL
* Prisma ORM
### Cloud & DevOps
* AWS Lambda
* AWS Cognito
* Vercel (Deployment)
---
## 📷 Screenshots
> Here’s a preview of the dashboard (Click to view):
---
## 🧰 Getting Started
### ✅ Requirements
Before you start, make sure you have:
* [Node.js](https://nodejs.org/en/) installed
* An [AWS account](https://aws.amazon.com/free/)
* A [Clerk account](https://clerk.com/) for user auth (optional but recommended)
---
### 📦 Installation
Clone the project:
```bash
git clone https://github.com/SashenJayathilaka/Project-Management-App.git
cd Project-Management-App
```
Install the dependencies:
```bash
npm install
```
---
### 🧪 Running Locally
To start the app locally:
```bash
npm run dev
```
Then open your browser and go to:
👉 [http://localhost:3000](http://localhost:3000)
---
### 🔐 Environment Variables
Create a `.env` file in the root directory and add the required keys.
Example:
```env
DATABASE_URL=your_postgres_db_url
AWS_REGION=your_aws_region
COGNITO_USER_POOL_ID=your_cognito_pool_id
COGNITO_CLIENT_ID=your_cognito_client_id
```
---
## 🚀 Deployment
### Deploy with Vercel
The easiest way to deploy is using **Vercel**:
1. Go to [https://vercel.com/new](https://vercel.com/new)
2. Import your GitHub repository
3. Add environment variables in the Vercel dashboard
4. Deploy and enjoy!
You can also deploy manually using other platforms like AWS, Netlify, or Docker.
---
## 📬 Contact
**Developer**: Sashen Jayathilaka
📧 Email: [sashenjayathilaka95@gmail.com](mailto:sashenjayathilaka95@gmail.com)
🐦 Twitter: [@SashenHasinduJ](https://twitter.com/SashenHasinduJ)
🔗 Project Repository: [GitHub Link](https://github.com/SashenJayathilaka/Project-Management-App.git)
---
⭐️ If you found this project helpful, feel free to star it!
================================================
FILE: aws-bucket-policy.json
================================================
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "*"
}
]
}
================================================
FILE: client/.eslintrc.json
================================================
{
"extends": "next/core-web-vitals"
}
================================================
FILE: client/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
================================================
FILE: client/.prettierrc
================================================
{
"plugins": ["prettier-plugin-tailwindcss"]
}
================================================
FILE: client/README.md
================================================
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
================================================
FILE: client/app/globals.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body,
#root,
.app {
height: 100%;
width: 100%;
@apply text-sm;
@apply bg-gray-500;
@apply text-gray-900;
}
.timeline ._3_ygE {
@apply rounded-tl-md border border-r-0 border-[#e6e4e4] dark:border-stroke-dark;
}
.timeline ._2eZzQ {
@apply border-[#e6e4e4] dark:border-stroke-dark;
}
.timeline ._2dZTy {
@apply fill-white dark:fill-dark-secondary;
}
.timeline ._2dZTy:nth-child(even) {
@apply fill-[#f5f5f5] dark:fill-dark-tertiary;
}
.timeline ._35nLX {
@apply fill-white stroke-[#e6e4e4] dark:fill-dark-secondary dark:stroke-stroke-dark;
}
.timeline ._9w8d5 {
@apply fill-[#333] dark:fill-white;
}
.timeline ._34SS0 {
@apply bg-white dark:bg-dark-secondary;
}
.timeline ._34SS0:nth-of-type(even) {
@apply bg-[#f5f5f5] dark:bg-dark-tertiary;
}
.timeline ._RuwuK,
.timeline ._3rUKi,
.timeline ._1rLuZ {
@apply stroke-[#e6e4e4] dark:stroke-stroke-dark;
}
.timeline ._3ZbQT {
@apply border-l-0 border-[#e6e4e4] dark:border-stroke-dark;
}
.timeline ._3T42e {
@apply bg-white dark:bg-dark-bg;
}
.timeline ._29NTg {
@apply dark:text-neutral-500;
}
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
================================================
FILE: client/app/layout.tsx
================================================
import DashboardWrapper from "@/components/wrapper/dashboardWrapper";
import type { Metadata } from "next";
import { Plus_Jakarta_Sans } from "next/font/google";
import "./globals.css";
const jakarta = Plus_Jakarta_Sans({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
{children}
);
}
================================================
FILE: client/app/loading.tsx
================================================
import { Spinner } from "@/components/global/loader/spinner";
type Props = {};
function Loading({}: Props) {
return (
);
}
export default Loading;
================================================
FILE: client/app/page.tsx
================================================
import HomePage from "@/components/home-page";
export default function Home() {
return ;
}
================================================
FILE: client/app/priority/backlog/page.tsx
================================================
import PriorityPage from "@/components/priorityPage";
import { Priority } from "@/types/type";
type Props = {};
function Page({}: Props) {
return ;
}
export default Page;
================================================
FILE: client/app/priority/high/page.tsx
================================================
import PriorityPage from "@/components/priorityPage";
import { Priority } from "@/types/type";
type Props = {};
function Page({}: Props) {
return ;
}
export default Page;
================================================
FILE: client/app/priority/low/page.tsx
================================================
import PriorityPage from "@/components/priorityPage";
import { Priority } from "@/types/type";
type Props = {};
function Page({}: Props) {
return ;
}
export default Page;
================================================
FILE: client/app/priority/medium/page.tsx
================================================
import PriorityPage from "@/components/priorityPage";
import { Priority } from "@/types/type";
type Props = {};
function Page({}: Props) {
return ;
}
export default Page;
================================================
FILE: client/app/priority/urgent/page.tsx
================================================
import PriorityPage from "@/components/priorityPage";
import { Priority } from "@/types/type";
type Props = {};
function Page({}: Props) {
return ;
}
export default Page;
================================================
FILE: client/app/projects/[id]/page.tsx
================================================
import ProjectPage from "@/components/project-page";
type Props = {
params: {
id: string;
};
};
function Page({ params }: Props) {
const { id } = params;
return ;
}
export default Page;
================================================
FILE: client/app/search/page.tsx
================================================
import SearchPage from "@/components/search-page";
type Props = {};
function Page({}: Props) {
return ;
}
export default Page;
================================================
FILE: client/app/settings/page.tsx
================================================
import SettingsPage from "@/components/settings-page";
import React from "react";
type Props = {};
function Page({}: Props) {
return ;
}
export default Page;
================================================
FILE: client/app/teams/page.tsx
================================================
import TeamPage from "@/components/team-page";
type Props = {};
function Page({}: Props) {
return ;
}
export default Page;
================================================
FILE: client/app/timeline/page.tsx
================================================
import TimeLinePage from "@/components/timeline";
import React from "react";
type Props = {};
function Page({}: Props) {
return ;
}
export default Page;
================================================
FILE: client/app/users/page.tsx
================================================
import UserPage from "@/components/user-page";
type Props = {};
function Page({}: Props) {
return ;
}
export default Page;
================================================
FILE: client/components/Header/index.tsx
================================================
import React from "react";
type Props = {
name: string;
buttonComponent?: any;
isSmallText?: boolean;
};
function Header({ name, buttonComponent, isSmallText }: Props) {
return (
{name}
{buttonComponent}
);
}
export default Header;
================================================
FILE: client/components/data/columns.tsx
================================================
import {
GridColDef,
GridToolbarContainer,
GridToolbarExport,
GridToolbarFilterButton,
} from "@mui/x-data-grid";
export const columns: GridColDef[] = [
{
field: "title",
headerName: "Title",
width: 100,
},
{
field: "description",
headerName: "Description",
width: 200,
},
{
field: "status",
headerName: "Status",
width: 130,
renderCell: (params) => (
{params.value}
),
},
{
field: "priority",
headerName: "Priority",
width: 75,
},
{
field: "tags",
headerName: "Tags",
width: 130,
},
{
field: "startDate",
headerName: "Start Date",
width: 130,
},
{
field: "dueDate",
headerName: "Due Date",
width: 130,
},
{
field: "author",
headerName: "Author",
width: 150,
renderCell: (params) => params.value?.author || "Unknown",
},
{
field: "assignee",
headerName: "Assignee",
width: 150,
renderCell: (params) => params.value?.assignee || "Unassigned",
},
];
export const userSettings = {
username: "johndoe",
email: "john.doe@example.com",
teamName: "Development Team",
roleName: "Developer",
};
export const CustomToolbar = () => (
);
export const taskColumns: GridColDef[] = [
{ field: "title", headerName: "Title", width: 200 },
{ field: "status", headerName: "Status", width: 150 },
{ field: "priority", headerName: "Priority", width: 150 },
{ field: "dueDate", headerName: "Due Date", width: 150 },
];
export const COLORS = ["#0088FE", "#00C49F", "#FFBB28", "#FF8042"];
export const priorityColumns: GridColDef[] = [
{
field: "title",
headerName: "Title",
width: 100,
},
{
field: "description",
headerName: "Description",
width: 200,
},
{
field: "status",
headerName: "Status",
width: 130,
renderCell: (params) => (
{params.value}
),
},
{
field: "priority",
headerName: "Priority",
width: 75,
},
{
field: "tags",
headerName: "Tags",
width: 130,
},
{
field: "startDate",
headerName: "Start Date",
width: 130,
},
{
field: "dueDate",
headerName: "Due Date",
width: 130,
},
{
field: "author",
headerName: "Author",
width: 150,
renderCell: (params) => params.value?.username || "Unknown",
},
{
field: "assignee",
headerName: "Assignee",
width: 150,
renderCell: (params) => params.value?.username || "Unassigned",
},
];
export const formFields = {
signUp: {
username: {
order: 1,
placeholder: "Choose a username",
label: "Username",
inputProps: { required: true },
},
email: {
order: 1,
placeholder: "Enter your email address",
label: "Email",
inputProps: { type: "email", required: true },
},
password: {
order: 3,
placeholder: "Enter your password",
label: "Password",
inputProps: { type: "password", required: true },
},
confirm_password: {
order: 4,
placeholder: "Confirm your password",
label: "Confirm Password",
inputProps: { type: "password", required: true },
},
},
};
================================================
FILE: client/components/global/auth-provider/index.tsx
================================================
import { formFields } from "@/components/data/columns";
import { Authenticator } from "@aws-amplify/ui-react";
import "@aws-amplify/ui-react/styles.css";
import { Amplify } from "aws-amplify";
import React from "react";
type Props = {
children: React.ReactNode;
};
Amplify.configure({
Auth: {
Cognito: {
userPoolId: process.env.NEXT_PUBLIC_COGNITO_USER_POOL_ID!,
userPoolClientId: process.env.NEXT_PUBLIC_COGNITO_USER_CLIENT_ID!,
},
},
});
function AuthProvider({ children }: Props) {
return (
{({ user }: any) =>
user ? (
{children}
) : (
Please Sign In Below
)
}
);
}
export default AuthProvider;
================================================
FILE: client/components/global/border-view/index.tsx
================================================
import { useGetTasksQuery, useUpdateTasksMutation } from "@/state/api";
import React from "react";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { Spinner } from "../loader/spinner";
import TaskColumn from "../task-column";
type Props = {
id: string;
setIsModelNewTasOpen: (isOpen: boolean) => void;
};
const taskStatus = ["To Do", "Work In Progress", "Under Review", "Completed"];
function BorderView({ id, setIsModelNewTasOpen }: Props) {
const {
data: tasks,
isLoading,
error,
} = useGetTasksQuery({ projectId: Number(id) });
const [updateTaskStatus] = useUpdateTasksMutation();
const moveTask = (taskId: number, toStatus: string) => {
updateTaskStatus({ taskId, status: toStatus });
};
if (isLoading) return ;
if (error) return An error has occurred
;
return (
{taskStatus.map((status) => (
))}
);
}
export default BorderView;
================================================
FILE: client/components/global/loader/spinner.tsx
================================================
type SpinnerProps = {
color?: string;
};
export const Spinner = ({ color }: SpinnerProps) => {
return (
);
};
================================================
FILE: client/components/global/project-header/index.tsx
================================================
import TabButton from "@/components/global/tab-button";
import Header from "@/components/Header";
import ModalNewProject from "@/components/modal/modal-new-project";
import {
Clock,
Filter,
Grid3x3,
Grid3X3,
List,
PlusSquare,
Share2,
Table,
} from "lucide-react";
import { useState } from "react";
type Props = {
activeTab: string;
setActiveTab: (tabName: string) => void;
};
function ProjectHeader({ activeTab, setActiveTab }: Props) {
const [isModalNameProjectOpen, setIsModalNameProjectOpen] = useState(false);
return (
setIsModalNameProjectOpen(false)}
/>
setIsModalNameProjectOpen(true)}
>
New Board
}
/>
}
activeTab={activeTab}
setActiveTab={setActiveTab}
/>
}
activeTab={activeTab}
setActiveTab={setActiveTab}
/>
}
activeTab={activeTab}
setActiveTab={setActiveTab}
/>
}
activeTab={activeTab}
setActiveTab={setActiveTab}
/>
);
}
export default ProjectHeader;
================================================
FILE: client/components/global/tab-button/index.tsx
================================================
import React from "react";
type Props = {
name: string;
icon: React.ReactNode;
setActiveTab: (tabName: string) => void;
activeTab: string;
};
function TabButton({ activeTab, icon, name, setActiveTab }: Props) {
const isActive = activeTab === name;
return (
setActiveTab(name)}
>
{icon}
{name}
);
}
export default TabButton;
================================================
FILE: client/components/global/task/index.tsx
================================================
import { TasksTypes } from "@/types/type";
import { format } from "date-fns";
import { EllipsisVertical, MessageSquareMore } from "lucide-react";
import Image from "next/image";
import { useDrag } from "react-dnd";
type Props = {
task: TasksTypes;
};
function Task({ task }: Props) {
const [{ isDragging }, drop] = 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 PriorityTags = ({ priority }: { priority: TasksTypes["priority"] }) => (
{priority}
);
return (
{
drop(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}
{task.assignee && (
)}
{task.author && (
)}
{numberOfComments}
);
}
export default Task;
================================================
FILE: client/components/global/task-card/index.tsx
================================================
import { TasksTypes } from "@/types/type";
import { format } from "date-fns";
import Image from "next/image";
import React from "react";
type Props = {
task: TasksTypes;
};
function TaskCard({ task }: Props) {
return (
{task.attachments && task.attachments.length > 0 && (
Attachments:
{task.attachments && task.attachments.length > 0 && (
)}
)}
ID: {task.id}
Title: {task.title}
Description: {" "}
{task.description || "No description provided"}
Status: {task.status}
Priority: {task.priority}
Tags: {task.tags || "No tags"}
Start Date: {" "}
{task.startDate ? format(new Date(task.startDate), "P") : "Not set"}
Due Date: {" "}
{task.dueDate ? format(new Date(task.dueDate), "P") : "Not set"}
Author: {" "}
{task.author ? task.author.username : "Unknown"}
Assignee: {" "}
{task.assignee ? task.assignee.username : "Unassigned"}
);
}
export default TaskCard;
================================================
FILE: client/components/global/task-column/index.tsx
================================================
import { EllipsisVertical, Plus } from "lucide-react";
import React from "react";
import { useDrop } from "react-dnd";
import Task from "../task";
import { TasksTypes } from "@/types/type";
type Props = {
status: string;
tasks: TasksTypes[];
moveTask: (taskId: number, toStatus: string) => void;
setIsModelNewTasOpen: (isOpen: boolean) => void;
};
function TaskColumn({ moveTask, setIsModelNewTasOpen, status, tasks }: Props) {
const [{ isOver }, drop] = useDrop(() => ({
accept: "task",
drop: (item: { id: number }) => moveTask(item.id, status),
collect: (monitor: any) => ({
isOver: !!monitor.isOver(),
}),
}));
const tasksCount = tasks.filter((task) => task.status === status).length;
const statusColor: any = {
"To Do": "#2563EB",
"Work In Progress": "#059669",
"Under Review": "#D97706",
Completed: "#000000",
};
return (
{
drop(instance);
}}
className={`sl:py-4 rounded-lg py-2 xl:px-2 ${isOver ? "bg-blue-100 dark:bg-neutral-950" : ""}`}
>
{status}{" "}
{tasksCount}
setIsModelNewTasOpen(true)}
>
{tasks
.filter((task) => task.status === status)
.map((task) => (
))}
);
}
export default TaskColumn;
================================================
FILE: client/components/home-page/index.tsx
================================================
"use client";
import { dataGridClassNames, dataGridSxStyles } from "@/lib/utils";
import { useGetProjectsQuery, useGetTasksQuery } from "@/state/api";
import { Priority, ProjectTypes, TasksTypes } from "@/types/type";
import { DataGrid } from "@mui/x-data-grid";
import {
Bar,
BarChart,
CartesianGrid,
Cell,
Legend,
Pie,
PieChart,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts";
import { COLORS, taskColumns } from "../data/columns";
import { Spinner } from "../global/loader/spinner";
import Header from "../Header";
import { useAppSelector } from "../wrapper/redux";
type Props = {};
function HomePage({}: Props) {
const {
data: tasks,
isLoading: tasksLoading,
isError: tasksError,
} = useGetTasksQuery({
projectId: parseInt("1"),
});
const { data: projects, isLoading: isProjectsLoading } =
useGetProjectsQuery();
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
if (tasksLoading || isProjectsLoading) return ;
if (tasksError || !tasks || !projects)
return An error has occurred
;
const priorityCount = tasks.reduce(
(acc: Record, task: TasksTypes) => {
const { priority } = task;
acc[priority as Priority] = (acc[priority as Priority] || 0) + 1;
return acc;
},
{},
);
const taskDistribution = Object.keys(priorityCount).map((key) => ({
name: key,
count: priorityCount[key],
}));
const statusCount = projects.reduce(
(acc: Record, project: ProjectTypes) => {
const status = project.endDate ? "Completed" : "Active";
acc[status] = (acc[status] || 0) + 1;
return acc;
},
{},
);
const projectStatus = Object.keys(statusCount).map((key) => ({
name: key,
count: statusCount[key],
}));
const chartColors = isDarkMode
? {
bar: "#8884d8",
barGrid: "#303030",
pieFill: "#4A90E2",
text: "#FFFFFF",
}
: {
bar: "#8884d8",
barGrid: "#E0E0E0",
pieFill: "#82ca9d",
text: "#000000",
};
return (
Task Priority Distribution
Project Status
{projectStatus.map((entry, index) => (
|
))}
Your Tasks
"data-grid-row"}
getCellClassName={() => "data-grid-cell"}
className={dataGridClassNames}
sx={dataGridSxStyles(isDarkMode)}
/>
);
}
export default HomePage;
================================================
FILE: client/components/list-view/index.tsx
================================================
import { useGetTasksQuery } from "@/state/api";
import { TasksTypes } from "@/types/type";
import { Spinner } from "../global/loader/spinner";
import TaskCard from "../global/task-card";
import Header from "../Header";
type Props = {
id: string;
setIsModelNewTasOpen: (isOpen: boolean) => void;
};
function ListView({ id, setIsModelNewTasOpen }: Props) {
const {
data: tasks,
error,
isLoading,
} = useGetTasksQuery({ projectId: Number(id) });
if (isLoading) return ;
if (error) return An error has occurred
;
return (
setIsModelNewTasOpen(true)}
>
Add Task
}
isSmallText
/>
{tasks?.map((task: TasksTypes) => (
))}
);
}
export default ListView;
================================================
FILE: client/components/modal/index.tsx
================================================
import Header from "@/components/Header";
import { X } from "lucide-react";
import React from "react";
import ReactDOM from "react-dom";
type Props = {
children: React.ReactNode;
isOpen: boolean;
onClose: () => void;
name: string;
};
function Modal({ children, isOpen, onClose, name }: Props) {
if (!isOpen) return null;
return ReactDOM.createPortal(
}
isSmallText
/>
{children}
,
document.body,
);
}
export default Modal;
================================================
FILE: client/components/modal/modal-new-project/index.tsx
================================================
import { Spinner } from "@/components/global/loader/spinner";
import { useCreateProjectMutation } from "@/state/api";
import { formatISO } from "date-fns";
import { useState } from "react";
import Modal from "..";
type Props = {
isOpen: boolean;
onClose: () => void;
};
function ModalNewProject({ isOpen, onClose }: Props) {
const [createProject, { isLoading }] = useCreateProjectMutation();
const [projectName, setProjectName] = useState("");
const [description, setDescription] = useState("");
const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState("");
const handleSubmit = async () => {
if (!projectName || !startDate || !endDate) return;
const formateStartDate = formatISO(new Date(startDate), {
representation: "complete",
});
const formateEndDate = formatISO(new Date(endDate), {
representation: "complete",
});
await createProject({
name: projectName,
description,
startDate: formateStartDate,
endDate: formateEndDate,
});
onClose();
};
const isFormValid = () => {
return projectName && description && startDate && endDate;
};
const inputStyle =
"w-full rounded-md border border-gray-300 p-2 shadow-sm dark:border-dark-tertiary dark:bg-dark-tertiary dark:text-white dark:focus:outline-none";
return (
);
}
export default ModalNewProject;
================================================
FILE: client/components/modal/model-new-task/index.tsx
================================================
import { Spinner } from "@/components/global/loader/spinner";
import { useCreateTasksMutation } from "@/state/api";
import { Priority, Status } from "@/types/type";
import { formatISO } from "date-fns";
import { useState } from "react";
import Modal from "..";
type Props = {
id?: string | null;
isOpen: boolean;
onClose: () => void;
};
function ModelNewTask({ id, isOpen, onClose }: Props) {
const [createTask, { isLoading }] = useCreateTasksMutation();
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [status, setStatus] = useState(Status.ToDo);
const [priority, setPriority] = useState(Priority.Backlog);
const [tags, setTags] = useState("");
const [startDate, setStartDate] = useState("");
const [dueDate, setDueDate] = useState("");
const [authorUserId, setAuthorUserId] = useState("");
const [assignedUserId, setAssignedUserId] = useState("");
const [projectId, setProjectId] = useState("");
console.log(id);
const handleSubmit = async () => {
if (!title || !authorUserId || !(id !== null || projectId)) return;
const formateStartDate = formatISO(new Date(startDate), {
representation: "complete",
});
const formateEndDate = formatISO(new Date(dueDate), {
representation: "complete",
});
await createTask({
title,
description,
status,
priority,
tags,
startDate: formateStartDate,
dueDate: formateEndDate,
authorUserId: parseInt(authorUserId),
assignedUserId: parseInt(assignedUserId),
projectId: id !== null ? Number(id) : Number(projectId),
});
onClose();
};
const isFormValid = () => {
return title && authorUserId && (id !== null || projectId);
};
const selectStyles =
"mb-4 block w-full rounded border border-gray-300 px-3 py-2 dark:border-dark-tertiary dark:bg-dark-tertiary dark:text-white dark:focus:outline-none";
const inputStyle =
"w-full rounded-md border border-gray-300 p-2 shadow-sm dark:border-dark-tertiary dark:bg-dark-tertiary dark:text-white dark:focus:outline-none";
return (
{
e.preventDefault();
handleSubmit();
}}
>
setTitle(e.target.value)}
/>
setDescription(e.target.value)}
/>
setStatus(Status[e.target.value as keyof typeof Status])
}
>
Select Status
To Do
Work In Progress
Under Review
Completed
setPriority(Priority[e.target.value as keyof typeof Priority])
}
>
Select Priority
Urgent
High
Medium
Low
Backlog
setTags(e.target.value)}
/>
setStartDate(e.target.value)}
/>
setDueDate(e.target.value)}
/>
setAuthorUserId(e.target.value)}
/>
setAssignedUserId(e.target.value)}
/>
{id === undefined && (
setProjectId(e.target.value)}
/>
)}
{isLoading ? : "Create Project"}
);
}
export default ModelNewTask;
================================================
FILE: client/components/navbar/index.tsx
================================================
import { setIsDarkMode, setIsSidebarCollapsed } from "@/state";
import { useGetAuthUserQuery } from "@/state/api";
import { signOut } from "aws-amplify/auth";
import { Menu, Moon, Search, Settings, Sun, User } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import React from "react";
import { useAppDispatch, useAppSelector } from "../wrapper/redux";
type Props = {};
function Navbar({}: Props) {
const dispatch = useAppDispatch();
const isSidebarCollapsed = useAppSelector(
(state) => state.global.isSidebarCollapsed,
);
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
const { data: currentUser } = useGetAuthUserQuery({});
const handleSignOut = async () => {
try {
await signOut();
} catch (error: any) {
console.log("🚀 ~ handleSignUp ~ error:", error);
}
};
if (!currentUser) return null;
const currentUserDetails = currentUser.user;
return (
{!isSidebarCollapsed ? null : (
dispatch(setIsSidebarCollapsed(!isSidebarCollapsed))}
>
)}
dispatch(setIsDarkMode(!isDarkMode))}
className={
isDarkMode
? `rounded p-2 dark:hover:bg-gray-700`
: `rounded p-2 hover:bg-gray-100`
}
>
{isDarkMode ? (
) : (
)}
{currentUserDetails?.username}
Sign out
);
}
export default Navbar;
================================================
FILE: client/components/priorityPage/index.tsx
================================================
"use client";
import { dataGridClassNames, dataGridSxStyles } from "@/lib/utils";
import { useGetAuthUserQuery, useGetTasksByUserQuery } from "@/state/api";
import { Priority, TasksTypes } from "@/types/type";
import { DataGrid } from "@mui/x-data-grid";
import { useState } from "react";
import { priorityColumns } from "../data/columns";
import TaskCard from "../global/task-card";
import Header from "../Header";
import ModelNewTask from "../modal/model-new-task";
import { useAppSelector } from "../wrapper/redux";
type Props = {
priority: Priority;
};
function PriorityPage({ priority }: Props) {
const [view, setView] = useState("list");
const [isMobileNewTaskOpen, setIsMobileNewTaskOpen] = useState(false);
const { data: currentUser } = useGetAuthUserQuery({});
const userId = Number(currentUser?.user?.userId) ?? null;
const {
data: task,
isLoading,
isError: isTasksError,
} = useGetTasksByUserQuery(userId || 0, { skip: userId == null });
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
const filteredTasks = task?.filter(
(t: TasksTypes) => t.priority === priority,
);
if (isTasksError || !task) return An error occurred
;
return (
setIsMobileNewTaskOpen(false)}
/>
setIsMobileNewTaskOpen(true)}
>
Add Task
}
/>
setView("list")}
>
List
setView("table")}
>
Table
{isLoading ? (
Loading tasks...
) : view === "list" ? (
{filteredTasks?.map((task: TasksTypes) => (
))}
) : (
view === "table" &&
filteredTasks && (
row.id}
className={dataGridClassNames}
sx={dataGridSxStyles(isDarkMode)}
/>
)
)}
);
}
export default PriorityPage;
================================================
FILE: client/components/project-card/index.tsx
================================================
import { ProjectTypes } from "@/types/type";
import React from "react";
type Props = {
project: ProjectTypes;
};
function ProjectCard({ project }: Props) {
return (
{project.name}
{project.description}
Start Date: {project.startDate}
End Date: {project.endDate}
);
}
export default ProjectCard;
================================================
FILE: client/components/project-page/index.tsx
================================================
"use client";
import { useState } from "react";
import BorderView from "../global/border-view";
import ProjectHeader from "../global/project-header";
import ListView from "../list-view";
import ModelNewTask from "../modal/model-new-task";
import TableView from "../table-view";
import Timeline from "../timeline-view";
type Props = {
id: string;
};
function ProjectPage({ id }: Props) {
const [activeTAB, setActiveTAB] = useState("Board");
const [isModelNewTasOpen, setIsModelNewTasOpen] = useState(false);
return (
setIsModelNewTasOpen(false)}
/>
{activeTAB === "Board" && (
)}
{activeTAB === "List" && (
)}
{activeTAB === "Timeline" && (
)}
{activeTAB === "Table" && (
)}
);
}
export default ProjectPage;
================================================
FILE: client/components/search-page/index.tsx
================================================
"use client";
import { useSearchQuery } from "@/state/api";
import { debounce } from "lodash";
import { useEffect, useState } from "react";
import Header from "../Header";
import { Spinner } from "../global/loader/spinner";
import TaskCard from "../global/task-card";
import ProjectCard from "../project-card";
import UserCard from "../user-card";
type Props = {};
function SearchPage({}: Props) {
const [searchTerm, setSearchTerm] = useState("");
const {
data: searchResults,
isLoading,
isError,
} = useSearchQuery(searchTerm, {
skip: searchTerm.length < 3,
});
const handleSearch = debounce((e: React.ChangeEvent) => {
setSearchTerm(e.target.value);
}, 500);
useEffect(() => {
return handleSearch.cancel;
}, [handleSearch.cancel]);
return (
{isLoading &&
}
{isError &&
Error occurred while fetching search results.
}
{!isLoading && !isError && searchResults && (
{searchResults.tasks && searchResults.tasks?.length > 0 && (
Tasks
)}
{searchResults.tasks?.map((task) => (
))}
{searchResults.projects && searchResults.projects?.length > 0 && (
Projects
)}
{searchResults.projects?.map((project) => (
))}
{searchResults.users && searchResults.users?.length > 0 && (
Users
)}
{searchResults.users?.map((user) => (
))}
)}
);
}
export default SearchPage;
================================================
FILE: client/components/settings-page/index.tsx
================================================
import Header from "../Header";
import { userSettings } from "../data/columns";
type Props = {};
const labelStyles = "block text-sm font-medium dark:text-white";
const textStyles =
"mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2 dark:text-white";
function SettingsPage({}: Props) {
return (
Username
{userSettings.username}
Email
{userSettings.email}
Team
{userSettings.teamName}
Role
{userSettings.roleName}
);
}
export default SettingsPage;
================================================
FILE: client/components/sidebar/index.tsx
================================================
"use client";
import { setIsSidebarCollapsed } from "@/state";
import { useGetAuthUserQuery, useGetProjectsQuery } from "@/state/api";
import { signOut } from "aws-amplify/auth";
import {
AlertCircle,
AlertOctagon,
AlertTriangle,
Briefcase,
ChevronDown,
ChevronUp,
Home,
Layers3,
LockIcon,
LucideIcon,
Search,
Settings,
ShieldAlert,
User,
Users,
X,
} from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useState } from "react";
import { useDispatch } from "react-redux";
import { useAppSelector } from "../wrapper/redux";
type Props = {};
function SideBar({}: Props) {
const dispatch = useDispatch();
const [showProject, setShowProject] = useState(true);
const [showPriority, setShowPriority] = useState(true);
const { data: projects } = useGetProjectsQuery();
const { data: currentUser } = useGetAuthUserQuery({});
const handleSignOut = async () => {
try {
await signOut();
} catch (error: any) {
console.log("🚀 ~ handleSignUp ~ error:", error);
}
};
const isSidebarCollapsed = useAppSelector(
(state) => state.global.isSidebarCollapsed,
);
if (!currentUser) return null;
const currentUserDetails = currentUser.user;
const sideBarClassNames = `fixed flex flex-col h-[100%] justify-between shadow-xl
transition-all duration-300 h-full z-40 dark:bg-black overflow-y-auto bg-white ${isSidebarCollapsed ? "w-0 hidden" : "w-64"}`;
return (
{/* */}
LIST
{isSidebarCollapsed ? null : (
dispatch(setIsSidebarCollapsed(!isSidebarCollapsed))
}
>
)}
setShowProject((prev) => !prev)}
className="flex w-full items-center justify-between px-8 py-3 text-gray-500"
>
Projects
{showProject ? (
) : (
)}
{showProject &&
projects?.map((project) => (
))}
setShowPriority((prev) => !prev)}
className="flex w-full items-center justify-between px-8 py-3 text-gray-500"
>
Priory
{showPriority ? (
) : (
)}
{showPriority && (
<>
>
)}
{currentUserDetails?.username}
Sign out
);
}
interface SideBarLinksType {
href: string;
icon: LucideIcon;
label: string;
}
const SidebarLink = ({ href, icon: Icon, label }: SideBarLinksType) => {
const pathName = usePathname();
const isActive =
pathName === href || (pathName === "/" && href === "/dashboard");
const screenWidth = window.innerWidth;
const isSidebarCollapsed = useAppSelector(
(state) => state.global.isSidebarCollapsed,
);
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
return (
{isActive && (
)}
{label}
);
};
export default SideBar;
// 1.40
================================================
FILE: client/components/table-view/index.tsx
================================================
import { columns } from "@/components/data/columns";
import { dataGridClassNames, dataGridSxStyles } from "@/lib/utils";
import { useGetTasksQuery } from "@/state/api";
import { DataGrid } from "@mui/x-data-grid";
import { Spinner } from "../global/loader/spinner";
import Header from "../Header";
import { useAppSelector } from "../wrapper/redux";
type Props = {
id: string;
setIsModelNewTasOpen: (isOpen: boolean) => void;
};
function TableView({ id, setIsModelNewTasOpen }: Props) {
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
const {
data: tasks,
error,
isLoading,
} = useGetTasksQuery({ projectId: Number(id) });
if (isLoading) return ;
if (error) return An error has occurred
;
return (
setIsModelNewTasOpen(true)}
>
Add Task
}
isSmallText
/>
);
}
export default TableView;
================================================
FILE: client/components/team-page/index.tsx
================================================
"use client";
import { dataGridClassNames, dataGridSxStyles } from "@/lib/utils";
import { useGetTeamsQuery } from "@/state/api";
import { DataGrid, GridColDef } from "@mui/x-data-grid";
import { CustomToolbar } from "../data/columns";
import { Spinner } from "../global/loader/spinner";
import Header from "../Header";
import { useAppSelector } from "../wrapper/redux";
type Props = {};
const columns: GridColDef[] = [
{ field: "id", headerName: "Team ID", width: 100 },
{ field: "teamName", headerName: "Team Name", width: 150 },
{ field: "productOwnerUsername", headerName: "Product Owner", width: 200 },
{
field: "projectManagerUsername",
headerName: "Project Manager",
width: 200,
},
];
function TeamPage({}: Props) {
const { data: teams, isLoading, isError } = useGetTeamsQuery();
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
if (isLoading) return ;
if (isError) return An error has occurred
;
return (
);
}
export default TeamPage;
================================================
FILE: client/components/timeline/index.tsx
================================================
"use client";
import { useGetProjectsQuery } from "@/state/api";
import { TaskTypeItems } from "@/types/type";
import { DisplayOption, Gantt, ViewMode } from "gantt-task-react";
import "gantt-task-react/dist/index.css";
import React, { useMemo, useState } from "react";
import { Spinner } from "../global/loader/spinner";
import { useAppSelector } from "../wrapper/redux";
import Header from "../Header";
type Props = {};
function TimeLinePage({}: Props) {
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
const { data: projects, isError, isLoading } = useGetProjectsQuery();
const [displayOptions, setDisplayOptions] = useState({
viewMode: ViewMode.Month,
locale: "en-US",
});
const ganttTasks = useMemo(() => {
return (
projects?.map((project) => ({
start: new Date(project.startDate as string),
end: new Date(project.endDate as string),
name: project.name,
id: `Project-${project.id}`,
type: "project" as TaskTypeItems,
progress: 50,
isDisabled: false,
})) || []
);
}, [projects]);
const handleViewModeChange = (
event: React.ChangeEvent,
) => {
setDisplayOptions((prevOptions) => ({
...prevOptions,
viewMode: event.target.value as ViewMode,
}));
};
if (isLoading) return ;
if (isError || !projects)
return An error occurred while fetching projects
;
return (
);
}
export default TimeLinePage;
================================================
FILE: client/components/timeline-view/index.tsx
================================================
import { useGetTasksQuery } from "@/state/api";
import { DisplayOption, Gantt, ViewMode } from "gantt-task-react";
import "gantt-task-react/dist/index.css";
import React, { useMemo, useState } from "react";
import { Spinner } from "../global/loader/spinner";
import { useAppSelector } from "../wrapper/redux";
type Props = {
id: string;
setIsModelNewTasOpen: (isOpen: boolean) => void;
};
type TaskTypeItems = "task" | "milestone" | "project";
function Timeline({ id, setIsModelNewTasOpen }: Props) {
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
const {
data: tasks,
error,
isLoading,
} = useGetTasksQuery({ projectId: Number(id) });
const [displayOptions, setDisplayOptions] = useState({
viewMode: ViewMode.Month,
locale: "en-US",
});
const ganttTasks = useMemo(() => {
return (
tasks?.map((task) => ({
start: new Date(task.startDate as string),
end: new Date(task.dueDate as string),
name: task.title,
id: `Task-${task.id}`,
type: "task" as TaskTypeItems,
progress: task.points ? (task.points / 100) * 100 : 0,
isDisabled: false,
})) || []
);
}, [tasks]);
const handleViewModeChange = (
event: React.ChangeEvent,
) => {
setDisplayOptions((prevOptions) => ({
...prevOptions,
viewMode: event.target.value as ViewMode,
}));
};
if (isLoading) return ;
if (error) return An error has occurred
;
return (
Project Tasks Timeline
Day
Week
Month
setIsModelNewTasOpen(true)}
>
Add New Task
);
}
export default Timeline;
================================================
FILE: client/components/user-card/index.tsx
================================================
import { User } from "@/types/type";
import Image from "next/image";
import React from "react";
type Props = {
user: User;
};
function UserCard({ user }: Props) {
return (
{user.profilePictureUrl && (
)}
{user.username}
{user.email}
);
}
export default UserCard;
================================================
FILE: client/components/user-page/index.tsx
================================================
"use client";
import { dataGridClassNames, dataGridSxStyles } from "@/lib/utils";
import { useGetUsersQuery } from "@/state/api";
import { DataGrid, GridColDef } from "@mui/x-data-grid";
import Image from "next/image";
import { CustomToolbar } from "../data/columns";
import { Spinner } from "../global/loader/spinner";
import Header from "../Header";
import { useAppSelector } from "../wrapper/redux";
type Props = {};
const columns: GridColDef[] = [
{ field: "userId", headerName: "ID", width: 100 },
{ field: "username", headerName: "Username", width: 150 },
{
field: "profilePictureUrl",
headerName: "Profile Picture",
width: 100,
renderCell: (params) => (
),
},
];
function UserPage({}: Props) {
const { data: users, isLoading, isError } = useGetUsersQuery();
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
if (isLoading) return ;
if (isError) return An error has occurred
;
return (
row.userId}
pagination
slots={{
toolbar: CustomToolbar,
}}
className={dataGridClassNames}
sx={dataGridSxStyles(isDarkMode)}
/>
);
}
export default UserPage;
================================================
FILE: client/components/wrapper/dashboardWrapper.tsx
================================================
"use client";
import Navbar from "@/components/navbar";
import SideBar from "@/components/sidebar";
import React, { useEffect } from "react";
import AuthProvider from "../global/auth-provider";
import StoreProvider, { useAppSelector } from "./redux";
type Props = {
children: React.ReactNode;
};
function DashboardLayout({ children }: Props) {
const isSidebarCollapsed = useAppSelector(
(state) => state.global.isSidebarCollapsed,
);
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
useEffect(() => {
if (isDarkMode) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
});
return (
{children}
);
}
const DashboardWrapper = ({ children }: { children: React.ReactNode }) => {
return (
{children}
);
};
export default DashboardWrapper;
================================================
FILE: client/components/wrapper/redux.tsx
================================================
import globalReducer from "@/state";
import { api } from "@/state/api";
import { combineReducers, configureStore } from "@reduxjs/toolkit";
import { setupListeners } from "@reduxjs/toolkit/query";
import { useRef } from "react";
import {
Provider,
TypedUseSelectorHook,
useDispatch,
useSelector,
} from "react-redux";
import {
FLUSH,
PAUSE,
PERSIST,
persistReducer,
persistStore,
PURGE,
REGISTER,
REHYDRATE,
} from "redux-persist";
import { PersistGate } from "redux-persist/integration/react";
import createWebStorage from "redux-persist/lib/storage/createWebStorage";
/* REDUX PERSISTENCE */
const createNoopStorage = () => {
return {
getItem(_key: any) {
return Promise.resolve(null);
},
setItem(_key: any, value: any) {
return Promise.resolve(value);
},
removeItem(_key: any) {
return Promise.resolve();
},
};
};
const storage =
typeof window === "undefined"
? createNoopStorage()
: createWebStorage("local");
const persistConfig = {
key: "root",
storage,
whitelist: ["global"],
};
const rootReducer = combineReducers({
global: globalReducer,
[api.reducerPath]: api.reducer,
});
const persistedReducer = persistReducer(persistConfig, rootReducer);
/* REDUX STORE */
export const makeStore = () => {
return configureStore({
reducer: persistedReducer,
// @ts-ignore
middleware: (getDefault) =>
getDefault({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}).concat(api.middleware),
});
};
/* REDUX TYPES */
export type AppStore = ReturnType;
export type RootState = ReturnType;
export type AppDispatch = AppStore["dispatch"];
export const useAppDispatch = () => useDispatch();
export const useAppSelector: TypedUseSelectorHook = useSelector;
/* PROVIDER */
export default function StoreProvider({
children,
}: {
children: React.ReactNode;
}) {
const storeRef = useRef();
if (!storeRef.current) {
storeRef.current = makeStore();
setupListeners(storeRef.current.dispatch);
}
const persistor = persistStore(storeRef.current);
return (
{children}
);
}
================================================
FILE: client/lib/utils.ts
================================================
export const dataGridClassNames =
"border border-gray-200 bg-white shadow dark:border-stroke-dark dark:bg-dark-secondary dark:text-gray-200";
export const dataGridSxStyles = (isDarkMode: boolean) => {
return {
"& .MuiDataGrid-columnHeaders": {
color: `${isDarkMode ? "#e5e7eb" : ""}`,
'& [role="row"] > *': {
backgroundColor: `${isDarkMode ? "#1d1f21" : "white"}`,
borderColor: `${isDarkMode ? "#2d3135" : ""}`,
},
},
"& .MuiIconbutton-root": {
color: `${isDarkMode ? "#a3a3a3" : ""}`,
},
"& .MuiTablePagination-root": {
color: `${isDarkMode ? "#a3a3a3" : ""}`,
},
"& .MuiTablePagination-selectIcon": {
color: `${isDarkMode ? "#a3a3a3" : ""}`,
},
"& .MuiDataGrid-cell": {
border: "none",
},
"& .MuiDataGrid-row": {
borderBottom: `1px solid ${isDarkMode ? "#2d3135" : "e5e7eb"}`,
},
"& .MuiDataGrid-withBorderColor": {
borderColor: `${isDarkMode ? "#2d3135" : "e5e7eb"}`,
},
};
};
================================================
FILE: client/next.config.mjs
================================================
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "sh-s3-images.s3.us-east-1.amazonaws.com",
port: "",
pathname: "/**",
},
],
},
};
export default nextConfig;
================================================
FILE: client/package.json
================================================
{
"name": "client",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@aws-amplify/ui-react": "^6.9.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/material": "^6.3.0",
"@mui/x-data-grid": "^7.23.5",
"@reduxjs/toolkit": "^2.5.0",
"aws-amplify": "^6.12.1",
"axios": "^1.7.9",
"date-fns": "^4.1.0",
"dotenv": "^16.4.7",
"gantt-task-react": "^0.3.9",
"lodash": "^4.17.21",
"lucide-react": "^0.469.0",
"next": "14.2.7",
"numeral": "^2.0.6",
"react": "^18",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18",
"react-redux": "^9.2.0",
"recharts": "^2.15.0",
"redux-persist": "^6.0.0"
},
"devDependencies": {
"@types/lodash": "^4.17.13",
"@types/node": "^20",
"@types/numeral": "^2.0.5",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/uuid": "^10.0.0",
"eslint": "^8",
"eslint-config-next": "14.2.7",
"postcss": "^8",
"prettier": "^3.4.2",
"prettier-plugin-tailwindcss": "^0.6.9",
"tailwind-merge": "^2.6.0",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
================================================
FILE: client/postcss.config.mjs
================================================
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;
================================================
FILE: client/state/api.ts
================================================
import {
ProjectTypes,
SearchResults,
TasksTypes,
Team,
User,
} from "@/types/type";
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { fetchAuthSession, getCurrentUser } from "aws-amplify/auth";
export const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: process.env.NEXT_PUBLIC_BASE_URL,
prepareHeaders: async (headers) => {
const session = await fetchAuthSession();
const { accessToken } = session.tokens ?? {};
if (accessToken) {
headers.set("Authorization", `Bearer ${accessToken}`);
}
return headers;
},
}),
reducerPath: "api",
tagTypes: ["Projects", "Tasks", "Users", "Teams"],
endpoints: (build) => ({
getAuthUser: build.query({
queryFn: async (_, _queryApi, _extraoptions, fetchWihBQ) => {
try {
const user = await getCurrentUser();
const session = await fetchAuthSession();
if (!session) throw new Error("No session found");
const { userSub } = session;
const { accessToken } = session.tokens ?? {};
const userDetailsResponse = await fetchWihBQ(`users/${userSub}`);
const userDetails = userDetailsResponse.data as User;
return { data: { user, userSub, userDetails } };
} catch (error: any) {
return { error: error.message || "could not fetch user data" };
}
},
}),
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 }],
}),
createTasks: build.mutation>({
query: (task) => ({
url: "tasks",
method: "POST",
body: task,
}),
invalidatesTags: ["Tasks"],
}),
updateTasks: build.mutation(
{
query: ({ taskId, status }) => ({
url: `tasks/${taskId}/status`,
method: "PATCH",
body: { status },
}),
invalidatesTags: (result, error, { taskId }) => [
{ type: "Tasks", id: taskId },
],
},
),
getTeams: build.query({
query: () => "teams",
providesTags: ["Teams"],
}),
getUsers: build.query({
query: () => "users",
providesTags: ["Users"],
}),
search: build.query({
query: (query) => `search?query=${query}`,
}),
}),
});
export const {
useGetProjectsQuery,
useCreateProjectMutation,
useGetTasksQuery,
useCreateTasksMutation,
useUpdateTasksMutation,
useSearchQuery,
useGetUsersQuery,
useGetTeamsQuery,
useGetTasksByUserQuery,
useGetAuthUserQuery,
} = api;
================================================
FILE: client/state/index.ts
================================================
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
export interface initialStateTypes {
isSidebarCollapsed: boolean;
isDarkMode: boolean;
}
const initialState: initialStateTypes = {
isSidebarCollapsed: false,
isDarkMode: false,
};
export const globalSlice = createSlice({
name: "global",
initialState,
reducers: {
setIsSidebarCollapsed: (state, action: PayloadAction) => {
state.isSidebarCollapsed = action.payload;
},
setIsDarkMode: (state, action: PayloadAction) => {
state.isDarkMode = action.payload;
},
},
});
export const { setIsSidebarCollapsed, setIsDarkMode } = globalSlice.actions;
export default globalSlice.reducer;
================================================
FILE: client/tailwind.config.ts
================================================
import type { Config } from "tailwindcss";
export default {
darkMode: "class",
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors: {
white: "#ffffff",
gray: {
100: "#f3f4f6",
200: "#e5e7eb",
300: "#d1d5db",
500: "#6b7280",
700: "#374151",
800: "#1f2937",
},
blue: {
200: "#93c5fd",
400: "#60a5fa",
500: "#3b82f6",
},
"dark-bg": "#101214",
"dark-secondary": "#1d1f21",
"dark-tertiary": "#3b3d40",
"blue-primary": "#0275ff",
"stroke-dark": "#2d3135",
background: "var(--background)",
foreground: "var(--foreground)",
},
},
},
plugins: [],
} satisfies Config;
================================================
FILE: client/tsconfig.json
================================================
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
================================================
FILE: client/types/type.ts
================================================
export interface ProjectTypes {
id: number;
name: string;
description?: string;
startDate?: string;
endDate?: string;
}
export enum Status {
ToDo = "To Do",
WorkInProgress = "Work In Progress",
UnderReview = "Under Review",
Completed = "Completed",
}
export enum Priority {
Urgent = "Urgent",
High = "High",
Medium = "Medium",
Low = "Low",
Backlog = "Backlog",
}
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 TasksTypes {
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?: TasksTypes[];
projects?: ProjectTypes[];
users?: User[];
}
export interface Team {
teamId: number;
teamName: string;
productOwnerUserId?: number;
projectManagerUserId?: number;
}
export type TaskTypeItems = "task" | "milestone" | "project";
================================================
FILE: lamda_trigger.mjs
================================================
import https from "node:https";
export const handler = async (event) => {
const postdata = JSON.stringify({
username:
event.request.userAttributes["preferred_username"] || event.userName,
cognitoId: event.userName,
profilePictureUrl: "i1.jpg",
teamId: 1,
});
const opctions = {
hostname: "https://2yk8eg8yxd.execute-api.us-east-1.amazonaws.com/prod",
port: 443,
path: "/create-user",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": postdata.length,
},
};
const responseBody = await new Promise((resolve, reject) => {
const req = https.request(opctions, (res) => {
/* console.log(`statusCode: ${res.statusCode}`) */
res.setEncoding("utf8");
let body = "";
res.on("data", (chunk) => (body += chunk));
res.on("end", () => resolve(body));
});
req.on("error", reject);
req.write(postdata);
req.end();
});
return event();
};
================================================
FILE: production-level-application/.eslintrc.json
================================================
{
"extends": "next/core-web-vitals"
}
================================================
FILE: production-level-application/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
================================================
FILE: production-level-application/README.md
================================================
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
================================================
FILE: production-level-application/actions/index.ts
================================================
import { currentUser } from "@clerk/nextjs/server";
import { redirect } from "next/navigation";
export const onCurrentUser = async () => {
const user = await currentUser();
if (!user) return redirect("/");
return user;
};
================================================
FILE: production-level-application/app/(auth)/layout.tsx
================================================
import React from "react";
type Props = {
children: React.ReactNode;
};
function Layout({ children }: Props) {
return (
{children}
);
}
export default Layout;
================================================
FILE: production-level-application/app/(auth)/sign-in/[[...sign-in]]/page.tsx
================================================
import { SignIn } from "@clerk/nextjs";
import React from "react";
type Props = {};
function Page({}: Props) {
return ;
}
export default Page;
================================================
FILE: production-level-application/app/(auth)/sign-up/[[...sign-up]]/page.tsx
================================================
import { SignUp } from "@clerk/nextjs";
import React from "react";
type Props = {};
function Page({}: Props) {
return ;
}
export default Page;
================================================
FILE: production-level-application/app/api/move/[taskId]/route.tsx
================================================
import prisma from "@/lib/prismadb";
import { NextResponse } from "next/server";
export async function PATCH(
request: Request,
{ params }: { params: { taskId?: string } }
) {
const { taskId } = params;
const body = await request.json();
const { status } = body;
if (!taskId)
return NextResponse.json({ error: "Missing taskId" }, { status: 400 });
try {
const updateTasks = await prisma.task.update({
where: {
id: taskId,
},
data: {
status: status,
},
});
return NextResponse.json(updateTasks);
} catch (error: any) {
return NextResponse.json(
{
error: `Failed to update task ${error.message}`,
},
{ status: 500 }
);
}
}
export async function DELETE(
request: Request,
{ params }: { params: { taskId?: string } }
) {
const { taskId } = params;
if (!taskId)
return NextResponse.json({ error: "Missing taskId" }, { status: 401 });
try {
const deleteTask = await prisma.task.delete({
where: {
id: taskId,
},
});
return NextResponse.json(deleteTask, { status: 200 });
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 400 });
}
}
================================================
FILE: production-level-application/app/api/projects/[projectId]/route.ts
================================================
import prisma from "@/lib/prismadb";
import { NextResponse } from "next/server";
export async function DELETE(
request: Request,
{ params }: { params: { projectId?: string } }
) {
const { projectId } = params;
if (!projectId)
return NextResponse.json({ error: "Missing projectId" }, { status: 400 });
try {
const deleteTask = await prisma.project.delete({
where: {
id: projectId,
},
});
return NextResponse.json(deleteTask);
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 400 });
}
}
export async function PATCH(
request: Request,
{ params }: { params: { projectId?: string } }
) {
const { projectId } = params;
const body = await request.json();
const { userId } = body;
if (!projectId)
return NextResponse.json({ error: "Missing projectId" }, { status: 400 });
try {
const checkItsAvailable = await prisma.project.findUnique({
where: {
id: projectId,
authorsIds: {
has: userId,
},
},
});
if (checkItsAvailable)
return NextResponse.json(
{ error: "Your Already In Project Or Current Project Not available!" },
{ status: 401 }
);
const updateProject = await prisma.project.update({
where: {
id: projectId,
},
data: {
authorsIds: {
push: userId,
},
},
});
return NextResponse.json(updateProject, { status: 200 });
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 400 });
}
}
================================================
FILE: production-level-application/app/api/projects/route.ts
================================================
import { onCurrentUser } from "@/actions";
import prisma from "@/lib/prismadb";
import { NextResponse } from "next/server";
export async function GET(request: Request) {
const user = await onCurrentUser();
const currentUserId = await prisma.user.findUnique({
where: {
clerkId: user.id,
},
});
if (!currentUserId)
return NextResponse.json({ error: "User not found" }, { status: 404 });
try {
const project = await prisma.project.findMany({
where: {
authorsIds: {
has: currentUserId.id,
},
},
});
return NextResponse.json(project);
} catch (error: any) {
return NextResponse.json(
{ error: `Failed to fetch projects ${error.message}` },
{ status: 500 }
);
}
}
export async function POST(request: Request) {
const body = await request.json();
const { name, description, startDate, endDate, user } = body;
try {
const createUser = await prisma.user.findFirst({
where: {
clerkId: user,
},
});
if (!createUser?.id)
return NextResponse.json({ error: "User not found" }, { status: 404 });
const newProject = await prisma.project.create({
data: {
name,
description,
startDate,
endDate,
userId: createUser?.id,
authorsIds: [createUser?.id],
},
});
return NextResponse.json({ newProject }, { status: 201 });
} catch (error: any) {
return NextResponse.json(
{ error: `Failed to create project ${error.message}` },
{ status: 500 }
);
}
}
================================================
FILE: production-level-application/app/api/tasks/[projectId]/route.ts
================================================
import prisma from "@/lib/prismadb";
import { NextResponse } from "next/server";
interface IParams {
projectId?: string;
}
export async function GET(request: Request, { params }: { params: IParams }) {
const { projectId } = params;
if (!projectId)
return NextResponse.json({ error: "Missing projectId" }, { status: 400 });
try {
const tasks = await prisma.task.findMany({
where: {
projectId: projectId,
},
include: {
author: true,
comments: true,
attachments: true,
},
});
return NextResponse.json(tasks);
} catch (error: any) {
return NextResponse.json(
{
error: `Failed to fetch tasks ${error.message}`,
},
{ status: 500 }
);
}
}
export async function PATCH(
request: Request,
{ params }: { params: { projectId?: string } }
) {
const { projectId } = params;
const body = await request.json();
const { userId } = body;
if (!projectId || !userId)
return NextResponse.json(
{ error: "Missing projectId or userId" },
{ status: 400 }
);
try {
const project = await prisma.project.findUnique({
where: {
id: projectId,
},
select: {
authorsIds: true,
},
});
if (!project)
return NextResponse.json({ error: "Project not found" }, { status: 404 });
const updateUsers = project.authorsIds.filter((id) => id !== userId);
const removeUserFromProject = await prisma.project.update({
where: {
id: projectId,
},
data: {
authorsIds: {
set: updateUsers,
},
},
});
return NextResponse.json(removeUserFromProject);
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
================================================
FILE: production-level-application/app/api/tasks/route.ts
================================================
import prisma from "@/lib/prismadb";
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const body = await request.json();
const {
title,
description,
status,
priority,
tags,
startDate,
dueDate,
points,
projectId,
authorUserId,
imageSrc,
userId,
} = body;
try {
const newTask = await prisma.task.create({
data: {
title,
description,
status,
priority,
tags,
startDate,
dueDate,
points,
projectId,
authorUserId,
imageSrc,
userId,
},
include: {
author: true,
},
});
return NextResponse.json({ newTask }, { status: 201 });
} catch (error: any) {
console.log(error.message);
return NextResponse.json(
{ error: `Failed to create task ${error.message}` },
{ status: 500 }
);
}
}
================================================
FILE: production-level-application/app/api/tasks/user/[userId]/route.ts
================================================
import prisma from "@/lib/prismadb";
import { NextResponse } from "next/server";
interface IParams {
userId?: string;
}
export async function GET(request: Request, { params }: { params: IParams }) {
const { userId } = params;
try {
const tasks = await prisma.task.findMany({
where: {
OR: [{ authorUserId: userId }, { assignedUserId: userId }],
},
include: {
author: true,
},
});
return NextResponse.json(tasks);
} catch (error: any) {
return NextResponse.json(
{ error: `Failed to get user tasks ${error.message}` },
{ status: 500 }
);
}
}
================================================
FILE: production-level-application/app/api/user/[userId]/route.ts
================================================
import prisma from "@/lib/prismadb";
import { NextResponse } from "next/server";
interface IParams {
userId?: string;
}
export async function GET(request: Request, { params }: { params: IParams }) {
const { userId } = params;
try {
const findUser = await prisma.user.findFirst({
where: {
clerkId: userId,
},
});
return NextResponse.json(findUser);
} catch (error: any) {
return NextResponse.json({ error: "User not found" }, { status: 404 });
}
}
================================================
FILE: production-level-application/app/api/user/route.ts
================================================
import prisma from "@/lib/prismadb";
import { NextResponse } from "next/server";
export async function POST(request: Request) {
try {
const body = await request.json();
const { firstName, secondName, clerkId, profilePictureUrl } = body;
const checkUser = await prisma.user.findUnique({
where: { clerkId },
});
if (checkUser) {
return NextResponse.json(
{ error: "User already exists" },
{ status: 404 }
);
} else {
const newUsers = await prisma.user.create({
data: {
firstName,
secondName,
clerkId,
profilePictureUrl,
},
});
return NextResponse.json({
message: "User created successfully",
newUsers,
});
}
} catch (error: any) {
return NextResponse.json(
{
error: `Failed to fetch users ${error.message}`,
},
{ status: 500 }
);
}
}
================================================
FILE: production-level-application/app/dashboard/layout.tsx
================================================
import DashboardWrapper from "@/components/wrapper/dashboardWrapper";
import React from "react";
import { ToastContainer } from "react-toastify";
type Props = {
children: React.ReactNode;
};
function layout({ children }: Props) {
return (
{children}
);
}
export default layout;
================================================
FILE: production-level-application/app/dashboard/loading.tsx
================================================
import { Spinner } from "@/components/global/loader/spinner";
type Props = {};
function Loading({}: Props) {
return ;
}
export default Loading;
================================================
FILE: production-level-application/app/dashboard/page.tsx
================================================
import ClientOnly from "@/components/global/client-only";
import RootHomePage from "@/components/home-page/root-home-page";
type Props = {};
async function Page({}: Props) {
return (
);
}
export default Page;
================================================
FILE: production-level-application/app/dashboard/priority/backlog/page.tsx
================================================
import ClientOnly from "@/components/global/client-only";
import PriorityPage from "@/components/priority-page";
import { Priority } from "@/types/type";
type Props = {};
function Page({}: Props) {
return (
);
}
export default Page;
================================================
FILE: production-level-application/app/dashboard/priority/high/page.tsx
================================================
import ClientOnly from "@/components/global/client-only";
import PriorityPage from "@/components/priority-page";
import { Priority } from "@/types/type";
type Props = {};
function Page({}: Props) {
return (
);
}
export default Page;
================================================
FILE: production-level-application/app/dashboard/priority/low/page.tsx
================================================
import ClientOnly from "@/components/global/client-only";
import PriorityPage from "@/components/priority-page";
import { Priority } from "@/types/type";
type Props = {};
function Page({}: Props) {
return (
;
);
}
export default Page;
================================================
FILE: production-level-application/app/dashboard/priority/medium/page.tsx
================================================
import ClientOnly from "@/components/global/client-only";
import PriorityPage from "@/components/priority-page";
import { Priority } from "@/types/type";
type Props = {};
function Page({}: Props) {
return (
);
}
export default Page;
================================================
FILE: production-level-application/app/dashboard/priority/urgent/page.tsx
================================================
import ClientOnly from "@/components/global/client-only";
import PriorityPage from "@/components/priority-page";
import { Priority } from "@/types/type";
type Props = {};
function Page({}: Props) {
return (
);
}
export default Page;
================================================
FILE: production-level-application/app/dashboard/projects/[id]/page.tsx
================================================
import ClientOnly from "@/components/global/client-only";
import ProjectPage from "@/components/global/project-page";
type Props = {
params: {
id: string;
};
};
function Page({ params }: Props) {
const { id } = params;
return (
);
}
export default Page;
================================================
FILE: production-level-application/app/dashboard/timeline/page.tsx
================================================
import ClientOnly from "@/components/global/client-only";
import TimeLinePage from "@/components/timeline";
type Props = {};
function Page({}: Props) {
return (
);
}
export default Page;
================================================
FILE: production-level-application/app/globals.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body,
#root,
.app {
height: 100%;
width: 100%;
@apply text-sm;
@apply bg-gray-500;
@apply text-gray-900;
}
.timeline ._3_ygE {
@apply rounded-tl-md border border-r-0 border-[#e6e4e4] dark:border-stroke-dark;
}
.timeline ._2eZzQ {
@apply border-[#e6e4e4] dark:border-stroke-dark;
}
.timeline ._2dZTy {
@apply fill-white dark:fill-dark-secondary;
}
.timeline ._2dZTy:nth-child(even) {
@apply fill-[#f5f5f5] dark:fill-dark-tertiary;
}
.timeline ._35nLX {
@apply fill-white stroke-[#e6e4e4] dark:fill-dark-secondary dark:stroke-stroke-dark;
}
.timeline ._9w8d5 {
@apply fill-[#333] dark:fill-white;
}
.timeline ._34SS0 {
@apply bg-white dark:bg-dark-secondary;
}
.timeline ._34SS0:nth-of-type(even) {
@apply bg-[#f5f5f5] dark:bg-dark-tertiary;
}
.timeline ._RuwuK,
.timeline ._3rUKi,
.timeline ._1rLuZ {
@apply stroke-[#e6e4e4] dark:stroke-stroke-dark;
}
.timeline ._3ZbQT {
@apply border-l-0 border-[#e6e4e4] dark:border-stroke-dark;
}
.timeline ._3T42e {
@apply bg-white dark:bg-dark-bg;
}
.timeline ._29NTg {
@apply dark:text-neutral-500;
}
::-webkit-scrollbar {
width: 0px;
height: 0px;
}
================================================
FILE: production-level-application/app/layout.tsx
================================================
import ReduxProvider from "@/provider/reduxProvider";
import { ClerkProvider } from "@clerk/nextjs";
import type { Metadata } from "next";
import { Plus_Jakarta_Sans } from "next/font/google";
import "./globals.css";
const jakarta = Plus_Jakarta_Sans({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Project Management App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
{children}
);
}
================================================
FILE: production-level-application/app/loading.tsx
================================================
import { Spinner } from "@/components/global/loader/spinner";
type Props = {};
function Loading({}: Props) {
return ;
}
export default Loading;
================================================
FILE: production-level-application/app/page.tsx
================================================
import ClientOnly from "@/components/global/client-only";
import HomeNavBar from "@/components/navbar/home";
type Props = {};
function Page({}: Props) {
return (
);
}
export default Page;
================================================
FILE: production-level-application/components/data/columns.tsx
================================================
import {
GridColDef,
GridToolbarContainer,
GridToolbarExport,
GridToolbarFilterButton,
} from "@mui/x-data-grid";
export const columns: GridColDef[] = [
{
field: "title",
headerName: "Title",
width: 100,
},
{
field: "description",
headerName: "Description",
width: 200,
},
{
field: "status",
headerName: "Status",
width: 130,
renderCell: (params) => (
{params.value}
),
},
{
field: "priority",
headerName: "Priority",
width: 75,
},
{
field: "tags",
headerName: "Tags",
width: 130,
},
{
field: "startDate",
headerName: "Start Date",
width: 130,
},
{
field: "dueDate",
headerName: "Due Date",
width: 130,
},
{
field: "author",
headerName: "Author",
width: 150,
renderCell: (params) => params.value?.author || "Unknown",
},
{
field: "assignee",
headerName: "Assignee",
width: 150,
renderCell: (params) => params.value?.assignee || "Unassigned",
},
];
export const userSettings = {
username: "johndoe",
email: "john.doe@example.com",
teamName: "Development Team",
roleName: "Developer",
};
export const CustomToolbar = () => (
);
export const taskColumns: GridColDef[] = [
{ field: "title", headerName: "Title", width: 200 },
{ field: "status", headerName: "Status", width: 150 },
{ field: "priority", headerName: "Priority", width: 150 },
{ field: "dueDate", headerName: "Due Date", width: 150 },
];
export const COLORS = ["#0088FE", "#00C49F", "#FFBB28", "#FF8042"];
export const priorityColumns: GridColDef[] = [
{
field: "title",
headerName: "Title",
width: 100,
},
{
field: "description",
headerName: "Description",
width: 200,
},
{
field: "status",
headerName: "Status",
width: 130,
renderCell: (params) => (
{params.value}
),
},
{
field: "priority",
headerName: "Priority",
width: 75,
},
{
field: "tags",
headerName: "Tags",
width: 130,
},
{
field: "startDate",
headerName: "Start Date",
width: 130,
},
{
field: "dueDate",
headerName: "Due Date",
width: 130,
},
{
field: "author",
headerName: "Author",
width: 150,
renderCell: (params) => params.value?.username || "Unknown",
},
{
field: "assignee",
headerName: "Assignee",
width: 150,
renderCell: (params) => params.value?.username || "Unassigned",
},
];
export const formFields = {
signUp: {
username: {
order: 1,
placeholder: "Choose a username",
label: "Username",
inputProps: { required: true },
},
email: {
order: 1,
placeholder: "Enter your email address",
label: "Email",
inputProps: { type: "email", required: true },
},
password: {
order: 3,
placeholder: "Enter your password",
label: "Password",
inputProps: { type: "password", required: true },
},
confirm_password: {
order: 4,
placeholder: "Confirm your password",
label: "Confirm Password",
inputProps: { type: "password", required: true },
},
},
};
================================================
FILE: production-level-application/components/global/border-view/index.tsx
================================================
import { useGetTasksQuery, useUpdateTasksMutation } from "@/state/api";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { Spinner } from "../loader/spinner";
import TaskColumn from "../task-column";
type Props = {
id: string;
setIsModelNewTasOpen: (isOpen: boolean) => void;
};
const taskStatus = ["To Do", "Work In Progress", "Under Review", "Completed"];
function BorderView({ id, setIsModelNewTasOpen }: Props) {
const { data: tasks, isLoading, error } = useGetTasksQuery({ projectId: id });
const [updateTaskStatus] = useUpdateTasksMutation();
const moveTask = (taskId: number, toStatus: string) => {
updateTaskStatus({ taskId, status: toStatus });
};
if (isLoading) return ;
if (error) return An error has occurred
;
return (
{taskStatus.map((status) => (
))}
);
}
export default BorderView;
================================================
FILE: production-level-application/components/global/client-only/index.tsx
================================================
"use client";
import { motion } from "framer-motion";
type Props = {
children: React.ReactNode;
};
function ClientOnly({ children }: Props) {
return (
{children}
);
}
export default ClientOnly;
================================================
FILE: production-level-application/components/global/image-card/index.tsx
================================================
import { ImageCardProps } from "@/types/type";
import React from "react";
type Props = {};
function ImageCard({ src, aspectRatio, marginTop = "" }: ImageCardProps) {
return (
);
}
export default ImageCard;
================================================
FILE: production-level-application/components/global/image-upload/index.tsx
================================================
"use client";
import { Camera, PhoneOutgoingIcon } from "lucide-react";
import { CldUploadWidget } from "next-cloudinary";
import Image from "next/image";
import React, { useCallback } from "react";
declare global {
var cloudinary: any;
}
type Props = {
setImageSrc: (image: { secure_url: string }) => void;
value: string;
};
function ImageUpload({ setImageSrc, value }: Props) {
/* const [resource, setResource] = React.useState(null);
console.log("🚀 ~ ImageUpload ~ resource:", resource.secure_url); */
/* const handleCallback = useCallback(
(result: any) => {
onChange(result.info.secure_url);
},
[onchange]
); */
return (
{
if (result?.info?.secure_url) {
setImageSrc({ secure_url: result.info.secure_url });
} else {
console.error("Upload failed, secure_url not found");
}
}}
uploadPreset="pmapppreset"
options={{
maxFiles: 1,
}}
>
{({ open }) => {
return (
open?.()}
className="relative cursor-pointer hover:opacity-70 transition border-dashed border-2 p-20 border-neutral-300 flex flex-col justify-center items-center gap-4 text-neutral-600 h-96"
>
Click to upload
{value && (
)}
);
}}
);
}
export default ImageUpload;
================================================
FILE: production-level-application/components/global/list-view/index.tsx
================================================
import { useGetTasksQuery } from "@/state/api";
import { TasksTypes } from "@/types/type";
import { Spinner } from "../loader/spinner";
import Header from "@/components/header";
import TaskCard from "../task-card";
type Props = {
id: string;
setIsModelNewTasOpen: (isOpen: boolean) => void;
};
function ListView({ id, setIsModelNewTasOpen }: Props) {
const { data: tasks, error, isLoading } = useGetTasksQuery({ projectId: id });
if (isLoading) return ;
if (error) return An error has occurred
;
return (
setIsModelNewTasOpen(true)}
>
Add Task
}
isSmallText
/>
{tasks?.map((task: TasksTypes) => (
))}
);
}
export default ListView;
================================================
FILE: production-level-application/components/global/loader/buttonLoader.tsx
================================================
type SpinnerProps = {
color?: string;
};
export const ButtonLoader = ({ color }: SpinnerProps) => {
return (
);
};
================================================
FILE: production-level-application/components/global/loader/spinner.tsx
================================================
type SpinnerProps = {
color?: string;
};
export const Spinner = ({ color }: SpinnerProps) => {
return (
);
};
================================================
FILE: production-level-application/components/global/project-header/index.tsx
================================================
import Header from "@/components/header";
import ModalNewProject from "@/components/modal/modal-new-project";
import ModelShareModel from "@/components/modal/model-share-project";
import {
useDeleteProjectMutation,
useGetUserQuery,
useRemoveUserFromTaskMutation,
} from "@/state/api";
import { ProjectTypes } from "@/types/type";
import {
Clock,
Filter,
Grid3x3,
Grid3X3,
List,
LogOut,
PlusSquare,
Share2,
Table,
Trash2,
Users,
} from "lucide-react";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { toast } from "react-toastify";
import TabButton from "../tab-button";
type Props = {
id: string;
userId?: string;
activeTab: string;
projectsUserId?: ProjectTypes;
setActiveTab: (tabName: string) => void;
};
function ProjectHeader({
activeTab,
setActiveTab,
id,
userId,
projectsUserId,
}: Props) {
const router = useRouter();
const { data: user, error } = useGetUserQuery({
userId: userId ?? "defaultUserId",
});
const [deleteProject] = useDeleteProjectMutation();
const [removeUsers, { error: removeUser }] = useRemoveUserFromTaskMutation();
const [sharedProjectId, setSharedProjectId] = useState("");
const [isModalNameProjectOpen, setIsModalNameProjectOpen] = useState(false);
const [isModelShareProjectOpen, setIsModelShareProjectOpen] = useState(false);
/* const handleUpdateAuthors = async (authorIds: string, sProjectId: string) => {
if (!authorIds) return;
if (!sProjectId) return;
try {
await updateProjectAuthors({
projectId: sProjectId,
userId: authorIds,
}).then(() => setIsModalNameProjectOpen(false));
} catch (error: any) {
console.log("🚀 ~ handleUpdateAuthors ~ error:", error);
}
}; */
const handleRemoveAuthor = async (userId: string, projectId: string) => {
if (!userId) return;
if (!projectId) return;
try {
await removeUsers({
projectId: projectId,
userId: userId,
}).then(() =>
toast.warning("Leave Project Successfully!", {
position: "bottom-right",
})
);
router.refresh();
router.push("/dashboard");
} catch (error: any) {
console.error("Error removing user from Project:", error);
}
};
const handleDelete = async (projectId: string) => {
if (!projectId) {
console.error("projectId is undefined or invalid");
return;
}
try {
deleteProject({ projectId }).then(() =>
toast.warning("Project Delete Successfully!", {
position: "bottom-right",
})
);
router.refresh();
router.push("/dashboard");
} catch (error) {
console.error("Error deleting Project:", error);
}
};
return (
setIsModalNameProjectOpen(false)}
/>
setIsModelShareProjectOpen(false)}
/>
{/* */}
setIsModalNameProjectOpen(true)}
>
New Board
setIsModelShareProjectOpen(true)}
>
Invite or Join
{projectsUserId?.userId === user?.id ? (
handleDelete(id)}
>
Delete
) : (
handleRemoveAuthor(
String(user?.id),
String(projectsUserId?.id)
)
}
>
Leave
)}
}
/>
}
activeTab={activeTab}
setActiveTab={setActiveTab}
/>
}
activeTab={activeTab}
setActiveTab={setActiveTab}
/>
}
activeTab={activeTab}
setActiveTab={setActiveTab}
/>
}
activeTab={activeTab}
setActiveTab={setActiveTab}
/>
);
}
export default ProjectHeader;
================================================
FILE: production-level-application/components/global/project-page/index.tsx
================================================
"use client";
import ModelNewTask from "@/components/modal/model-new-task";
import { useGetProjectsQuery } from "@/state/api";
import { ProjectTypes } from "@/types/type";
import { useUser } from "@clerk/nextjs";
import { useEffect, useState } from "react";
import BorderView from "../border-view";
import ClientOnly from "../client-only";
import ListView from "../list-view";
import ProjectHeader from "../project-header";
import TableView from "../table-view";
import Timeline from "../timeline-view";
type Props = {
id: string;
};
function ProjectPage({ id }: Props) {
const { user } = useUser();
const [activeTAB, setActiveTAB] = useState("Board");
const [filterData, setFilterData] = useState();
const [isModelNewTasOpen, setIsModelNewTasOpen] = useState(false);
const { data: projects, isLoading: isProjectsLoading } =
useGetProjectsQuery();
useEffect(() => {
const filterData = projects?.find((p) => String(p.id) === String(id));
setFilterData(filterData);
}, [id, projects]);
return (
setIsModelNewTasOpen(false)}
/>
{activeTAB === "Board" && (
)}
{activeTAB === "List" && (
)}
{activeTAB === "Timeline" && (
)}
{activeTAB === "Table" && (
)}
);
}
export default ProjectPage;
================================================
FILE: production-level-application/components/global/tab-button/index.tsx
================================================
import React from "react";
type Props = {
name: string;
icon: React.ReactNode;
setActiveTab: (tabName: string) => void;
activeTab: string;
};
function TabButton({ activeTab, icon, name, setActiveTab }: Props) {
const isActive = activeTab === name;
return (
setActiveTab(name)}
>
{icon}
{name}
);
}
export default TabButton;
================================================
FILE: production-level-application/components/global/table-view/index.tsx
================================================
import { columns } from "@/components/data/columns";
import Header from "@/components/header";
import { useAppSelector } from "@/components/wrapper/redux";
import { dataGridClassNames, dataGridSxStyles } from "@/lib/utils";
import { useGetTasksQuery } from "@/state/api";
import { DataGrid } from "@mui/x-data-grid";
import { Spinner } from "../loader/spinner";
type Props = {
id: string;
setIsModelNewTasOpen: (isOpen: boolean) => void;
};
function TableView({ id, setIsModelNewTasOpen }: Props) {
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
const { data: tasks, error, isLoading } = useGetTasksQuery({ projectId: id });
if (isLoading) return ;
if (error) return An error has occurred
;
return (
setIsModelNewTasOpen(true)}
>
Add Task
}
isSmallText
/>
);
}
export default TableView;
================================================
FILE: production-level-application/components/global/task-card/index.tsx
================================================
import { TasksTypes } from "@/types/type";
import { format } from "date-fns";
import Image from "next/image";
import React from "react";
type Props = {
task: TasksTypes;
};
function TaskCard({ task }: Props) {
return (
{task.attachments && task.attachments.length > 0 && (
Attachments:
{task.attachments && task.attachments.length > 0 && (
)}
)}
ID: {task.id}
Title: {task.title}
Description: {" "}
{task.description || "No description provided"}
Status: {task.status}
Priority: {task.priority}
Tags: {task.tags || "No tags"}
Start Date: {" "}
{task.startDate ? format(new Date(task.startDate), "P") : "Not set"}
Due Date: {" "}
{task.dueDate ? format(new Date(task.dueDate), "P") : "Not set"}
Author: {" "}
{task.author ? task.author.firstName : "Unknown"}
Assignee: {" "}
{task.assignee ? task.assignee.firstName : "Unassigned"}
);
}
export default TaskCard;
================================================
FILE: production-level-application/components/global/task-column/index.tsx
================================================
import { EllipsisVertical, Plus } from "lucide-react";
import React from "react";
import { useDrop } from "react-dnd";
import { TasksTypes } from "@/types/type";
import Task from "@/components/task";
type Props = {
status: string;
tasks: TasksTypes[];
moveTask: (taskId: number, toStatus: string) => void;
setIsModelNewTasOpen: (isOpen: boolean) => void;
};
function TaskColumn({ moveTask, setIsModelNewTasOpen, status, tasks }: Props) {
const [{ isOver }, drop] = useDrop(() => ({
accept: "task",
drop: (item: { id: number }) => moveTask(item.id, status),
collect: (monitor: any) => ({
isOver: !!monitor.isOver(),
}),
}));
const tasksCount = tasks.filter((task) => task.status === status).length;
const statusColor: any = {
"To Do": "#2563EB",
"Work In Progress": "#059669",
"Under Review": "#D97706",
Completed: "#000000",
};
return (
{
drop(instance);
}}
className={`sl:py-4 rounded-lg py-2 xl:px-2 ${
isOver ? "bg-blue-100 dark:bg-neutral-950" : ""
}`}
>
{status}{" "}
{tasksCount}
setIsModelNewTasOpen(true)}
>
{tasks
.filter((task) => task.status === status)
.map((task) => (
))}
);
}
export default TaskColumn;
================================================
FILE: production-level-application/components/global/timeline-view/index.tsx
================================================
import { useAppSelector } from "@/components/wrapper/redux";
import { useGetTasksQuery } from "@/state/api";
import { DisplayOption, Gantt, ViewMode } from "gantt-task-react";
import "gantt-task-react/dist/index.css";
import React, { useMemo, useState } from "react";
import { Spinner } from "../loader/spinner";
type Props = {
id: string;
setIsModelNewTasOpen: (isOpen: boolean) => void;
};
type TaskTypeItems = "task" | "milestone" | "project";
function Timeline({ id, setIsModelNewTasOpen }: Props) {
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
const { data: tasks, error, isLoading } = useGetTasksQuery({ projectId: id });
const [displayOptions, setDisplayOptions] = useState({
viewMode: ViewMode.Month,
locale: "en-US",
});
const ganttTasks = useMemo(() => {
return (
tasks?.map((task) => ({
start: new Date(task.startDate as string),
end: new Date(task.dueDate as string),
name: task.title,
id: `Task-${task.id}`,
type: "task" as TaskTypeItems,
progress: task.points ? (task.points / 100) * 100 : 0,
isDisabled: false,
})) || []
);
}, [tasks]);
const handleViewModeChange = (
event: React.ChangeEvent
) => {
setDisplayOptions((prevOptions) => ({
...prevOptions,
viewMode: event.target.value as ViewMode,
}));
};
if (isLoading) return ;
if (error) return An error has occurred
;
return (
Project Tasks Timeline
Day
Week
Month
setIsModelNewTasOpen(true)}
>
Add New Task
);
}
export default Timeline;
================================================
FILE: production-level-application/components/header/index.tsx
================================================
import React from "react";
type Props = {
name: string;
buttonComponent?: any;
isSmallText?: boolean;
};
function Header({ name, buttonComponent, isSmallText }: Props) {
return (
{name}
{buttonComponent}
);
}
export default Header;
================================================
FILE: production-level-application/components/home-page/index.tsx
================================================
/* eslint-disable @next/next/no-img-element */
"use client";
import { dataGridClassNames, dataGridSxStyles } from "@/lib/utils";
import { useGetProjectsQuery, useGetTasksQuery } from "@/state/api";
import { Priority, ProjectTypes, TasksTypes } from "@/types/type";
import { DataGrid } from "@mui/x-data-grid";
import { useState } from "react";
import {
Bar,
BarChart,
CartesianGrid,
Cell,
Legend,
Pie,
PieChart,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts";
import { COLORS, taskColumns } from "../data/columns";
import { Spinner } from "../global/loader/spinner";
import Header from "../header";
import { useAppSelector } from "../wrapper/redux";
type Props = {
projectId?: number;
project: ProjectTypes[];
};
function HomePage({ projectId, project }: Props) {
const [selectProjectId, setSelectProjectId] = useState(projectId);
const {
data: tasks,
isLoading: tasksLoading,
isError: tasksError,
} = useGetTasksQuery({
projectId: String(selectProjectId),
});
const { data: projects, isLoading: isProjectsLoading } =
useGetProjectsQuery();
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
if (tasksLoading || isProjectsLoading) return ;
if (tasksError || !tasks || !projects)
return An error has occurred
;
const priorityCount = tasks.reduce(
(acc: Record, task: TasksTypes) => {
const { priority } = task;
acc[priority as Priority] = (acc[priority as Priority] || 0) + 1;
return acc;
},
{}
);
const taskDistribution = Object.keys(priorityCount).map((key) => ({
name: key,
count: priorityCount[key],
}));
const statusCount = tasks.reduce(
(acc: Record, task: TasksTypes) => {
const { status } = task;
acc[status as any] = (acc[status as any] || 0) + 1;
return acc;
},
{}
);
const projectStatus = Object.keys(statusCount).map((key) => ({
name: key,
count: statusCount[key],
}));
const chartColors = isDarkMode
? {
bar: "#8884d8",
barGrid: "#303030",
pieFill: "#4A90E2",
text: "#FFFFFF",
}
: {
bar: "#8884d8",
barGrid: "#E0E0E0",
pieFill: "#82ca9d",
text: "#000000",
};
const selectStyles =
"mb-4 block w-full rounded border border-gray-300 px-3 py-2 dark:border-dark-tertiary dark:bg-dark-tertiary dark:text-white dark:focus:outline-none";
return (
<>
{tasks.length > 0 ? (
setSelectProjectId(e.target.value as any)}
>
{project.map((project) => (
{project.name.toUpperCase()}
))}
Task Priority Distribution
Project Status
{projectStatus.map((entry, index) => (
|
))}
Your Tasks
"data-grid-row"}
getCellClassName={() => "data-grid-cell"}
className={dataGridClassNames}
sx={dataGridSxStyles(isDarkMode)}
/>
) : (
{project.length > 0 && (
setSelectProjectId(e.target.value as any)}
>
{project.map((project) => (
{project.name.toUpperCase()}
))}
)}
{`Currently You Don't have Task`}
)}
>
);
}
export default HomePage;
================================================
FILE: production-level-application/components/home-page/root-home-page/index.tsx
================================================
/* eslint-disable @next/next/no-img-element */
"use client";
import ModalNewProject from "@/components/modal/modal-new-project";
import { useCreateUserMutation, useGetProjectsQuery } from "@/state/api";
import { useUser } from "@clerk/nextjs";
import { PlusSquare } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import HomePage from "..";
type Props = {};
function RootHomePage({}: Props) {
const { isSignedIn, user } = useUser();
const [createUser] = useCreateUserMutation();
const { data: projects, isLoading: isProjectsLoading } =
useGetProjectsQuery();
const [isModalNameProjectOpen, setIsModalNameProjectOpen] = useState(false);
const createCurrentUser = useMemo(
() => async () => {
if (user?.id) {
await createUser({
firstName: user.firstName! || user.username!,
secondName: user.lastName! || "",
profilePictureUrl: user.firstName
? user.imageUrl
: `https://api.dicebear.com/9.x/initials/png?seed=${user.username}`,
clerkId: user.id,
});
}
},
[
createUser,
user?.firstName,
user?.id,
user?.imageUrl,
user?.lastName,
user?.username,
]
);
useEffect(() => {
createCurrentUser();
}, [createCurrentUser, user?.id]);
return (
{projects && projects.length > 0 ? (
) : (
{`Currently You Don't have Project`}
setIsModalNameProjectOpen(false)}
/>
setIsModalNameProjectOpen(true)}
>
New Board
)}
);
}
export default RootHomePage;
================================================
FILE: production-level-application/components/modal/index.tsx
================================================
import { X } from "lucide-react";
import React from "react";
import ReactDOM from "react-dom";
import ClientOnly from "../global/client-only";
import Header from "../header";
type Props = {
children: React.ReactNode;
isOpen: boolean;
onClose: () => void;
name: string;
};
function Modal({ children, isOpen, onClose, name }: Props) {
if (!isOpen) return null;
return ReactDOM.createPortal(
}
isSmallText
/>
{children}
,
document.body
);
}
export default Modal;
================================================
FILE: production-level-application/components/modal/modal-new-project/index.tsx
================================================
import { ButtonLoader } from "@/components/global/loader/buttonLoader";
import { useCreateProjectMutation } from "@/state/api";
import { useUser } from "@clerk/nextjs";
import { formatISO } from "date-fns";
import { useState } from "react";
import { toast } from "react-toastify";
import Modal from "..";
type Props = {
isOpen: boolean;
onClose: () => void;
};
function ModalNewProject({ isOpen, onClose }: Props) {
const { isSignedIn, user, isLoaded } = useUser();
const [createProject, { isLoading }] = useCreateProjectMutation();
const [projectName, setProjectName] = useState("");
const [description, setDescription] = useState("");
const [startDate, setStartDate] = useState("");
const [endDate, setEndDate] = useState("");
const handleSubmit = async () => {
if (!projectName || !startDate || !endDate) return;
const formateStartDate = formatISO(new Date(startDate), {
representation: "complete",
});
const formateEndDate = formatISO(new Date(endDate), {
representation: "complete",
});
await createProject({
user: user?.id,
name: projectName,
description,
startDate: formateStartDate,
endDate: formateEndDate,
})
.then(() =>
toast.success("Project Created Successfully!", {
position: "bottom-right",
})
)
.catch(() =>
toast.error("Project Created Error", { position: "bottom-right" })
);
onClose();
};
const isFormValid = () => {
return projectName && description && startDate && endDate;
};
const inputStyle =
"w-full rounded-md border border-gray-300 p-2 shadow-sm dark:border-dark-tertiary dark:bg-dark-tertiary dark:text-white dark:focus:outline-none";
return (
{
e.preventDefault();
handleSubmit();
}}
>
setProjectName(e.target.value)}
/>
setDescription(e.target.value)}
/>
setStartDate(e.target.value)}
/>
setEndDate(e.target.value)}
/>
{isLoading ? : "Create Project"}
);
}
export default ModalNewProject;
================================================
FILE: production-level-application/components/modal/model-new-task/index.tsx
================================================
import ImageUpload from "@/components/global/image-upload";
import { ButtonLoader } from "@/components/global/loader/buttonLoader";
import {
useCreateTasksMutation,
useGetUserQuery,
useLazyGetTasksQuery,
} from "@/state/api";
import { Priority, Status } from "@/types/type";
import { formatISO } from "date-fns";
import { useState } from "react";
import { toast } from "react-toastify";
import Modal from "..";
type Props = {
id?: string | null;
userId?: string;
isOpen: boolean;
onClose: () => void;
};
function ModelNewTask({ id, userId, isOpen, onClose }: Props) {
const { data: user, error } = useGetUserQuery({
userId: userId ?? "defaultUserId",
});
const [createTask, { isLoading }] = useCreateTasksMutation();
const [refetchTasks] = useLazyGetTasksQuery();
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [status, setStatus] = useState(Status.ToDo);
const [priority, setPriority] = useState(Priority.Backlog);
const [tags, setTags] = useState("");
const [startDate, setStartDate] = useState("");
const [dueDate, setDueDate] = useState("");
const [projectId, setProjectId] = useState("");
const [imageSrc, setImageSrc] = useState({
secure_url: "",
});
const handleSubmit = async () => {
if (!title || !(id !== null || projectId)) return;
const formateStartDate = formatISO(new Date(startDate), {
representation: "complete",
});
const formateEndDate = formatISO(new Date(dueDate), {
representation: "complete",
});
await createTask({
title,
description,
status,
priority,
tags,
startDate: formateStartDate,
dueDate: formateEndDate,
authorUserId: /* user?.id */ userId,
projectId: id !== null ? id : projectId,
imageSrc: imageSrc.secure_url,
userId: user?.id,
})
.then(() =>
toast.success("Task Created Successfully!", {
position: "bottom-right",
})
)
.catch(() =>
toast.error("Task Created Error", { position: "bottom-right" })
);
refetchTasks({ projectId: String(id) });
setTitle("");
setDescription("");
setStatus(Status.ToDo);
setPriority(Priority.Backlog);
setTags("");
setStartDate("");
setDueDate("");
setProjectId("");
setImageSrc({
secure_url: "",
});
onClose();
};
const isFormValid = () => {
return title && (id !== null || projectId) && startDate && dueDate;
};
const selectStyles =
"mb-4 block w-full rounded border border-gray-300 px-3 py-2 dark:border-dark-tertiary dark:bg-dark-tertiary dark:text-white dark:focus:outline-none";
const inputStyle =
"w-full rounded-md border border-gray-300 p-2 shadow-sm dark:border-dark-tertiary dark:bg-dark-tertiary dark:text-white dark:focus:outline-none";
return (
{
e.preventDefault();
handleSubmit();
}}
>
setTitle(e.target.value)}
/>
setDescription(e.target.value)}
/>
setStatus(e.target.value as any)}
>
Select Status
To Do
Work In Progress
Under Review
Completed
setPriority(Priority[e.target.value as keyof typeof Priority])
}
>
Select Priority
Urgent
High
Medium
Low
Backlog
setTags(e.target.value)}
/>
setStartDate(e.target.value)}
/>
setDueDate(e.target.value)}
/>
{/* setAuthorUserId(e.target.value)}
/>
setAssignedUserId(e.target.value)}
/> */}
{id === undefined && (
setProjectId(e.target.value)}
/>
)}
{isLoading ? : "Create Task"}
);
}
export default ModelNewTask;
================================================
FILE: production-level-application/components/modal/model-share-project/index.tsx
================================================
import { useState } from "react";
import Modal from "..";
import { useUpdateProjectAuthorsMutation } from "@/state/api";
import { toast } from "react-toastify";
type Props = {
id: string;
currentUserId: string;
isOpen: boolean;
onClose: () => void;
sharedProjectId: string;
setSharedProjectId: (sharedProjectId: string) => void;
};
function ModelShareModel({
isOpen,
onClose,
id,
currentUserId,
setSharedProjectId,
sharedProjectId,
}: Props) {
const [updateProjectAuthors, { error: updateError }] =
useUpdateProjectAuthorsMutation();
const [copied, setCopied] = useState(false);
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(id);
setCopied(true);
setTimeout(() => setCopied(false), 2000); // Reset after 2 seconds
} catch (err) {
console.error("Failed to copy:", err);
}
};
const handleUpdateAuthors = async (authorIds: string, sProjectId: string) => {
if (!authorIds) return;
if (!sProjectId) return;
try {
await updateProjectAuthors({
projectId: sProjectId,
userId: authorIds,
})
.then(() => {
if (
updateError &&
"status" in updateError &&
updateError.status === 401
) {
toast.error(
"Your Already In Project Or Current Project Not available!",
{
position: "bottom-right",
}
);
} else if (
updateError &&
"status" in updateError &&
updateError.status === 400
) {
toast.error("Missing projectId", {
position: "bottom-right",
});
} else {
toast.success("Project Join Successfully!", {
position: "bottom-right",
});
setSharedProjectId("");
onClose();
}
})
.catch(() =>
toast.error("Project Join Error", { position: "bottom-right" })
);
} catch (error: any) {
console.log("🚀 ~ handleUpdateAuthors ~ error:", error);
}
};
return (
);
}
export default ModelShareModel;
================================================
FILE: production-level-application/components/navbar/home/index.tsx
================================================
"use client";
import ImageCard from "@/components/global/image-card";
import { useAppDispatch, useAppSelector } from "@/components/wrapper/redux";
import { setIsDarkMode } from "@/state";
import { Moon, Sun } from "lucide-react";
import Link from "next/link";
import React from "react";
type Props = {};
const imageData = [
{
src: "https://www.itarian.com/assets-new/images/project-management.png",
aspectRatio: "0.94",
},
{
src: "https://png.pngtree.com/png-vector/20220527/ourmid/pngtree-woman-doing-project-management-png-image_4752006.png",
aspectRatio: "0.94",
marginTop: "mt-4",
},
/* {
src: "https://marketplace.canva.com/EAE2pXyqyx0/1/0/1600w/canva-simple-instagram-frame-template-instagram-post-ObQSn5BL2ZQ.jpg",
aspectRatio: "1.7",
marginTop: "mt-4",
}, */
];
const imageData2 = [
{
src: "https://cdni.iconscout.com/illustration/premium/thumb/online-test-illustration-download-in-svg-png-gif-file-formats--exam-education-e-learning-digital-world-pack-seo-web-illustrations-6770271.png?f=webp",
aspectRatio: "1.7",
},
{
src: "https://static.vecteezy.com/system/resources/previews/022/608/528/non_2x/business-infographic-analysis-marketing-data-analysis-marketing-business-solutions-free-png.png",
aspectRatio: "0.94",
marginTop: "mt-4",
},
{
src: "https://cdni.iconscout.com/illustration/premium/thumb/project-planning-illustration-download-in-svg-png-gif-file-formats--management-plan-business-discussion-teamwork-pack-illustrations-6666400.png?f=webp",
aspectRatio: "0.94",
marginTop: "mt-4",
},
];
function HomeNavBar({}: Props) {
const dispatch = useAppDispatch();
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
return (
{isDarkMode && (
)}
{/*
li
*/}
Project Management
Features
Pricing
About
dispatch(setIsDarkMode(!isDarkMode))}
className={
isDarkMode
? `rounded p-2 hover:bg-gray-700`
: `rounded p-2 hover:bg-gray-100`
}
>
{isDarkMode ? (
) : (
)}
Login
{/* Rest of the component implementation follows with the extracted components */}
{/* Features section */}
Smart Project Management System for Agile Teams
{`The Smart Project Management System is a comprehensive
web-based platform designed to help Agile teams
efficiently plan, execute, and monitor projects. The
system offers a collaborative workspace where team
members can create tasks, assign responsibilities, set
deadlines, and track progress using visual tools like
Kanban boards and Gantt charts.`}
Get Started
Learn More
{/*
{imageData.map((image, index) => (
))}
{imageData2.map((image, index) => (
))}
*/}
);
}
export default HomeNavBar;
================================================
FILE: production-level-application/components/navbar/index.tsx
================================================
import { setIsDarkMode, setIsSidebarCollapsed } from "@/state";
import { UserButton } from "@clerk/nextjs";
import { Menu, Moon, Search, Settings, Sun } from "lucide-react";
import Link from "next/link";
import { useAppDispatch, useAppSelector } from "../wrapper/redux";
type Props = {};
function Navbar({}: Props) {
const dispatch = useAppDispatch();
const isSidebarCollapsed = useAppSelector(
(state) => state.global.isSidebarCollapsed
);
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
return (
{!isSidebarCollapsed ? null : (
dispatch(setIsSidebarCollapsed(!isSidebarCollapsed))}
>
)}
dispatch(setIsDarkMode(!isDarkMode))}
className={
isDarkMode
? `rounded p-2 dark:hover:bg-gray-700`
: `rounded p-2 hover:bg-gray-100`
}
>
{isDarkMode ? (
) : (
)}
);
}
export default Navbar;
================================================
FILE: production-level-application/components/priority-page/index.tsx
================================================
"use client";
import { dataGridClassNames, dataGridSxStyles } from "@/lib/utils";
import { useGetTasksByUserQuery } from "@/state/api";
import { Priority, TasksTypes } from "@/types/type";
import { useUser } from "@clerk/nextjs";
import { DataGrid } from "@mui/x-data-grid";
import { useState } from "react";
import { priorityColumns } from "../data/columns";
import TaskCard from "../global/task-card";
import Header from "../header";
import ModelNewTask from "../modal/model-new-task";
import { useAppSelector } from "../wrapper/redux";
type Props = {
priority: Priority;
};
function PriorityPage({ priority }: Props) {
const { isSignedIn, user } = useUser();
const [view, setView] = useState("list");
const [isMobileNewTaskOpen, setIsMobileNewTaskOpen] = useState(false);
const userId = user?.id ?? null;
const {
data: task,
isLoading,
isError: isTasksError,
} = useGetTasksByUserQuery(userId as any);
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
const filteredTasks = task?.filter(
(t: TasksTypes) => t.priority === priority
);
if (isTasksError || !task) return An error occurred
;
return (
setIsMobileNewTaskOpen(false)}
/>
setIsMobileNewTaskOpen(true)}
>
Add Task
}
/>
setView("list")}
>
List
setView("table")}
>
Table
{isLoading ? (
Loading tasks...
) : view === "list" ? (
{filteredTasks?.map((task: TasksTypes) => (
))}
) : (
view === "table" &&
filteredTasks && (
row.id}
className={dataGridClassNames}
sx={dataGridSxStyles(isDarkMode)}
/>
)
)}
);
}
export default PriorityPage;
================================================
FILE: production-level-application/components/sidebar/index.tsx
================================================
"use client";
import { setIsSidebarCollapsed } from "@/state";
import { useGetProjectsQuery } from "@/state/api";
import {
AlertCircle,
AlertOctagon,
AlertTriangle,
Briefcase,
ChevronDown,
ChevronUp,
Home,
Layers3,
LockIcon,
LucideIcon,
PlusSquare,
ShieldAlert,
X,
} from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useState } from "react";
import { useDispatch } from "react-redux";
import ModalNewProject from "../modal/modal-new-project";
import { useAppSelector } from "../wrapper/redux";
type Props = {};
function SideBar({}: Props) {
const dispatch = useDispatch();
const [showProject, setShowProject] = useState(true);
const [showPriority, setShowPriority] = useState(true);
const [isModalNameProjectOpen, setIsModalNameProjectOpen] = useState(false);
const { data: projects } = useGetProjectsQuery();
const isSidebarCollapsed = useAppSelector(
(state) => state.global.isSidebarCollapsed
);
/* if (!currentUser) return null;
const currentUserDetails = currentUser.user; */
const sideBarClassNames = `fixed flex flex-col h-full justify-between shadow-xl
transition-all duration-300 ease-in-out z-40 dark:bg-black overflow-y-auto bg-white
${
isSidebarCollapsed
? "max-w-0 overflow-hidden opacity-0"
: "max-w-64 opacity-100"
}`;
return (
{/* */}
LIST
{isSidebarCollapsed ? null : (
dispatch(setIsSidebarCollapsed(!isSidebarCollapsed))
}
>
)}
{projects && projects.length > 0 && (
)}
{/*
*/}
setShowProject((prev) => !prev)}
className="flex w-full items-center justify-between px-8 py-3 text-gray-500"
>
Projects
{showProject ? (
) : (
)}
{showProject &&
projects?.map((project) => (
))}
setShowPriority((prev) => !prev)}
className="flex w-full items-center justify-between px-8 py-3 text-gray-500"
>
Priory
{showPriority ? (
) : (
)}
{showPriority && (
<>
>
)}
setIsModalNameProjectOpen(false)}
/>
setIsModalNameProjectOpen(true)}
>
New Board
{/* {!!currentUserDetails?.profilePictureUrl ? (
) : (
)} */}
{/* */}
{/*
sashen
Sign out
*/}
);
}
interface SideBarLinksType {
href: string;
icon: LucideIcon;
label: string;
}
const SidebarLink = ({ href, icon: Icon, label }: SideBarLinksType) => {
const pathName = usePathname();
const isActive =
pathName === href || (pathName === "/" && href === "/dashboard");
const screenWidth = window.innerWidth;
const isSidebarCollapsed = useAppSelector(
(state) => state.global.isSidebarCollapsed
);
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
return (
{isActive && (
)}
{label}
);
};
export default SideBar;
// 1.40
================================================
FILE: production-level-application/components/task/index.tsx
================================================
import { useDeleteTasksMutation } from "@/state/api";
import { TasksTypes } from "@/types/type";
import { useUser } from "@clerk/nextjs";
import { format } from "date-fns";
import { EllipsisVertical, Trash2 } from "lucide-react";
import Image from "next/image";
import { useDrag } from "react-dnd";
import ClientOnly from "../global/client-only";
import { toast } from "react-toastify";
type Props = {
task: TasksTypes;
};
function Task({ task }: Props) {
const { user } = useUser();
const [deleteTask] = useDeleteTasksMutation();
const [{ isDragging }, drop] = 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 handleDelete = async (taskId: number) => {
if (!taskId) {
console.error("taskId is undefined or invalid");
return;
}
try {
deleteTask({ taskId }).then(() =>
toast.warning("Task Delete Successfully!", {
position: "bottom-right",
})
);
} catch (error) {
console.error("Error deleting task:", error);
}
};
const timeAgo = (dateString: string): string => {
// Step 1: Check the original date format coming in
//console.log("Original date string:", dateString);
// If the date has 'Z' (UTC), convert it to ISO format
let formattedDateString = dateString;
// Check if the string includes a 'Z', if not, make sure the string is in ISO format
if (!dateString.includes("Z") && dateString.includes(" ")) {
formattedDateString = dateString.replace(" ", "T"); // Convert space to 'T' for ISO format
}
//console.log("Formatted to ISO 8601:", formattedDateString);
// Step 2: Parse the string into a Date object
const createdAt = new Date(formattedDateString);
//console.log("Created At Date Object:", createdAt);
// Step 3: Check if the Date is valid
if (isNaN(createdAt.getTime())) {
// console.error("Invalid Date:", formattedDateString);
return "Invalid date"; // If the date is invalid
}
// Step 4: Get the current time for comparison
const now = new Date();
// console.log("Current Date:", now);
// Step 5: Calculate the time difference in seconds
const differenceInSeconds = Math.floor(
(now.getTime() - createdAt.getTime()) / 1000
);
//console.log("Difference in seconds:", differenceInSeconds);
// Step 6: Handle different time differences
if (differenceInSeconds < 60) {
return `${differenceInSeconds} seconds ago`;
} else if (differenceInSeconds < 3600) {
const minutes = Math.floor(differenceInSeconds / 60);
return `${minutes} minutes ago`;
} else if (differenceInSeconds < 86400) {
const hours = Math.floor(differenceInSeconds / 3600);
return `${hours} hours ago`;
} else if (differenceInSeconds < 2592000) {
const days = Math.floor(differenceInSeconds / 86400);
return `${days} days ago`;
} else if (differenceInSeconds < 31536000) {
const months = Math.floor(differenceInSeconds / 2592000);
return `${months} months ago`;
} else {
const years = Math.floor(differenceInSeconds / 31536000);
return `${years} years ago`;
}
};
const PriorityTags = ({ priority }: { priority: TasksTypes["priority"] }) => (
{priority}
);
return (
{
drop(instance);
}}
className={`mb-4 rounded-md bg-white shadow dark:bg-dark-secondary ${
isDragging ? "opacity-50" : "opacity-100"
}`}
>
{task.imageSrc && (
)}
{task.priority &&
}
{taskTagsSplit.map((tag) => (
{" "}
{tag}
))}
{task.title}
{typeof task.points === "number" && (
{task.points} pts
)}
{formattedStartDate && {formattedStartDate} - }
{formattedDueDate && {formattedDueDate} }
{task.description}
{task.assignee && (
)}
{task.author?.profilePictureUrl && (
)}
{user?.id === task.authorUserId && (
{
handleDelete(task.id);
}}
>
)}
{task.createdAt && (
{timeAgo(task.createdAt)}
)}
);
}
export default Task;
================================================
FILE: production-level-application/components/timeline/index.tsx
================================================
"use client";
import { useGetProjectsQuery } from "@/state/api";
import { TaskTypeItems } from "@/types/type";
import { DisplayOption, Gantt, ViewMode } from "gantt-task-react";
import "gantt-task-react/dist/index.css";
import React, { useMemo, useState } from "react";
import { Spinner } from "../global/loader/spinner";
import Header from "../header";
import { useAppSelector } from "../wrapper/redux";
type Props = {};
function TimeLinePage({}: Props) {
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
const { data: projects, isError, isLoading } = useGetProjectsQuery();
const [displayOptions, setDisplayOptions] = useState({
viewMode: ViewMode.Month,
locale: "en-US",
});
const ganttTasks = useMemo(() => {
return (
projects?.map((project) => ({
start: new Date(project.startDate as string),
end: new Date(project.endDate as string),
name: project.name,
id: `Project-${project.id}`,
type: "project" as TaskTypeItems,
progress: 50,
isDisabled: false,
})) || []
);
}, [projects]);
const handleViewModeChange = (
event: React.ChangeEvent
) => {
setDisplayOptions((prevOptions) => ({
...prevOptions,
viewMode: event.target.value as ViewMode,
}));
};
if (isLoading) return ;
if (isError || !projects)
return An error occurred while fetching projects
;
return (
);
}
export default TimeLinePage;
================================================
FILE: production-level-application/components/wrapper/dashboardWrapper.tsx
================================================
"use client";
import React, { useEffect } from "react";
import Navbar from "../navbar";
import SideBar from "../sidebar";
import { useAppSelector } from "./redux";
type Props = {
children: React.ReactNode;
};
function DashboardLayout({ children }: Props) {
const isSidebarCollapsed = useAppSelector(
(state) => state.global.isSidebarCollapsed
);
const isDarkMode = useAppSelector((state) => state.global.isDarkMode);
useEffect(() => {
if (isDarkMode) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
});
return (
{children}
);
}
const DashboardWrapper = ({ children }: { children: React.ReactNode }) => {
return {children} ;
};
export default DashboardWrapper;
================================================
FILE: production-level-application/components/wrapper/redux.tsx
================================================
import globalReducer from "@/state";
import { api } from "@/state/api";
import { combineReducers, configureStore } from "@reduxjs/toolkit";
import { setupListeners } from "@reduxjs/toolkit/query";
import { useRef } from "react";
import {
Provider,
TypedUseSelectorHook,
useDispatch,
useSelector,
} from "react-redux";
import {
FLUSH,
PAUSE,
PERSIST,
persistReducer,
persistStore,
PURGE,
REGISTER,
REHYDRATE,
} from "redux-persist";
import { PersistGate } from "redux-persist/integration/react";
import createWebStorage from "redux-persist/lib/storage/createWebStorage";
/* REDUX PERSISTENCE */
const createNoopStorage = () => {
return {
getItem(_key: any) {
return Promise.resolve(null);
},
setItem(_key: any, value: any) {
return Promise.resolve(value);
},
removeItem(_key: any) {
return Promise.resolve();
},
};
};
const storage =
typeof window === "undefined"
? createNoopStorage()
: createWebStorage("local");
const persistConfig = {
key: "root",
storage,
whitelist: ["global"],
};
const rootReducer = combineReducers({
global: globalReducer,
[api.reducerPath]: api.reducer,
});
const persistedReducer = persistReducer(persistConfig, rootReducer);
/* REDUX STORE */
export const makeStore = () => {
return configureStore({
reducer: persistedReducer,
// @ts-ignore
middleware: (getDefault) =>
getDefault({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}).concat(api.middleware),
});
};
/* REDUX TYPES */
export type AppStore = ReturnType;
export type RootState = ReturnType;
export type AppDispatch = AppStore["dispatch"];
export const useAppDispatch = () => useDispatch();
export const useAppSelector: TypedUseSelectorHook = useSelector;
/* PROVIDER */
export default function StoreProvider({
children,
}: {
children: React.ReactNode;
}) {
const storeRef = useRef();
if (!storeRef.current) {
storeRef.current = makeStore();
setupListeners(storeRef.current.dispatch);
}
const persistor = persistStore(storeRef.current);
return (
{children}
);
}
================================================
FILE: production-level-application/lib/prismadb.ts
================================================
import { PrismaClient } from "@prisma/client";
declare global {
var prisma: PrismaClient | undefined;
}
const client = globalThis.prisma || new PrismaClient();
if (process.env.NODE_ENV !== "production") globalThis.prisma = client;
export default client;
================================================
FILE: production-level-application/lib/utils.ts
================================================
export const dataGridClassNames =
"border border-gray-200 bg-white shadow dark:border-stroke-dark dark:bg-dark-secondary dark:text-gray-200";
export const dataGridSxStyles = (isDarkMode: boolean) => {
return {
"& .MuiDataGrid-columnHeaders": {
color: `${isDarkMode ? "#e5e7eb" : ""}`,
'& [role="row"] > *': {
backgroundColor: `${isDarkMode ? "#1d1f21" : "white"}`,
borderColor: `${isDarkMode ? "#2d3135" : ""}`,
},
},
"& .MuiIconbutton-root": {
color: `${isDarkMode ? "#a3a3a3" : ""}`,
},
"& .MuiTablePagination-root": {
color: `${isDarkMode ? "#a3a3a3" : ""}`,
},
"& .MuiTablePagination-selectIcon": {
color: `${isDarkMode ? "#a3a3a3" : ""}`,
},
"& .MuiDataGrid-cell": {
border: "none",
},
"& .MuiDataGrid-row": {
borderBottom: `1px solid ${isDarkMode ? "#2d3135" : "e5e7eb"}`,
},
"& .MuiDataGrid-withBorderColor": {
borderColor: `${isDarkMode ? "#2d3135" : "e5e7eb"}`,
},
};
};
================================================
FILE: production-level-application/middleware.ts
================================================
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
const isProtectedRoute = createRouteMatcher(["/dashboard(.*)", "/api(.*)"]);
export default clerkMiddleware(async (auth, req) => {
if (isProtectedRoute(req)) await auth.protect();
});
export const config = {
matcher: [
// Skip Next.js internals and all static files, unless found in search params
"/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
// Always run for API routes
"/(api|trpc)(.*)",
],
};
================================================
FILE: production-level-application/next.config.mjs
================================================
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "res.cloudinary.com",
port: "",
pathname: "/**",
},
{
protocol: "https",
hostname: "img.clerk.com",
port: "",
pathname: "/**",
},
{
protocol: "https",
hostname: "api.dicebear.com",
port: "",
pathname: "/**",
},
],
},
};
export default nextConfig;
================================================
FILE: production-level-application/package.json
================================================
{
"name": "pm-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"postinstall": "prisma generate"
},
"dependencies": {
"@clerk/nextjs": "^6.9.14",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/material": "^6.4.1",
"@mui/x-data-grid": "^7.24.0",
"@prisma/client": "^6.2.1",
"@reduxjs/toolkit": "^2.5.0",
"date-fns": "^4.1.0",
"framer-motion": "^12.0.5",
"gantt-task-react": "^0.3.9",
"lodash": "^4.17.21",
"lucide-react": "^0.473.0",
"next": "14.2.7",
"next-cloudinary": "^6.16.0",
"react": "^18",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18",
"react-redux": "^9.2.0",
"react-toastify": "^11.0.3",
"recharts": "^2.15.0",
"redux-persist": "^6.0.0",
"uuid": "^11.0.5"
},
"devDependencies": {
"@types/lodash": "^4.17.14",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.7",
"postcss": "^8",
"prisma": "^6.2.1",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
================================================
FILE: production-level-application/postcss.config.mjs
================================================
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;
================================================
FILE: production-level-application/prisma/schema.prisma
================================================
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
clerkId String @unique
firstName String
secondName String
emailAddress String?
profilePictureUrl String?
taskAssignments TaskAssignment[]
attachments Attachment[]
comments Comment[]
team Team[]
Project Project[]
Task Task[]
}
model Team {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
teamName String
productOwnerUserId Int?
projectManagerUserId Int?
projectTeams ProjectTeam[]
user User[]
}
model Project {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String
description String?
startDate DateTime?
endDate DateTime?
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String? @db.Uuid
authorsIds String[]
tasks Task[]
projectTeams ProjectTeam[]
}
model ProjectTeam {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
teamId String @db.Uuid
projectId String @db.Uuid
team Team @relation(fields: [teamId], references: [id])
project Project @relation(fields: [projectId], references: [id])
}
model Task {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
title String
description String?
status String?
priority String?
tags String?
startDate DateTime?
dueDate DateTime?
points Int?
imageSrc String?
projectId String @db.Uuid
authorUserId String
assignedUserId String?
author User? @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String? @db.Uuid
editAuthors String[]
createdAt DateTime @default(dbgenerated("NOW()"))
updatedAt DateTime @updatedAt
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
taskAssignments TaskAssignment[]
attachments Attachment[]
comments Comment[]
}
model TaskAssignment {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
userId String @db.Uuid
taskId String @db.Uuid
user User @relation(fields: [userId], references: [id])
task Task @relation(fields: [taskId], references: [id])
}
model Attachment {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
fileURL String
fileName String?
taskId String @db.Uuid
uploadedById String @db.Uuid
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
uploadedBy User @relation(fields: [uploadedById], references: [id])
}
model Comment {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
text String
taskId String @db.Uuid
userId String? @unique @db.Uuid
task Task @relation(fields: [taskId], references: [id])
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
}
================================================
FILE: production-level-application/provider/reduxProvider.tsx
================================================
"use client";
import StoreProvider from "@/components/wrapper/redux";
import React from "react";
type Props = {
children: React.ReactNode;
};
function ReduxProvider({ children }: Props) {
return {children} ;
}
export default ReduxProvider;
================================================
FILE: production-level-application/state/api.ts
================================================
import { ProjectTypes, TasksTypes, User } from "@/types/type";
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
export const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: process.env.NEXT_PUBLIC_API_URL,
}),
reducerPath: "api",
tagTypes: ["Projects", "Tasks", "Users", "Teams"],
endpoints: (build) => ({
createUser: build.mutation>({
query: (user) => ({
url: "user",
method: "POST",
body: user,
}),
invalidatesTags: ["Users"],
}),
getUser: build.query({
query: ({ userId }) => `user/${userId}`,
providesTags: (result, error, { userId }) =>
result ? [{ type: "Users", id: userId }] : [],
}),
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}`,
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 }],
}),
createTasks: build.mutation>({
query: (task) => ({
url: "tasks",
method: "POST",
body: task,
}),
invalidatesTags: ["Tasks"],
}),
updateTasks: build.mutation(
{
query: ({ taskId, status }) => ({
url: `move/${taskId}`,
method: "PATCH",
body: { status },
}),
invalidatesTags: (result, error, { taskId }) => [
{ type: "Tasks", id: taskId },
],
}
),
deleteTasks: build.mutation({
query: ({ taskId }) => ({
url: `move/${taskId}`,
method: "DELETE",
}),
invalidatesTags: (result, error, { taskId }) => [
{ type: "Tasks", id: taskId },
],
}),
updateProjectAuthors: build.mutation<
ProjectTypes,
{ projectId: string; userId: string }
>({
query: ({ projectId, userId }) => ({
url: `projects/${projectId}`,
method: "PATCH",
body: { userId },
}),
invalidatesTags: ["Projects"],
}),
deleteProject: build.mutation({
query: ({ projectId }) => ({
url: `projects/${projectId}`,
method: "DELETE",
}),
invalidatesTags: ["Projects"],
}),
removeUserFromTask: build.mutation<
ProjectTypes,
{ projectId: string; userId: string }
>({
query: ({ projectId, userId }) => ({
url: `tasks/${projectId}`,
method: "PATCH",
body: { userId },
}),
invalidatesTags: ["Projects"],
}),
}),
});
export const {
useCreateUserMutation,
useGetProjectsQuery,
useCreateProjectMutation,
useGetTasksQuery,
useGetTasksByUserQuery,
useCreateTasksMutation,
useUpdateTasksMutation,
useDeleteTasksMutation,
useDeleteProjectMutation,
useGetUserQuery,
useUpdateProjectAuthorsMutation,
useRemoveUserFromTaskMutation,
useLazyGetTasksQuery,
} = api;
================================================
FILE: production-level-application/state/index.ts
================================================
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
export interface initialStateTypes {
isSidebarCollapsed: boolean;
isDarkMode: boolean;
}
const initialState: initialStateTypes = {
isSidebarCollapsed: false,
isDarkMode: false,
};
export const globalSlice = createSlice({
name: "global",
initialState,
reducers: {
setIsSidebarCollapsed: (state, action: PayloadAction) => {
state.isSidebarCollapsed = action.payload;
},
setIsDarkMode: (state, action: PayloadAction) => {
state.isDarkMode = action.payload;
},
},
});
export const { setIsSidebarCollapsed, setIsDarkMode } = globalSlice.actions;
export default globalSlice.reducer;
================================================
FILE: production-level-application/tailwind.config.ts
================================================
import type { Config } from "tailwindcss";
export default {
darkMode: "class",
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors: {
white: "#ffffff",
gray: {
100: "#f3f4f6",
200: "#e5e7eb",
300: "#d1d5db",
500: "#6b7280",
700: "#374151",
800: "#1f2937",
},
blue: {
200: "#93c5fd",
400: "#60a5fa",
500: "#3b82f6",
},
"dark-bg": "#101214",
"dark-secondary": "#1d1f21",
"dark-tertiary": "#3b3d40",
"blue-primary": "#0275ff",
"stroke-dark": "#2d3135",
background: "var(--background)",
foreground: "var(--foreground)",
},
},
},
plugins: [],
} satisfies Config;
================================================
FILE: production-level-application/tsconfig.json
================================================
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
================================================
FILE: production-level-application/types/type.ts
================================================
export interface ProjectTypes {
id: number;
name: string;
description?: string;
startDate?: string;
endDate?: string;
user?: String;
userId?: string;
}
export enum Status {
ToDo = "To Do",
WorkInProgress = "Work In Progress",
UnderReview = "Under Review",
Completed = "Completed",
}
export enum Priority {
Urgent = "Urgent",
High = "High",
Medium = "Medium",
Low = "Low",
Backlog = "Backlog",
}
export interface User {
id?: string;
userId?: number;
firstName: string;
secondName: string;
email: string;
profilePictureUrl?: string;
clerkId?: string;
}
export interface Attachment {
id: number;
fileURL: string;
fileName: string;
taskId: number;
uploadedById: number;
}
export interface TasksTypes {
id: number;
title: string;
description?: string;
status?: Status;
priority?: Priority;
tags?: string;
startDate?: string;
dueDate?: string;
points?: number;
projectId: string;
authorUserId?: string;
assignedUserId?: number;
imageSrc?: string;
userId?: string;
createdAt?: string;
updatedAt?: string;
author?: User;
assignee?: User;
comments?: Comment[];
attachments?: Attachment[];
}
export interface SearchResults {
tasks?: TasksTypes[];
projects?: ProjectTypes[];
users?: User[];
}
export interface Team {
teamId: number;
teamName: string;
productOwnerUserId?: number;
projectManagerUserId?: number;
}
export type TaskTypeItems = "task" | "milestone" | "project";
export interface ImageCardProps {
src: string;
aspectRatio: string;
marginTop?: string;
}
================================================
FILE: server/.gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
dist
.env
================================================
FILE: server/ecosystem.config.js
================================================
module.exports = {
apps: [
{
name: "project-management",
script: "npm",
args: "run dev",
env: {
NODE_ENV: "development",
},
},
],
};
================================================
FILE: server/package.json
================================================
{
"name": "server",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rimraf dist && npx tsc",
"start": "npm run build && node dist/index.js",
"dev": "npm run build && concurrently \"npx tsc -w\" \"nodemon --exec ts-node src/index.ts\"",
"seed": "ts-node prisma/seed.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",
"@types/morgan": "^1.9.9",
"@types/node": "^22.10.3",
"concurrently": "^9.1.2",
"nodemon": "^3.1.9",
"prisma": "^6.2.1",
"rimraf": "^6.0.1",
"ts-node": "^10.9.2",
"typescript": "^5.7.2"
},
"dependencies": {
"@prisma/client": "^6.2.1",
"body-parser": "^1.20.3",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"helmet": "^8.0.0",
"morgan": "^1.10.0"
}
}
================================================
FILE: server/prisma/migrations/20250103002536_init/migration.sql
================================================
-- CreateTable
CREATE TABLE "User" (
"userId" SERIAL NOT NULL,
"cognitoId" TEXT NOT NULL,
"username" TEXT NOT NULL,
"profilePictureUrl" TEXT,
"teamId" INTEGER,
CONSTRAINT "User_pkey" PRIMARY KEY ("userId")
);
-- CreateTable
CREATE TABLE "Team" (
"id" SERIAL NOT NULL,
"teamName" TEXT NOT NULL,
"productOwnerUserId" INTEGER,
"projectManagerUserId" INTEGER,
CONSTRAINT "Team_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Project" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"startDate" TIMESTAMP(3),
"endDate" TIMESTAMP(3),
CONSTRAINT "Project_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProjectTeam" (
"id" SERIAL NOT NULL,
"teamId" INTEGER NOT NULL,
"projectId" INTEGER NOT NULL,
CONSTRAINT "ProjectTeam_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Task" (
"id" SERIAL NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"status" TEXT,
"priority" TEXT,
"tags" TEXT,
"startDate" TIMESTAMP(3),
"dueDate" TIMESTAMP(3),
"points" INTEGER,
"projectId" INTEGER NOT NULL,
"authorUserId" INTEGER NOT NULL,
"assignedUserId" INTEGER,
CONSTRAINT "Task_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TaskAssignment" (
"id" SERIAL NOT NULL,
"userId" INTEGER NOT NULL,
"taskId" INTEGER NOT NULL,
CONSTRAINT "TaskAssignment_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Attachment" (
"id" SERIAL NOT NULL,
"fileURL" TEXT NOT NULL,
"fileName" TEXT,
"taskId" INTEGER NOT NULL,
"uploadedById" INTEGER NOT NULL,
CONSTRAINT "Attachment_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Comment" (
"id" SERIAL NOT NULL,
"text" TEXT NOT NULL,
"taskId" INTEGER NOT NULL,
"userId" INTEGER NOT NULL,
CONSTRAINT "Comment_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_cognitoId_key" ON "User"("cognitoId");
-- CreateIndex
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
-- AddForeignKey
ALTER TABLE "User" ADD CONSTRAINT "User_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProjectTeam" ADD CONSTRAINT "ProjectTeam_teamId_fkey" FOREIGN KEY ("teamId") REFERENCES "Team"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ProjectTeam" ADD CONSTRAINT "ProjectTeam_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Task" ADD CONSTRAINT "Task_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Task" ADD CONSTRAINT "Task_authorUserId_fkey" FOREIGN KEY ("authorUserId") REFERENCES "User"("userId") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Task" ADD CONSTRAINT "Task_assignedUserId_fkey" FOREIGN KEY ("assignedUserId") REFERENCES "User"("userId") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TaskAssignment" ADD CONSTRAINT "TaskAssignment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("userId") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TaskAssignment" ADD CONSTRAINT "TaskAssignment_taskId_fkey" FOREIGN KEY ("taskId") REFERENCES "Task"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Attachment" ADD CONSTRAINT "Attachment_taskId_fkey" FOREIGN KEY ("taskId") REFERENCES "Task"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Attachment" ADD CONSTRAINT "Attachment_uploadedById_fkey" FOREIGN KEY ("uploadedById") REFERENCES "User"("userId") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Comment" ADD CONSTRAINT "Comment_taskId_fkey" FOREIGN KEY ("taskId") REFERENCES "Task"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Comment" ADD CONSTRAINT "Comment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("userId") ON DELETE RESTRICT ON UPDATE CASCADE;
================================================
FILE: server/prisma/migrations/migration_lock.toml
================================================
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"
================================================
FILE: server/prisma/schema.prisma
================================================
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
userId Int @id @default(autoincrement())
cognitoId String @unique
username String @unique
profilePictureUrl String?
teamId Int?
authoredTasks Task[] @relation("TaskAuthor")
assignedTasks Task[] @relation("TaskAssignee")
taskAssignments TaskAssignment[]
attachments Attachment[]
comments Comment[]
team Team? @relation(fields: [teamId], references: [id])
}
model Team {
id Int @id @default(autoincrement())
teamName String
productOwnerUserId Int?
projectManagerUserId Int?
projectTeams ProjectTeam[]
user User[]
}
model Project {
id Int @id @default(autoincrement())
name String
description String?
startDate DateTime?
endDate DateTime?
tasks Task[]
projectTeams ProjectTeam[]
}
model ProjectTeam {
id Int @id @default(autoincrement())
teamId Int
projectId Int
team Team @relation(fields: [teamId], references: [id])
project Project @relation(fields: [projectId], references: [id])
}
model Task {
id Int @id @default(autoincrement())
title String
description String?
status String?
priority String?
tags String?
startDate DateTime?
dueDate DateTime?
points Int?
projectId Int
authorUserId Int
assignedUserId Int?
project Project @relation(fields: [projectId], references: [id])
author User @relation("TaskAuthor", fields: [authorUserId], references: [userId])
assignee User? @relation("TaskAssignee", fields: [assignedUserId], references: [userId])
taskAssignments TaskAssignment[]
attachments Attachment[]
comments Comment[]
}
model TaskAssignment {
id Int @id @default(autoincrement())
userId Int
taskId Int
user User @relation(fields: [userId], references: [userId])
task Task @relation(fields: [taskId], references: [id])
}
model Attachment {
id Int @id @default(autoincrement())
fileURL String
fileName String?
taskId Int
uploadedById Int
task Task @relation(fields: [taskId], references: [id])
uploadedBy User @relation(fields: [uploadedById], references: [userId])
}
model Comment {
id Int @id @default(autoincrement())
text String
taskId Int
userId Int
task Task @relation(fields: [taskId], references: [id])
user User @relation(fields: [userId], references: [userId])
}
================================================
FILE: server/prisma/seed.ts
================================================
import { PrismaClient } from "@prisma/client";
import fs from "fs";
import path from "path";
const prisma = new PrismaClient();
async function deleteAllData(orderedFileNames: string[]) {
const modelNames = orderedFileNames.map((fileName) => {
const modelName = path.basename(fileName, path.extname(fileName));
return modelName.charAt(0).toUpperCase() + modelName.slice(1);
});
for (const modelName of modelNames) {
const model: any = prisma[modelName as keyof typeof prisma];
try {
await model.deleteMany({});
console.log(`Cleared data from ${modelName}`);
} catch (error) {
console.error(`Error clearing data from ${modelName}:`, error);
}
}
}
async function main() {
const dataDirectory = path.join(__dirname, "seedData");
const orderedFileNames = [
"team.json",
"project.json",
"projectTeam.json",
"user.json",
"task.json",
"attachment.json",
"comment.json",
"taskAssignment.json",
];
await deleteAllData(orderedFileNames);
for (const fileName of orderedFileNames) {
const filePath = path.join(dataDirectory, fileName);
const jsonData = JSON.parse(fs.readFileSync(filePath, "utf-8"));
const modelName = path.basename(fileName, path.extname(fileName));
const model: any = prisma[modelName as keyof typeof prisma];
try {
for (const data of jsonData) {
await model.create({ data });
}
console.log(`Seeded ${modelName} with data from ${fileName}`);
} catch (error) {
console.error(`Error seeding data for ${modelName}:`, error);
}
}
}
main()
.catch((e) => console.error(e))
.finally(async () => await prisma.$disconnect());
// 02.02
================================================
FILE: server/prisma/seedData/attachment.json
================================================
[
{
"id": 1,
"fileURL": "i1.jpg",
"fileName": "DesignDoc.pdf",
"taskId": 1,
"uploadedById": 1
},
{
"id": 2,
"fileURL": "i2.jpg",
"fileName": "NavAlgorithm.pdf",
"taskId": 2,
"uploadedById": 3
},
{
"id": 3,
"fileURL": "i3.jpg",
"fileName": "EnergySolutions.pdf",
"taskId": 3,
"uploadedById": 5
},
{
"id": 4,
"fileURL": "i4.jpg",
"fileName": "SoftwareWorkflow.pdf",
"taskId": 4,
"uploadedById": 7
},
{
"id": 5,
"fileURL": "i5.jpg",
"fileName": "AIPredictions.pdf",
"taskId": 5,
"uploadedById": 9
},
{
"id": 6,
"fileURL": "i6.jpg",
"fileName": "BiotechTest.pdf",
"taskId": 6,
"uploadedById": 11
},
{
"id": 7,
"fileURL": "i7.jpg",
"fileName": "GolfAI.pdf",
"taskId": 7,
"uploadedById": 13
},
{
"id": 8,
"fileURL": "i8.jpg",
"fileName": "HotelDB.pdf",
"taskId": 8,
"uploadedById": 15
},
{
"id": 9,
"fileURL": "i9.jpg",
"fileName": "TelecomUpgrade.pdf",
"taskId": 9,
"uploadedById": 17
},
{
"id": 10,
"fileURL": "i10.jpg",
"fileName": "SecurityProtocol.pdf",
"taskId": 10,
"uploadedById": 19
}
]
================================================
FILE: server/prisma/seedData/comment.json
================================================
[
{
"id": 1,
"text": "We need to update this design to include new specifications.",
"taskId": 1,
"userId": 2
},
{
"id": 2,
"text": "Can we meet to discuss the navigation algorithm updates?",
"taskId": 2,
"userId": 4
},
{
"id": 3,
"text": "This energy solution looks promising, but needs more research.",
"taskId": 3,
"userId": 6
},
{
"id": 4,
"text": "Let's revise the software development workflow to include agile methodologies.",
"taskId": 4,
"userId": 8
},
{
"id": 5,
"text": "We should consider newer AI models for better accuracy.",
"taskId": 5,
"userId": 10
},
{
"id": 6,
"text": "Product testing needs to be more rigorous.",
"taskId": 6,
"userId": 12
},
{
"id": 7,
"text": "Optimization algorithms are not yet efficient.",
"taskId": 7,
"userId": 14
},
{
"id": 8,
"text": "Database overhaul could impact current operations negatively.",
"taskId": 8,
"userId": 16
},
{
"id": 9,
"text": "Infrastructure upgrades must be done during low traffic hours.",
"taskId": 9,
"userId": 18
},
{
"id": 10,
"text": "Security measures need to be enhanced to prevent data breaches.",
"taskId": 10,
"userId": 20
},
{
"id": 11,
"text": "Consider using more robust training datasets for AI.",
"taskId": 11,
"userId": 1
},
{
"id": 12,
"text": "Server security update meeting scheduled for next week.",
"taskId": 12,
"userId": 2
},
{
"id": 13,
"text": "UX redesign has been well received in initial user tests.",
"taskId": 13,
"userId": 3
},
{
"id": 14,
"text": "Data analytics implementation needs to account for real-time processing delays.",
"taskId": 14,
"userId": 4
},
{
"id": 15,
"text": "Encryption project needs to align with international security standards.",
"taskId": 15,
"userId": 5
},
{
"id": 16,
"text": "Review cloud storage optimization strategies in Q3 meeting.",
"taskId": 16,
"userId": 6
},
{
"id": 17,
"text": "Hardware compatibility tests to include newer device models.",
"taskId": 17,
"userId": 7
},
{
"id": 18,
"text": "Visualization tools to support both 2D and 3D data representations.",
"taskId": 18,
"userId": 8
},
{
"id": 19,
"text": "IoT device prototypes to undergo extensive field testing.",
"taskId": 19,
"userId": 9
},
{
"id": 20,
"text": "Legacy system upgrade to start with backend databases.",
"taskId": 20,
"userId": 10
},
{
"id": 21,
"text": "Network security framework should prioritize threat detection improvements.",
"taskId": 21,
"userId": 1
},
{
"id": 22,
"text": "Application deployment strategies to include Docker integration.",
"taskId": 22,
"userId": 2
},
{
"id": 23,
"text": "Market analysis should cover competitive product landscapes.",
"taskId": 23,
"userId": 3
},
{
"id": 24,
"text": "Feedback mechanisms to utilize adaptive questioning techniques.",
"taskId": 24,
"userId": 4
},
{
"id": 25,
"text": "API integration must ensure data privacy compliance.",
"taskId": 25,
"userId": 5
}
]
================================================
FILE: server/prisma/seedData/project.json
================================================
[
{
"id": 1,
"name": "Apollo",
"description": "A space exploration project.",
"startDate": "2023-01-01T00:00:00Z",
"endDate": "2023-12-31T00:00:00Z"
},
{
"id": 2,
"name": "Beacon",
"description": "Developing advanced navigation systems.",
"startDate": "2023-02-01T00:00:00Z",
"endDate": "2023-10-15T00:00:00Z"
},
{
"id": 3,
"name": "Catalyst",
"description": "A project to boost renewable energy use.",
"startDate": "2023-03-05T00:00:00Z",
"endDate": "2024-03-05T00:00:00Z"
},
{
"id": 4,
"name": "Delta",
"description": "Delta project for new software development techniques.",
"startDate": "2023-01-20T00:00:00Z",
"endDate": "2023-09-20T00:00:00Z"
},
{
"id": 5,
"name": "Echo",
"description": "Echo project focused on AI advancements.",
"startDate": "2023-04-15T00:00:00Z",
"endDate": "2023-11-30T00:00:00Z"
},
{
"id": 6,
"name": "Foxtrot",
"description": "Exploring cutting-edge biotechnology.",
"startDate": "2023-02-25T00:00:00Z",
"endDate": "2023-08-25T00:00:00Z"
},
{
"id": 7,
"name": "Golf",
"description": "Development of new golf equipment using AI.",
"startDate": "2023-05-10T00:00:00Z",
"endDate": "2023-12-10T00:00:00Z"
},
{
"id": 8,
"name": "Hotel",
"description": "Hotel management system overhaul.",
"startDate": "2023-03-01T00:00:00Z",
"endDate": "2024-01-01T00:00:00Z"
},
{
"id": 9,
"name": "India",
"description": "Telecommunication infrastructure upgrade.",
"startDate": "2023-06-01T00:00:00Z",
"endDate": "2023-12-01T00:00:00Z"
},
{
"id": 10,
"name": "Juliet",
"description": "Initiative to enhance cyber-security measures.",
"startDate": "2023-07-01T00:00:00Z",
"endDate": "2024-02-01T00:00:00Z"
}
]
================================================
FILE: server/prisma/seedData/projectTeam.json
================================================
[
{ "id": 1, "teamId": 1, "projectId": 1 },
{ "id": 2, "teamId": 2, "projectId": 1 },
{ "id": 3, "teamId": 3, "projectId": 1 },
{ "id": 4, "teamId": 4, "projectId": 1 },
{ "id": 5, "teamId": 5, "projectId": 1 },
{ "id": 6, "teamId": 1, "projectId": 2 },
{ "id": 7, "teamId": 2, "projectId": 2 },
{ "id": 8, "teamId": 3, "projectId": 2 },
{ "id": 9, "teamId": 4, "projectId": 2 },
{ "id": 10, "teamId": 5, "projectId": 2 },
{ "id": 11, "teamId": 1, "projectId": 3 },
{ "id": 12, "teamId": 2, "projectId": 3 },
{ "id": 13, "teamId": 3, "projectId": 3 },
{ "id": 14, "teamId": 4, "projectId": 3 },
{ "id": 15, "teamId": 5, "projectId": 3 },
{ "id": 16, "teamId": 1, "projectId": 4 },
{ "id": 17, "teamId": 2, "projectId": 4 },
{ "id": 18, "teamId": 3, "projectId": 4 },
{ "id": 19, "teamId": 4, "projectId": 4 },
{ "id": 20, "teamId": 5, "projectId": 4 }
]
================================================
FILE: server/prisma/seedData/task.json
================================================
[
{
"id": 1,
"title": "Task 1",
"description": "Design the main module.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "Design",
"startDate": "2023-01-10T00:00:00Z",
"dueDate": "2023-04-10T00:00:00Z",
"projectId": 1,
"authorUserId": 1,
"assignedUserId": 2
},
{
"id": 2,
"title": "Task 2",
"description": "Implement the navigation algorithm.",
"status": "To Do",
"priority": "High",
"tags": "Coding",
"startDate": "2023-01-15T00:00:00Z",
"dueDate": "2023-05-15T00:00:00Z",
"projectId": 2,
"authorUserId": 3,
"assignedUserId": 4
},
{
"id": 3,
"title": "Task 3",
"description": "Develop renewable energy solutions.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "Development",
"startDate": "2023-03-20T00:00:00Z",
"dueDate": "2023-09-20T00:00:00Z",
"projectId": 3,
"authorUserId": 5,
"assignedUserId": 6
},
{
"id": 4,
"title": "Task 4",
"description": "Outline new software development workflows.",
"status": "To Do",
"priority": "High",
"tags": "Planning",
"startDate": "2023-01-25T00:00:00Z",
"dueDate": "2023-06-25T00:00:00Z",
"projectId": 4,
"authorUserId": 7,
"assignedUserId": 8
},
{
"id": 5,
"title": "Task 5",
"description": "Research AI models for prediction.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "Research",
"startDate": "2023-04-20T00:00:00Z",
"dueDate": "2023-10-20T00:00:00Z",
"projectId": 5,
"authorUserId": 9,
"assignedUserId": 10
},
{
"id": 6,
"title": "Task 6",
"description": "Biotech product testing.",
"status": "To Do",
"priority": "Backlog",
"tags": "Testing",
"startDate": "2023-03-01T00:00:00Z",
"dueDate": "2023-08-01T00:00:00Z",
"projectId": 6,
"authorUserId": 11,
"assignedUserId": 12
},
{
"id": 7,
"title": "Task 7",
"description": "AI optimization for golf equipment.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "Optimization",
"startDate": "2023-05-15T00:00:00Z",
"dueDate": "2023-11-15T00:00:00Z",
"projectId": 7,
"authorUserId": 13,
"assignedUserId": 14
},
{
"id": 8,
"title": "Task 8",
"description": "Overhaul of the database for hotel management.",
"status": "To Do",
"priority": "High",
"tags": "Database",
"startDate": "2023-04-01T00:00:00Z",
"dueDate": "2023-10-01T00:00:00Z",
"projectId": 8,
"authorUserId": 15,
"assignedUserId": 16
},
{
"id": 9,
"title": "Task 9",
"description": "Upgrade telecom infrastructure.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "Infrastructure",
"startDate": "2023-06-10T00:00:00Z",
"dueDate": "2023-12-10T00:00:00Z",
"projectId": 9,
"authorUserId": 17,
"assignedUserId": 18
},
{
"id": 10,
"title": "Task 10",
"description": "Enhance security protocols.",
"status": "To Do",
"priority": "Urgent",
"tags": "Security",
"startDate": "2023-07-05T00:00:00Z",
"dueDate": "2024-01-05T00:00:00Z",
"projectId": 10,
"authorUserId": 19,
"assignedUserId": 20
},
{
"id": 11,
"title": "Task 11",
"description": "Finalize AI training parameters.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "AI, Training",
"startDate": "2023-01-20T00:00:00Z",
"dueDate": "2023-05-20T00:00:00Z",
"projectId": 5,
"authorUserId": 1,
"assignedUserId": 3
},
{
"id": 12,
"title": "Task 12",
"description": "Update server security protocols.",
"status": "To Do",
"priority": "High",
"tags": "Security",
"startDate": "2023-02-10T00:00:00Z",
"dueDate": "2023-06-10T00:00:00Z",
"projectId": 1,
"authorUserId": 2,
"assignedUserId": 4
},
{
"id": 13,
"title": "Task 13",
"description": "Redesign user interface for better UX.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "Design, UX",
"startDate": "2023-03-15T00:00:00Z",
"dueDate": "2023-07-15T00:00:00Z",
"projectId": 2,
"authorUserId": 5,
"assignedUserId": 6
},
{
"id": 14,
"title": "Task 14",
"description": "Implement real-time data analytics.",
"status": "To Do",
"priority": "High",
"tags": "Analytics",
"startDate": "2023-04-05T00:00:00Z",
"dueDate": "2023-08-05T00:00:00Z",
"projectId": 3,
"authorUserId": 7,
"assignedUserId": 8
},
{
"id": 15,
"title": "Task 15",
"description": "Develop end-to-end encryption solution.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "Encryption",
"startDate": "2023-05-01T00:00:00Z",
"dueDate": "2023-09-01T00:00:00Z",
"projectId": 4,
"authorUserId": 9,
"assignedUserId": 10
},
{
"id": 16,
"title": "Task 16",
"description": "Optimize cloud storage usage.",
"status": "To Do",
"priority": "Backlog",
"tags": "Cloud, Storage",
"startDate": "2023-06-15T00:00:00Z",
"dueDate": "2023-10-15T00:00:00Z",
"projectId": 5,
"authorUserId": 11,
"assignedUserId": 12
},
{
"id": 17,
"title": "Task 17",
"description": "Test software for hardware compatibility.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "Testing, Hardware",
"startDate": "2023-07-10T00:00:00Z",
"dueDate": "2023-11-10T00:00:00Z",
"projectId": 6,
"authorUserId": 13,
"assignedUserId": 14
},
{
"id": 18,
"title": "Task 18",
"description": "Create new data visualization tools.",
"status": "To Do",
"priority": "High",
"tags": "Visualization",
"startDate": "2023-08-05T00:00:00Z",
"dueDate": "2023-12-05T00:00:00Z",
"projectId": 7,
"authorUserId": 15,
"assignedUserId": 16
},
{
"id": 19,
"title": "Task 19",
"description": "Build prototype for new IoT devices.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "IoT",
"startDate": "2023-09-01T00:00:00Z",
"dueDate": "2024-01-01T00:00:00Z",
"projectId": 8,
"authorUserId": 17,
"assignedUserId": 18
},
{
"id": 20,
"title": "Task 20",
"description": "Update legacy systems to new tech standards.",
"status": "To Do",
"priority": "Urgent",
"tags": "Legacy, Upgrade",
"startDate": "2023-10-10T00:00:00Z",
"dueDate": "2024-02-10T00:00:00Z",
"projectId": 9,
"authorUserId": 19,
"assignedUserId": 20
},
{
"id": 21,
"title": "Task 21",
"description": "Establish new network security framework.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "Security",
"startDate": "2023-01-30T00:00:00Z",
"dueDate": "2023-05-30T00:00:00Z",
"projectId": 10,
"authorUserId": 1,
"assignedUserId": 3
},
{
"id": 22,
"title": "Task 22",
"description": "Revise application deployment strategies.",
"status": "To Do",
"priority": "High",
"tags": "Deployment",
"startDate": "2023-02-20T00:00:00Z",
"dueDate": "2023-06-20T00:00:00Z",
"projectId": 1,
"authorUserId": 2,
"assignedUserId": 4
},
{
"id": 23,
"title": "Task 23",
"description": "Conduct market analysis for product fit.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "Market Analysis",
"startDate": "2023-03-25T00:00:00Z",
"dueDate": "2023-07-25T00:00:00Z",
"projectId": 2,
"authorUserId": 5,
"assignedUserId": 6
},
{
"id": 24,
"title": "Task 24",
"description": "Optimize user feedback collection mechanism.",
"status": "To Do",
"priority": "High",
"tags": "Feedback",
"startDate": "2023-04-15T00:00:00Z",
"dueDate": "2023-08-15T00:00:00Z",
"projectId": 3,
"authorUserId": 7,
"assignedUserId": 8
},
{
"id": 25,
"title": "Task 25",
"description": "Integrate new API for third-party services.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "API Integration",
"startDate": "2023-05-05T00:00:00Z",
"dueDate": "2023-09-05T00:00:00Z",
"projectId": 4,
"authorUserId": 9,
"assignedUserId": 10
},
{
"id": 26,
"title": "Task 26",
"description": "Update internal tooling for development teams.",
"status": "To Do",
"priority": "Backlog",
"tags": "Tooling",
"startDate": "2023-06-25T00:00:00Z",
"dueDate": "2023-10-25T00:00:00Z",
"projectId": 5,
"authorUserId": 11,
"assignedUserId": 12
},
{
"id": 27,
"title": "Task 27",
"description": "Prepare cloud migration strategy document.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "Cloud Migration",
"startDate": "2023-07-20T00:00:00Z",
"dueDate": "2023-11-20T00:00:00Z",
"projectId": 6,
"authorUserId": 13,
"assignedUserId": 14
},
{
"id": 28,
"title": "Task 28",
"description": "Design scalable database architecture.",
"status": "To Do",
"priority": "Medium",
"tags": "Database Design",
"startDate": "2023-08-15T00:00:00Z",
"dueDate": "2023-12-15T00:00:00Z",
"projectId": 7,
"authorUserId": 15,
"assignedUserId": 16
},
{
"id": 29,
"title": "Task 29",
"description": "Prototype new mobile technology.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "Mobile Tech",
"startDate": "2023-09-10T00:00:00Z",
"dueDate": "2024-01-10T00:00:00Z",
"projectId": 8,
"authorUserId": 17,
"assignedUserId": 18
},
{
"id": 30,
"title": "Task 30",
"description": "Enhance data encryption levels.",
"status": "To Do",
"priority": "High",
"tags": "Encryption",
"startDate": "2023-10-15T00:00:00Z",
"dueDate": "2024-02-15T00:00:00Z",
"projectId": 9,
"authorUserId": 19,
"assignedUserId": 20
},
{
"id": 31,
"title": "Task 31",
"description": "Refactor backend code for better maintainability.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "Refactoring, Backend",
"startDate": "2023-11-01T00:00:00Z",
"dueDate": "2024-03-01T00:00:00Z",
"projectId": 10,
"authorUserId": 20,
"assignedUserId": 1
},
{
"id": 32,
"title": "Task 32",
"description": "Expand the network infrastructure to support increased traffic.",
"status": "To Do",
"priority": "Medium",
"tags": "Networking, Infrastructure",
"startDate": "2023-11-05T00:00:00Z",
"dueDate": "2024-01-05T00:00:00Z",
"projectId": 1,
"authorUserId": 2,
"assignedUserId": 3
},
{
"id": 33,
"title": "Task 33",
"description": "Create a new client dashboard interface.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "UI, Dashboard",
"startDate": "2023-11-10T00:00:00Z",
"dueDate": "2024-02-10T00:00:00Z",
"projectId": 2,
"authorUserId": 4,
"assignedUserId": 5
},
{
"id": 34,
"title": "Task 34",
"description": "Develop an automated testing framework for new software releases.",
"status": "To Do",
"priority": "Medium",
"tags": "Testing, Automation",
"startDate": "2023-11-15T00:00:00Z",
"dueDate": "2024-03-15T00:00:00Z",
"projectId": 3,
"authorUserId": 6,
"assignedUserId": 7
},
{
"id": 35,
"title": "Task 35",
"description": "Optimize database queries to improve application performance.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "Database, Optimization",
"startDate": "2023-11-20T00:00:00Z",
"dueDate": "2024-01-20T00:00:00Z",
"projectId": 4,
"authorUserId": 8,
"assignedUserId": 9
},
{
"id": 36,
"title": "Task 36",
"description": "Implement end-user training for new system features.",
"status": "To Do",
"priority": "Backlog",
"tags": "Training, User Experience",
"startDate": "2023-11-25T00:00:00Z",
"dueDate": "2024-01-25T00:00:00Z",
"projectId": 5,
"authorUserId": 10,
"assignedUserId": 11
},
{
"id": 37,
"title": "Task 37",
"description": "Conduct a comprehensive security audit of the existing infrastructure.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "Security, Audit",
"startDate": "2023-12-01T00:00:00Z",
"dueDate": "2024-02-01T00:00:00Z",
"projectId": 6,
"authorUserId": 12,
"assignedUserId": 13
},
{
"id": 38,
"title": "Task 38",
"description": "Revise mobile app to incorporate new payment integrations.",
"status": "To Do",
"priority": "Medium",
"tags": "Mobile, Payments",
"startDate": "2023-12-05T00:00:00Z",
"dueDate": "2024-02-05T00:00:00Z",
"projectId": 7,
"authorUserId": 14,
"assignedUserId": 15
},
{
"id": 39,
"title": "Task 39",
"description": "Update cloud configuration to optimize costs.",
"status": "Work In Progress",
"priority": "Urgent",
"tags": "Cloud, Cost Saving",
"startDate": "2023-12-10T00:00:00Z",
"dueDate": "2024-02-10T00:00:00Z",
"projectId": 8,
"authorUserId": 16,
"assignedUserId": 17
},
{
"id": 40,
"title": "Task 40",
"description": "Implement automated backup procedures for critical data.",
"status": "To Do",
"priority": "High",
"tags": "Backup, Automation",
"startDate": "2023-12-15T00:00:00Z",
"dueDate": "2024-02-15T00:00:00Z",
"projectId": 9,
"authorUserId": 18,
"assignedUserId": 19
}
]
================================================
FILE: server/prisma/seedData/taskAssignment.json
================================================
[
{ "id": 1, "userId": 1, "taskId": 1 },
{ "id": 2, "userId": 2, "taskId": 2 },
{ "id": 3, "userId": 3, "taskId": 3 },
{ "id": 4, "userId": 4, "taskId": 4 },
{ "id": 5, "userId": 5, "taskId": 5 },
{ "id": 6, "userId": 6, "taskId": 6 },
{ "id": 7, "userId": 7, "taskId": 7 },
{ "id": 8, "userId": 8, "taskId": 8 },
{ "id": 9, "userId": 9, "taskId": 9 },
{ "id": 10, "userId": 10, "taskId": 10 },
{ "id": 11, "userId": 11, "taskId": 11 },
{ "id": 12, "userId": 12, "taskId": 12 },
{ "id": 13, "userId": 13, "taskId": 13 },
{ "id": 14, "userId": 14, "taskId": 14 },
{ "id": 15, "userId": 15, "taskId": 15 },
{ "id": 16, "userId": 16, "taskId": 16 },
{ "id": 17, "userId": 17, "taskId": 17 },
{ "id": 18, "userId": 18, "taskId": 18 },
{ "id": 19, "userId": 19, "taskId": 19 },
{ "id": 20, "userId": 20, "taskId": 20 },
{ "id": 21, "userId": 1, "taskId": 21 },
{ "id": 22, "userId": 2, "taskId": 22 },
{ "id": 23, "userId": 3, "taskId": 23 },
{ "id": 24, "userId": 4, "taskId": 24 },
{ "id": 25, "userId": 5, "taskId": 25 },
{ "id": 26, "userId": 6, "taskId": 26 },
{ "id": 27, "userId": 7, "taskId": 27 },
{ "id": 28, "userId": 8, "taskId": 28 },
{ "id": 29, "userId": 9, "taskId": 29 },
{ "id": 30, "userId": 10, "taskId": 30 }
]
================================================
FILE: server/prisma/seedData/team.json
================================================
[
{
"teamName": "Quantum Innovations",
"productOwnerUserId": 11,
"projectManagerUserId": 2
},
{
"teamName": "Nebula Research",
"productOwnerUserId": 13,
"projectManagerUserId": 4
},
{
"teamName": "Orion Solutions",
"productOwnerUserId": 15,
"projectManagerUserId": 6
},
{
"teamName": "Krypton Developments",
"productOwnerUserId": 17,
"projectManagerUserId": 8
},
{
"teamName": "Zenith Technologies",
"productOwnerUserId": 19,
"projectManagerUserId": 10
}
]
================================================
FILE: server/prisma/seedData/user.json
================================================
[
{
"username": "AliceJones",
"teamId": 1,
"profilePictureUrl": "p1.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174001"
},
{
"username": "BobSmith",
"teamId": 2,
"profilePictureUrl": "p2.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174002"
},
{
"username": "CarolWhite",
"teamId": 3,
"profilePictureUrl": "p3.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174003"
},
{
"username": "DaveBrown",
"teamId": 4,
"profilePictureUrl": "p4.jpeg",
"cognitoId": "213b7530-1031-70e0-67e9-fe0805e18fb3"
},
{
"username": "EveClark",
"teamId": 5,
"profilePictureUrl": "p5.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174005"
},
{
"username": "FrankWright",
"teamId": 1,
"profilePictureUrl": "p6.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174006"
},
{
"username": "GraceHall",
"teamId": 2,
"profilePictureUrl": "p7.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174007"
},
{
"username": "HenryAllen",
"teamId": 3,
"profilePictureUrl": "p8.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174008"
},
{
"username": "IdaMartin",
"teamId": 4,
"profilePictureUrl": "p9.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174009"
},
{
"username": "JohnDoe",
"teamId": 5,
"profilePictureUrl": "p10.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174010"
},
{
"username": "LauraAdams",
"teamId": 1,
"profilePictureUrl": "p11.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174011"
},
{
"username": "NormanBates",
"teamId": 2,
"profilePictureUrl": "p12.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174012"
},
{
"username": "OliviaPace",
"teamId": 3,
"profilePictureUrl": "p13.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174013"
},
{
"username": "PeterQuill",
"teamId": 4,
"profilePictureUrl": "p1.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174014"
},
{
"username": "QuincyAdams",
"teamId": 5,
"profilePictureUrl": "p2.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174015"
},
{
"username": "RachelGreen",
"teamId": 1,
"profilePictureUrl": "p3.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174016"
},
{
"username": "SteveJobs",
"teamId": 2,
"profilePictureUrl": "p4.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174017"
},
{
"username": "TinaFey",
"teamId": 3,
"profilePictureUrl": "p5.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174018"
},
{
"username": "UrsulaMonroe",
"teamId": 4,
"profilePictureUrl": "p6.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174019"
},
{
"username": "VictorHugo",
"teamId": 5,
"profilePictureUrl": "p7.jpeg",
"cognitoId": "123e4567-e89b-12d3-a456-426614174020"
}
]
================================================
FILE: server/src/controllers/projectControllers.ts
================================================
import { PrismaClient } from "@prisma/client";
import { Request, Response } from "express";
const prisma = new PrismaClient();
export const getProjects = async (
req: Request,
res: Response
): Promise => {
try {
const project = await prisma.project.findMany();
res.json(project);
} catch (error: any) {
res
.status(500)
.json({ error: `Failed to fetch projects ${error.message}` });
}
};
export const createProject = async (
req: Request,
res: Response
): Promise => {
const { name, description, startDate, endDate } = req.body;
try {
const newProject = await prisma.project.create({
data: {
name,
description,
startDate,
endDate,
},
});
res.status(201).json(newProject);
} catch (error: any) {
res
.status(500)
.json({ error: `Failed to create project ${error.message}` });
}
};
================================================
FILE: server/src/controllers/searchControllers.ts
================================================
import { PrismaClient } from "@prisma/client";
import { Request, Response } from "express";
const prisma = new PrismaClient();
export const search = async (req: Request, res: Response): Promise => {
const { query } = req.query;
try {
const tasks = await prisma.task.findMany({
where: {
OR: [
{ title: { contains: query as string } },
{ description: { contains: query as string } },
],
},
});
const projects = await prisma.project.findMany({
where: {
OR: [
{ name: { contains: query as string } },
{ description: { contains: query as string } },
],
},
});
const users = await prisma.user.findMany({
where: {
OR: [{ username: { contains: query as string } }],
},
});
res.json({ tasks, projects, users });
} catch (error: any) {
res.status(500).json({ error: `Failed to search ${error.message}` });
}
};
================================================
FILE: server/src/controllers/taskControllers.ts
================================================
import { PrismaClient } from "@prisma/client";
import { Request, Response } from "express";
const prisma = new PrismaClient();
export const getTasks = async (req: Request, res: Response): Promise => {
const { projectId } = req.query;
try {
const tasks = await prisma.task.findMany({
where: {
projectId: Number(projectId),
},
include: {
author: true,
assignee: true,
comments: true,
attachments: true,
},
});
res.json(tasks);
} catch (error: any) {
res.status(500).json({ error: `Failed to fetch tasks ${error.message}` });
}
};
export const createTasks = async (
req: Request,
res: Response
): Promise => {
const {
title,
description,
status,
priority,
tags,
startDate,
dueDate,
points,
projectId,
authorUserId,
assignedUserId,
} = req.body;
try {
const newTask = await prisma.task.create({
data: {
title,
description,
status,
priority,
tags,
startDate,
dueDate,
points,
projectId,
authorUserId,
assignedUserId,
},
});
res.status(201).json(newTask);
} catch (error: any) {
res.status(500).json({ error: `Failed to create task ${error.message}` });
}
};
export const updateTasks = async (
req: Request,
res: Response
): Promise => {
const { taskId } = req.params;
const { status } = req.body;
try {
const updateTasks = await prisma.task.update({
where: {
id: Number(taskId),
},
data: {
status: status,
},
});
res.json(updateTasks);
} catch (error: any) {
res.status(500).json({ error: `Failed to update task ${error.message}` });
}
};
export const getUserTasks = async (
req: Request,
res: Response
): Promise => {
const { userId } = req.params;
/* console.log("🚀 ~ userId:", userId); */
try {
const tasks = await prisma.task.findMany({
where: {
OR: [
{ authorUserId: Number(userId) },
{ assignedUserId: Number(userId) },
],
},
include: {
author: true,
assignee: true,
},
});
res.json(tasks);
} catch (error: any) {
res
.status(500)
.json({ error: `Failed to get user tasks ${error.message}` });
}
};
================================================
FILE: server/src/controllers/teamControllers.ts
================================================
import { PrismaClient } from "@prisma/client";
import { Request, Response } from "express";
const prisma = new PrismaClient();
export const getTeams = async (req: Request, res: Response): Promise => {
try {
const teams = await prisma.team.findMany();
const teamWithUsernames = await Promise.all(
teams.map(async (team: any) => {
const productOwner = await prisma.user.findUnique({
where: { userId: team.productOwnerUserId! },
select: { username: true },
});
const projectManager = await prisma.user.findUnique({
where: { userId: team.projectManagerUserId! },
select: { username: true },
});
return {
...team,
productOwnerUsername: productOwner?.username,
projectManagerUsername: projectManager?.username,
};
})
);
res.json(teamWithUsernames);
} catch (error: any) {
res.status(500).json({ error: `Failed to fetch teams ${error.message}` });
}
};
================================================
FILE: server/src/controllers/usercontrollers.ts
================================================
import { PrismaClient } from "@prisma/client";
import { Request, Response } from "express";
const prisma = new PrismaClient();
export const getUsers = async (req: Request, res: Response): Promise => {
try {
const users = await prisma.user.findMany();
res.json(users);
} catch (error: any) {
res.status(500).json({ error: `Failed to fetch users ${error.message}` });
}
};
export const getUser = async (req: Request, res: Response): Promise => {
const { cognitoId } = req.params;
try {
const users = await prisma.user.findUnique({
where: {
cognitoId: cognitoId,
},
});
res.json(users);
} catch (error: any) {
res.status(500).json({ error: `Failed to fetch user ${error.message}` });
}
};
export const postUser = async (req: Request, res: Response) => {
try {
const {
username,
cognitoId,
profilePictureUrl = "i1.jpg",
teamId = 1,
} = req.body;
const newUsers = await prisma.user.create({
data: {
username,
cognitoId,
profilePictureUrl,
teamId,
},
});
res.json({ message: "User created successfully", newUsers });
} catch (error: any) {
res.status(500).json({ error: `Failed to fetch users ${error.message}` });
}
};
================================================
FILE: server/src/index.ts
================================================
import bodyParser from "body-parser";
import cors from "cors";
import dotenv from "dotenv";
import express, { Request, Response } from "express";
import helmet from "helmet";
import morgan from "morgan";
import projectRoutes from "./routes/projectRoutes";
import searchRoutes from "./routes/searchRoutes";
import taskRoutes from "./routes/taskRoute";
import userRoutes from "./routes/userRoutes";
import teamRoutes from "./routes/teamRoutes";
import { PrismaClient } from "@prisma/client/extension";
dotenv.config();
const app = express();
app.use(express.json());
app.use(helmet());
app.use(helmet.crossOriginResourcePolicy({ policy: "cross-origin" }));
app.use(morgan("common"));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors());
app.get("/", (req, res) => {
res.send("This is Home Route");
});
app.use("/projects", projectRoutes);
app.use("/tasks", taskRoutes);
app.use("/search", searchRoutes);
app.use("/users", userRoutes);
app.use("/teams", teamRoutes);
const port = Number(process.env.PORT) || 3000;
app.listen(port, "0.0.0.0", () => {
console.log(`Server running on port ${port}`);
});
================================================
FILE: server/src/routes/projectRoutes.ts
================================================
import { Router } from "express";
import { createProject, getProjects } from "../controllers/projectControllers";
const router = Router();
router.get("/", getProjects);
router.post("/", createProject);
export default router;
================================================
FILE: server/src/routes/searchRoutes.ts
================================================
import { Router } from "express";
import { search } from "../controllers/searchControllers";
const router = Router();
router.get("/", search);
export default router;
================================================
FILE: server/src/routes/taskRoute.ts
================================================
import { Router } from "express";
import {
createTasks,
getTasks,
getUserTasks,
updateTasks,
} from "../controllers/taskControllers";
const router = Router();
router.get("/", getTasks);
router.post("/", createTasks);
router.patch("/:taskId/status", updateTasks);
router.get("/user/:userId", getUserTasks);
export default router;
================================================
FILE: server/src/routes/teamRoutes.ts
================================================
import { Router } from "express";
import { getTeams } from "../controllers/teamControllers";
const router = Router();
router.get("/", getTeams);
export default router;
================================================
FILE: server/src/routes/userRoutes.ts
================================================
import { Router } from "express";
import { getUser, getUsers, postUser } from "../controllers/usercontrollers";
const router = Router();
router.get("/", getUsers);
router.get("/:cognitoId", getUser);
router.post("/", postUser);
export default router;
================================================
FILE: server/tsconfig.json
================================================
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "NodeNext" /* Specify what module code is generated. */,
// "rootDir": "./", /* Specify the root folder within your source files. */
"moduleResolution": "nodenext" /* Specify how TypeScript looks up a file from a given module specifier. */,
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "noUncheckedSideEffectImports": true, /* Check side effect imports. */
"resolveJsonModule": true /* Enable importing .json files. */,
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"include": ["src/**/*", "src/data/**/*.json", "prisma/**/*"]
}