Commit 5063f656 authored by Nirmal M.D.S's avatar Nirmal M.D.S

Merge branch 'IT20074340_Nirmal_M.D.S' into 'master'

Final Frontend commit

See merge request !9
parents 4124f34b 1351fe52
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Frontend Mentor - Kanban task management web app solution
- [this project YouTube video Tutorial](https://www.youtube.com/watch?v=3RWMktZNsJQ&t=1707s)
This is a solution to the [Kanban task management web app challenge on Frontend Mentor](https://www.frontendmentor.io/challenges/kanban-task-management-web-app-wgQLt-HlbB). Frontend Mentor challenges help you improve your coding skills by building realistic projects.
## Table of contents
- [Overview](#overview)
- [The challenge](#the-challenge)
- [Screenshot](#screenshot)
- [Links](#links)
- [Built with](#built-with)
- [Useful resources](#useful-resources)
- [Author](#author)
## Overview
### The challenge
Users should be able to:
- View the optimal layout for the app depending on their device's screen size
- See hover states for all interactive elements on the page
- Create, read, update, and delete boards and tasks
- Receive form validations when trying to create/edit boards and tasks
- Mark subtasks as complete and move tasks between columns
- Hide/show the board sidebar
Expected Behaviour:
- Boards
- Clicking different boards in the sidebar will change to the selected board.
- Clicking "Create New Board" in the sidebar opens the "Add New Board" modal.
- Clicking in the dropdown menu "Edit Board" opens up the "Edit Board" modal where details can be changed.
- Columns are added and removed for the Add/Edit Board modals.
- Deleting a board deletes all columns and tasks and requires confirmation.
- Columns
- A board needs at least one column before tasks can be added. If no columns exist, the "Add New Task" button in the header is disabled.
- Clicking "Add New Column" opens the "Edit Board" modal where columns are added.
- Tasks
- Adding a new task adds it to the bottom of the relevant column.
- Updating a task's status will move the task to the relevant column.
Bonus:
- The tasks can be dragged and dropped to a new column.
### Screenshot
![Screenshot](<./public/screenshots/Screen%20Shot%202023-03-29%20at%201.40.56%20PM%20(2).png>)
### Links
- Live Site URL: [link](https://kanban-task-management-react-tailwind.vercel.app/)
### Built with
- [TailwindCSS](https://tailwindcss.com/) - CSS Framework
- Drag and Drop API
- [React](https://reactjs.org/) - JS library
- [Redux](https://redux.js.org/) - State management tool
### Useful resources
- [Drag and Drop quick tutorial](https://www.youtube.com/watch?v=u65Y-vqYNAk)
## Author
- LinkedIn - [Hesam DearBoy](https://www.linkedin.com/in/hesam-azizpour-23259b265/)
This diff is collapsed.
{
"name": "kanban_taskmanager",
"version": "0.1.0",
"private": true,
"dependencies": {
"@babel/core": "^7.23.2",
"@babel/preset-env": "^7.23.2",
"@headlessui/react": "^1.7.13",
"@reduxjs/toolkit": "^1.9.3",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"assert": "^2.1.0",
"axios": "^1.6.0",
"babel-loader": "^9.1.3",
"browserify-zlib": "^0.2.0",
"https-browserify": "^1.0.0",
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
"react-scripts": "5.0.1",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"tailwind-scrollbar-hide": "^1.1.7",
"url": "^0.11.3",
"uuid": "^9.0.0",
"web-vitals": "^2.1.4",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@babel/preset-react": "^7.22.15",
"tailwindcss": "^3.2.7"
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import Header from "./components/Header";
import Home from "./components/Home";
import EmptyBoard from './components/EmptyBoard';
import boardsSlice from "./redux/boardsSlice";
function App() {
const [isBoardModalOpen, setIsBoardModalOpen] = useState(false);
const dispatch = useDispatch();
const boards = useSelector((state) => state.boards);
const activeBoard = boards.find((board) => board.isActive);
if (!activeBoard && boards.length > 0)
dispatch(boardsSlice.actions.setBoardActive({ index: 0 }));
return (
<div className=" overflow-hidden overflow-x-scroll">
<>
{boards.length > 0 ?
<>
<Header
setIsBoardModalOpen={setIsBoardModalOpen}
isBoardModalOpen={isBoardModalOpen}
/>
<Home
setIsBoardModalOpen={setIsBoardModalOpen}
isBoardModalOpen={isBoardModalOpen}
/>
</>
:
<>
<EmptyBoard type='add'/>
</>
}
</>
</div>
);
}
export default App;
<svg width="12" height="12" xmlns="http://www.w3.org/2000/svg"><path fill="#FFF" d="M7.368 12V7.344H12V4.632H7.368V0H4.656v4.632H0v2.712h4.656V12z"/></svg>
\ No newline at end of file
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M0 2.889A2.889 2.889 0 0 1 2.889 0H13.11A2.889 2.889 0 0 1 16 2.889V13.11A2.888 2.888 0 0 1 13.111 16H2.89A2.889 2.889 0 0 1 0 13.111V2.89Zm1.333 5.555v4.667c0 .859.697 1.556 1.556 1.556h6.889V8.444H1.333Zm8.445-1.333V1.333h-6.89A1.556 1.556 0 0 0 1.334 2.89V7.11h8.445Zm4.889-1.333H11.11v4.444h3.556V5.778Zm0 5.778H11.11v3.11h2a1.556 1.556 0 0 0 1.556-1.555v-1.555Zm0-7.112V2.89a1.555 1.555 0 0 0-1.556-1.556h-2v3.111h3.556Z" fill="#828FA3"/></svg>
\ No newline at end of file
<svg width="10" height="8" xmlns="http://www.w3.org/2000/svg"><path stroke="#FFF" stroke-width="2" fill="none" d="m1.276 3.066 2.756 2.756 5-5"/></svg>
\ No newline at end of file
<svg width="10" height="7" xmlns="http://www.w3.org/2000/svg"><path stroke="#635FC7" stroke-width="2" fill="none" d="m1 1 4 4 4-4"/></svg>
\ No newline at end of file
<svg width="10" height="7" xmlns="http://www.w3.org/2000/svg"><path stroke="#635FC7" stroke-width="2" fill="none" d="M9 6 5 2 1 6"/></svg>
\ No newline at end of file
<svg width="15" height="15" xmlns="http://www.w3.org/2000/svg"><g fill="#828FA3" fill-rule="evenodd"><path d="m12.728 0 2.122 2.122L2.122 14.85 0 12.728z"/><path d="M0 2.122 2.122 0 14.85 12.728l-2.122 2.122z"/></g></svg>
\ No newline at end of file
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M6.474.682c.434-.11.718.406.481.78A6.067 6.067 0 0 0 6.01 4.72c0 3.418 2.827 6.187 6.314 6.187.89.002 1.77-.182 2.584-.54.408-.18.894.165.724.57-1.16 2.775-3.944 4.73-7.194 4.73-4.292 0-7.771-3.41-7.771-7.615 0-3.541 2.466-6.518 5.807-7.37Zm8.433.07c.442-.294.969.232.674.674l-.525.787a1.943 1.943 0 0 0 0 2.157l.525.788c.295.441-.232.968-.674.673l-.787-.525a1.943 1.943 0 0 0-2.157 0l-.786.525c-.442.295-.97-.232-.675-.673l.525-.788a1.943 1.943 0 0 0 0-2.157l-.525-.787c-.295-.442.232-.968.674-.673l.787.525a1.943 1.943 0 0 0 2.157 0Z" fill="#828FA3"/></svg>
\ No newline at end of file
<svg width="18" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M8.522 11.223a4.252 4.252 0 0 1-3.654-5.22l3.654 5.22ZM9 12.25A8.685 8.685 0 0 1 1.5 8a8.612 8.612 0 0 1 2.76-2.864l-.86-1.23A10.112 10.112 0 0 0 .208 7.238a1.5 1.5 0 0 0 0 1.524A10.187 10.187 0 0 0 9 13.75c.414 0 .828-.025 1.239-.074l-1-1.43A8.88 8.88 0 0 1 9 12.25Zm8.792-3.488a10.14 10.14 0 0 1-4.486 4.046l1.504 2.148a.375.375 0 0 1-.092.523l-.648.453a.375.375 0 0 1-.523-.092L3.19 1.044A.375.375 0 0 1 3.282.52L3.93.068a.375.375 0 0 1 .523.092l1.735 2.479A10.308 10.308 0 0 1 9 2.25c3.746 0 7.031 2 8.792 4.988a1.5 1.5 0 0 1 0 1.524ZM16.5 8a8.674 8.674 0 0 0-6.755-4.219A1.75 1.75 0 1 0 12.75 5v-.001a4.25 4.25 0 0 1-1.154 5.366l.834 1.192A8.641 8.641 0 0 0 16.5 8Z" fill="#828FA3"/></svg>
\ No newline at end of file
<svg width="19" height="19" xmlns="http://www.w3.org/2000/svg"><path d="M9.167 15.833a.833.833 0 0 1 .833.834v.833a.833.833 0 0 1-1.667 0v-.833a.833.833 0 0 1 .834-.834ZM3.75 13.75a.833.833 0 0 1 .59 1.422l-1.25 1.25a.833.833 0 0 1-1.18-1.178l1.25-1.25a.833.833 0 0 1 .59-.244Zm10.833 0c.221 0 .433.088.59.244l1.25 1.25a.833.833 0 0 1-1.179 1.178l-1.25-1.25a.833.833 0 0 1 .59-1.422ZM9.167 5a4.167 4.167 0 1 1 0 8.334 4.167 4.167 0 0 1 0-8.334Zm-7.5 3.333a.833.833 0 0 1 0 1.667H.833a.833.833 0 1 1 0-1.667h.834Zm15.833 0a.833.833 0 0 1 0 1.667h-.833a.833.833 0 0 1 0-1.667h.833Zm-1.667-6.666a.833.833 0 0 1 .59 1.422l-1.25 1.25a.833.833 0 1 1-1.179-1.178l1.25-1.25a.833.833 0 0 1 .59-.244Zm-13.333 0c.221 0 .433.088.59.244l1.25 1.25a.833.833 0 0 1-1.18 1.178L1.91 3.09a.833.833 0 0 1 .59-1.422ZM9.167 0A.833.833 0 0 1 10 .833v.834a.833.833 0 1 1-1.667 0V.833A.833.833 0 0 1 9.167 0Z" fill="#828FA3"/></svg>
\ No newline at end of file
<svg width="16" height="11" xmlns="http://www.w3.org/2000/svg"><path d="M15.815 4.434A9.055 9.055 0 0 0 8 0 9.055 9.055 0 0 0 .185 4.434a1.333 1.333 0 0 0 0 1.354A9.055 9.055 0 0 0 8 10.222c3.33 0 6.25-1.777 7.815-4.434a1.333 1.333 0 0 0 0-1.354ZM8 8.89A3.776 3.776 0 0 1 4.222 5.11 3.776 3.776 0 0 1 8 1.333a3.776 3.776 0 0 1 3.778 3.778A3.776 3.776 0 0 1 8 8.89Zm2.889-3.778a2.889 2.889 0 1 1-5.438-1.36 1.19 1.19 0 1 0 1.19-1.189H6.64a2.889 2.889 0 0 1 4.25 2.549Z" fill="#FFF"/></svg>
\ No newline at end of file
<svg width="5" height="20" xmlns="http://www.w3.org/2000/svg"><g fill="#828FA3" fill-rule="evenodd"><circle cx="2.308" cy="2.308" r="2.308"/><circle cx="2.308" cy="10" r="2.308"/><circle cx="2.308" cy="17.692" r="2.308"/></g></svg>
\ No newline at end of file
<svg width="153" height="26" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M44.56 25v-5.344l1.92-2.112L50.928 25h5.44l-6.304-10.432 6.336-7.04h-5.92l-5.92 6.304V.776h-4.8V25h4.8Zm19.36.384c2.176 0 3.925-.672 5.248-2.016V25h4.48V13.48c0-1.259-.315-2.363-.944-3.312-.63-.95-1.51-1.69-2.64-2.224-1.13-.533-2.432-.8-3.904-.8-1.856 0-3.483.427-4.88 1.28-1.397.853-2.352 2.005-2.864 3.456l3.84 1.824a4.043 4.043 0 0 1 1.424-1.856c.65-.47 1.403-.704 2.256-.704.896 0 1.605.224 2.128.672.523.448.784 1.003.784 1.664v.48l-4.832.768c-2.09.341-3.648.992-4.672 1.952-1.024.96-1.536 2.176-1.536 3.648 0 1.579.55 2.816 1.648 3.712 1.099.896 2.587 1.344 4.464 1.344Zm.96-3.52c-.597 0-1.099-.15-1.504-.448-.405-.299-.608-.715-.608-1.248 0-.576.181-1.019.544-1.328.363-.31.885-.528 1.568-.656l3.968-.704v.544c0 1.067-.363 1.973-1.088 2.72-.725.747-1.685 1.12-2.88 1.12ZM81.968 25V14.792c0-1.003.299-1.808.896-2.416.597-.608 1.365-.912 2.304-.912.939 0 1.707.304 2.304.912.597.608.896 1.413.896 2.416V25h4.8V13.768c0-1.323-.277-2.48-.832-3.472a5.918 5.918 0 0 0-2.32-2.32c-.992-.555-2.15-.832-3.472-.832-1.11 0-2.09.208-2.944.624a4.27 4.27 0 0 0-1.952 1.904V7.528h-4.48V25h4.8Zm24.16.384c1.707 0 3.232-.405 4.576-1.216a8.828 8.828 0 0 0 3.184-3.296c.779-1.387 1.168-2.923 1.168-4.608 0-1.707-.395-3.248-1.184-4.624a8.988 8.988 0 0 0-3.2-3.28c-1.344-.81-2.848-1.216-4.512-1.216-2.112 0-3.787.619-5.024 1.856V.776h-4.8V25h4.48v-1.664c.619.661 1.392 1.168 2.32 1.52a8.366 8.366 0 0 0 2.992.528Zm-.576-4.32c-1.301 0-2.363-.443-3.184-1.328-.821-.885-1.232-2.043-1.232-3.472 0-1.408.41-2.56 1.232-3.456.821-.896 1.883-1.344 3.184-1.344 1.323 0 2.41.453 3.264 1.36.853.907 1.28 2.053 1.28 3.44 0 1.408-.427 2.56-1.28 3.456-.853.896-1.941 1.344-3.264 1.344Zm17.728 4.32c2.176 0 3.925-.672 5.248-2.016V25h4.48V13.48c0-1.259-.315-2.363-.944-3.312-.63-.95-1.51-1.69-2.64-2.224-1.13-.533-2.432-.8-3.904-.8-1.856 0-3.483.427-4.88 1.28-1.397.853-2.352 2.005-2.864 3.456l3.84 1.824a4.043 4.043 0 0 1 1.424-1.856c.65-.47 1.403-.704 2.256-.704.896 0 1.605.224 2.128.672.523.448.784 1.003.784 1.664v.48l-4.832.768c-2.09.341-3.648.992-4.672 1.952-1.024.96-1.536 2.176-1.536 3.648 0 1.579.55 2.816 1.648 3.712 1.099.896 2.587 1.344 4.464 1.344Zm.96-3.52c-.597 0-1.099-.15-1.504-.448-.405-.299-.608-.715-.608-1.248 0-.576.181-1.019.544-1.328.363-.31.885-.528 1.568-.656l3.968-.704v.544c0 1.067-.363 1.973-1.088 2.72-.725.747-1.685 1.12-2.88 1.12ZM141.328 25V14.792c0-1.003.299-1.808.896-2.416.597-.608 1.365-.912 2.304-.912.939 0 1.707.304 2.304.912.597.608.896 1.413.896 2.416V25h4.8V13.768c0-1.323-.277-2.48-.832-3.472a5.918 5.918 0 0 0-2.32-2.32c-.992-.555-2.15-.832-3.472-.832-1.11 0-2.09.208-2.944.624a4.27 4.27 0 0 0-1.952 1.904V7.528h-4.48V25h4.8Z" fill="#000112" fill-rule="nonzero"/><g transform="translate(0 1)" fill="#635FC7"><rect width="6" height="25" rx="2"/><rect opacity=".75" x="9" width="6" height="25" rx="2"/><rect opacity=".5" x="18" width="6" height="25" rx="2"/></g></g></svg>
\ No newline at end of file
<svg width="153" height="26" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M44.56 25v-5.344l1.92-2.112L50.928 25h5.44l-6.304-10.432 6.336-7.04h-5.92l-5.92 6.304V.776h-4.8V25h4.8Zm19.36.384c2.176 0 3.925-.672 5.248-2.016V25h4.48V13.48c0-1.259-.315-2.363-.944-3.312-.63-.95-1.51-1.69-2.64-2.224-1.13-.533-2.432-.8-3.904-.8-1.856 0-3.483.427-4.88 1.28-1.397.853-2.352 2.005-2.864 3.456l3.84 1.824a4.043 4.043 0 0 1 1.424-1.856c.65-.47 1.403-.704 2.256-.704.896 0 1.605.224 2.128.672.523.448.784 1.003.784 1.664v.48l-4.832.768c-2.09.341-3.648.992-4.672 1.952-1.024.96-1.536 2.176-1.536 3.648 0 1.579.55 2.816 1.648 3.712 1.099.896 2.587 1.344 4.464 1.344Zm.96-3.52c-.597 0-1.099-.15-1.504-.448-.405-.299-.608-.715-.608-1.248 0-.576.181-1.019.544-1.328.363-.31.885-.528 1.568-.656l3.968-.704v.544c0 1.067-.363 1.973-1.088 2.72-.725.747-1.685 1.12-2.88 1.12ZM81.968 25V14.792c0-1.003.299-1.808.896-2.416.597-.608 1.365-.912 2.304-.912.939 0 1.707.304 2.304.912.597.608.896 1.413.896 2.416V25h4.8V13.768c0-1.323-.277-2.48-.832-3.472a5.918 5.918 0 0 0-2.32-2.32c-.992-.555-2.15-.832-3.472-.832-1.11 0-2.09.208-2.944.624a4.27 4.27 0 0 0-1.952 1.904V7.528h-4.48V25h4.8Zm24.16.384c1.707 0 3.232-.405 4.576-1.216a8.828 8.828 0 0 0 3.184-3.296c.779-1.387 1.168-2.923 1.168-4.608 0-1.707-.395-3.248-1.184-4.624a8.988 8.988 0 0 0-3.2-3.28c-1.344-.81-2.848-1.216-4.512-1.216-2.112 0-3.787.619-5.024 1.856V.776h-4.8V25h4.48v-1.664c.619.661 1.392 1.168 2.32 1.52a8.366 8.366 0 0 0 2.992.528Zm-.576-4.32c-1.301 0-2.363-.443-3.184-1.328-.821-.885-1.232-2.043-1.232-3.472 0-1.408.41-2.56 1.232-3.456.821-.896 1.883-1.344 3.184-1.344 1.323 0 2.41.453 3.264 1.36.853.907 1.28 2.053 1.28 3.44 0 1.408-.427 2.56-1.28 3.456-.853.896-1.941 1.344-3.264 1.344Zm17.728 4.32c2.176 0 3.925-.672 5.248-2.016V25h4.48V13.48c0-1.259-.315-2.363-.944-3.312-.63-.95-1.51-1.69-2.64-2.224-1.13-.533-2.432-.8-3.904-.8-1.856 0-3.483.427-4.88 1.28-1.397.853-2.352 2.005-2.864 3.456l3.84 1.824a4.043 4.043 0 0 1 1.424-1.856c.65-.47 1.403-.704 2.256-.704.896 0 1.605.224 2.128.672.523.448.784 1.003.784 1.664v.48l-4.832.768c-2.09.341-3.648.992-4.672 1.952-1.024.96-1.536 2.176-1.536 3.648 0 1.579.55 2.816 1.648 3.712 1.099.896 2.587 1.344 4.464 1.344Zm.96-3.52c-.597 0-1.099-.15-1.504-.448-.405-.299-.608-.715-.608-1.248 0-.576.181-1.019.544-1.328.363-.31.885-.528 1.568-.656l3.968-.704v.544c0 1.067-.363 1.973-1.088 2.72-.725.747-1.685 1.12-2.88 1.12ZM141.328 25V14.792c0-1.003.299-1.808.896-2.416.597-.608 1.365-.912 2.304-.912.939 0 1.707.304 2.304.912.597.608.896 1.413.896 2.416V25h4.8V13.768c0-1.323-.277-2.48-.832-3.472a5.918 5.918 0 0 0-2.32-2.32c-.992-.555-2.15-.832-3.472-.832-1.11 0-2.09.208-2.944.624a4.27 4.27 0 0 0-1.952 1.904V7.528h-4.48V25h4.8Z" fill="#FFF" fill-rule="nonzero"/><g transform="translate(0 1)" fill="#635FC7"><rect width="6" height="25" rx="2"/><rect opacity=".75" x="9" width="6" height="25" rx="2"/><rect opacity=".5" x="18" width="6" height="25" rx="2"/></g></g></svg>
\ No newline at end of file
<svg width="24" height="25" xmlns="http://www.w3.org/2000/svg"><g fill="#635FC7" fill-rule="evenodd"><rect width="6" height="25" rx="2"/><rect opacity=".75" x="9" width="6" height="25" rx="2"/><rect opacity=".5" x="18" width="6" height="25" rx="2"/></g></svg>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
import { shuffle } from "lodash";
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import boardsSlice from "../redux/boardsSlice";
import Task from "./Task";
function Column({ colIndex }) {
const colors = [
"bg-red-500",
"bg-orange-500",
"bg-blue-500",
"bg-purple-500",
"bg-green-500",
"bg-indigo-500",
"bg-yellow-500",
"bg-pink-500",
"bg-sky-500",
];
const dispatch = useDispatch();
const [color, setColor] = useState(null)
const boards = useSelector((state) => state.boards);
const board = boards.find((board) => board.isActive === true);
const col = board.columns.find((col, i) => i === colIndex);
useEffect(() => {
setColor(shuffle(colors).pop())
}, [dispatch]);
const handleOnDrop = (e) => {
const { prevColIndex, taskIndex } = JSON.parse(
e.dataTransfer.getData("text")
);
if (colIndex !== prevColIndex) {
dispatch(
boardsSlice.actions.dragTask({ colIndex, prevColIndex, taskIndex })
);
}
};
const handleOnDragOver = (e) => {
e.preventDefault();
};
return (
<div
onDrop={handleOnDrop}
onDragOver={handleOnDragOver}
className="scrollbar-hide mx-5 pt-[90px] min-w-[280px] "
>
<p className=" font-semibold flex items-center gap-2 tracking-widest md:tracking-[.2em] text-[#828fa3]">
<div className={`rounded-full w-4 h-4 ${color} `} />
{col.name} ({col.tasks.length})
</p>
{col.tasks.map((task, index) => (
<Task key={index} taskIndex={index} colIndex={colIndex} />
))}
</div>
);
}
export default Column;
import React from "react";
function ElipsisMenu({ type, setOpenEditModal, setOpenDeleteModal }) {
return (
<div
className={
type === "Boards"
? " absolute top-16 right-5"
: " absolute top-6 right-4"
}
>
<div className=" flex justify-end items-center">
<div className=" w-40 text-sm z-50 font-medium shadow-md shadow-[#364e7e1a] bg-white dark:bg-[#20212c] space-y-4 py-5 px-4 rounded-lg h-auto pr-12">
<p
onClick={() => {
setOpenEditModal();
}}
className=" cursor-pointer dark:text-gray-400 text-gray-700"
>
Edit {type}
</p>
<p
onClick={() => setOpenDeleteModal()}
className=" cursor-pointer text-red-500"
>
Delete {type}
</p>
</div>
</div>
</div>
);
}
export default ElipsisMenu;
import React, { useState } from "react";
import AddEditBoardModal from "../modals/AddEditBoardModal";
function EmptyBoard({ type }) {
const [isBoardModalOpen, setIsBoardModalOpen] = useState(false);
return (
<div className=" bg-white dark:bg-[#2b2c37] h-screen w-screen flex flex-col items-center justify-center">
<h3 className=" text-gray-500 font-bold">
{type === "edit"
? "This board is empty. Create a new column to get started."
: "There are no boards available. Create a new board to get started"}
</h3>
<button
onClick={() => {
setIsBoardModalOpen(true);
}}
className="w-full items-center max-w-xs font-bold hover:opacity-70 dark:text-white dark:bg-[#635fc7] mt-8 relative text-white bg-[#635fc7] py-2 rounded-full"
>
{type === "edit" ? "+ Add New Column" : "+ Add New Board"}
</button>
{isBoardModalOpen && (
<AddEditBoardModal
type={type}
setIsBoardModalOpen={setIsBoardModalOpen}
/>
)}
</div>
);
}
export default EmptyBoard;
import React, { useState } from "react";
import Logo from "../assets/logo.svg";
import iconDown from "../assets/icon-chevron-down.svg";
import iconUp from "../assets/icon-chevron-up.svg";
import elipsis from "../assets/icon-vertical-ellipsis.svg";
import HeaderDropDown from "./HeaderDropDown";
import ElipsisMenu from "./ElipsisMenu";
import AddEditTaskModal from "../modals/AddEditTaskModal";
import AddEditBoardModal from "../modals/AddEditBoardModal";
import { useDispatch, useSelector } from "react-redux";
import DeleteModal from "../modals/DeleteModal";
import boardsSlice from "../redux/boardsSlice";
function Header({ setIsBoardModalOpen, isBoardModalOpen }) {
const [openDropdown, setOpenDropdown] = useState(false);
const [isElipsisMenuOpen, setIsElipsisMenuOpen] = useState(false);
const [boardType, setBoardType] = useState("add");
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [isTaskModalOpen, setIsTaskModalOpen] = useState(false);
const dispatch = useDispatch();
const boards = useSelector((state) => state.boards);
const board = boards.find((board) => board.isActive);
const onDropdownClick = () => {
setOpenDropdown((state) => !state);
setIsElipsisMenuOpen(false);
setBoardType("add");
};
const setOpenEditModal = () => {
setIsBoardModalOpen(true);
setIsElipsisMenuOpen(false);
};
const setOpenDeleteModal = () => {
setIsDeleteModalOpen(true);
setIsElipsisMenuOpen(false);
};
const onDeleteBtnClick = (e) => {
if (e.target.textContent === "Delete") {
dispatch(boardsSlice.actions.deleteBoard());
dispatch(boardsSlice.actions.setBoardActive({ index: 0 }));
setIsDeleteModalOpen(false);
} else {
setIsDeleteModalOpen(false);
}
};
return (
<div className=" p-4 fixed left-0 bg-white dark:bg-[#2b2c37] z-50 right-0 ">
<header className=" flex justify-between dark:text-white items-center ">
{/* Left Side */}
<div className=" flex items-center space-x-2 md:space-x-4">
<img src={Logo} alt=" Logo " className=" h-10 w-50"/>
{/* <img src="C:\Users\mdsac\Desktop\ZenFlow\src\assets\logo.svg" alt="" width="100" height="50"></img> */}
{/* <h3 className=" md:text-4xl hidden md:inline-block font-bold font-sans">
ZenFlow
</h3> */}
{/* <img src="C:\Users\mdsac\Desktop\ZenFlow\src\assets\logo.svg" alt="" width="100" height="50"></img> */}
<div className=" flex items-center ">
<h3 className=" truncate max-w-[200px] md:text-2xl text-xl font-bold md:ml-20 font-sans ">
{board.name}
</h3>
<img
src={openDropdown ? iconUp : iconDown}
alt=" dropdown icon"
className=" w-3 ml-2 md:hidden"
onClick={onDropdownClick}
/>
</div>
</div>
{/* Right Side */}
<div className=" flex space-x-4 items-center md:space-x-6 ">
<button
className=" button hidden md:block "
onClick={() => {
setIsTaskModalOpen((prevState) => !prevState);
}}
>
+ Add New Task
</button>
<button
onClick={() => {
setIsTaskModalOpen((prevState) => !prevState);
}}
className=" button py-1 px-3 md:hidden "
>
+
</button>
<img
onClick={() => {
setBoardType("edit");
setOpenDropdown(false)
setIsElipsisMenuOpen((prevState) => !prevState);
}}
src={elipsis}
alt="elipsis"
className=" cursor-pointer h-6"
/>
{isElipsisMenuOpen && (
<ElipsisMenu
type="Boards"
setOpenEditModal={setOpenEditModal}
setOpenDeleteModal={setOpenDeleteModal}
/>
)}
</div>
{openDropdown && (
<HeaderDropDown
setOpenDropdown={setOpenDropdown}
setIsBoardModalOpen={setIsBoardModalOpen}
/>
)}
</header>
{isTaskModalOpen && (
<AddEditTaskModal
setIsAddTaskModalOpen={setIsTaskModalOpen}
type="add"
device="mobile"
/>
)}
{isBoardModalOpen && (
<AddEditBoardModal
setBoardType={setBoardType}
type={boardType}
setIsBoardModalOpen={setIsBoardModalOpen}
/>
)}
{isDeleteModalOpen && (
<DeleteModal
setIsDeleteModalOpen={setIsDeleteModalOpen}
type="board"
title={board.name}
onDeleteBtnClick={onDeleteBtnClick}
/>
)}
</div>
);
}
export default Header;
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Switch } from "@headlessui/react";
import boardIcon from "../assets/icon-board.svg";
import useDarkMode from "../hooks/useDarkMode";
import darkIcon from "../assets/icon-dark-theme.svg";
import lightIcon from "../assets/icon-light-theme.svg";
import boardsSlice from "../redux/boardsSlice";
function HeaderDropDown({ setOpenDropdown, setIsBoardModalOpen }) {
const dispatch = useDispatch()
const [colorTheme, setTheme] = useDarkMode();
const [darkSide, setDarkSide] = useState(
colorTheme === "light" ? true : false
);
const toggleDarkMode = (checked) => {
setTheme(colorTheme);
setDarkSide(checked);
};
const boards = useSelector((state) => state.boards);
return (
<div
className=" py-10 px-6 absolute left-0 right-0 bottom-[-100vh] top-16 dropdown "
onClick={(e) => {
if (e.target !== e.currentTarget) {
return;
}
setOpenDropdown(false);
}}
>
{/* DropDown Modal */}
<div className=" bg-white dark:bg-[#2b2c37] shadow-md shadow-[#364e7e1a] w-full py-4 rounded-xl">
<h3 className=" dark:text-gray-300 text-gray-600 font-semibold mx-4 mb-8 ">
ALL BOARDS ({boards?.length})
</h3>
<div className=" dropdown-borad ">
{boards.map((board, index) => (
<div
className={` flex items-baseline space-x-2 px-5 py-4 ${
board.isActive &&
" bg-[#635fc7] rounded-r-full text-white mr-8 "
} `}
key={index}
onClick={() => {
dispatch(boardsSlice.actions.setBoardActive({ index }));
}}
>
<img src={boardIcon} className=" filter-white h-4 " />{" "}
<p className=" text-lg font-bold ">{board.name}</p>
</div>
))}
<div
onClick={() => {
setIsBoardModalOpen(true);
setOpenDropdown(false)
}}
className=" flex items-baseline space-x-2 text-[#635fc7] px-5 py-4 ">
<img src={boardIcon} className=" filter-white h-4 " />
<p className=" text-lg font-bold ">Create New Board </p>
</div>
<div className=" mx-2 p-4 space-x-2 bg-slate-100 dark:bg-[#20212c] flex justify-center items-center rounded-lg">
<img src={lightIcon} alt="sun indicating light mode" />
<Switch
checked={darkSide}
onChange={toggleDarkMode}
className={`${
darkSide ? "bg-[#635fc7]" : "bg-gray-200"
} relative inline-flex h-6 w-11 items-center rounded-full`}
>
<span className="sr-only">Enable notifications</span>
<span
className={`${
darkSide ? "translate-x-6" : "translate-x-1"
} inline-block h-4 w-4 transform rounded-full bg-white transition`}
/>
</Switch>
<img src={darkIcon} alt="moon indicating dark mode" />
</div>
</div>
</div>
</div>
);
}
export default HeaderDropDown;
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import AddEditBoardModal from "../modals/AddEditBoardModal";
import Column from "./Column";
import EmptyBoard from "./EmptyBoard";
import Sidebar from "./Sidebar";
function Home() {
const [windowSize, setWindowSize] = useState([
window.innerWidth,
window.innerHeight,
]);
useEffect(() => {
const handleWindowResize = () => {
setWindowSize([window.innerWidth, window.innerHeight]);
};
window.addEventListener("resize", handleWindowResize);
return () => {
window.removeEventListener("resize", handleWindowResize);
};
});
const [isBoardModalOpen, setIsBoardModalOpen] = useState(false);
const boards = useSelector((state) => state.boards);
const board = boards.find((board) => board.isActive === true);
const columns = board.columns;
const [isSideBarOpen, setIsSideBarOpen] = useState(true);
return (
<div
className={
windowSize[0] >= 768 && isSideBarOpen
? " bg-[#f4f7fd] scrollbar-hide h-screen flex dark:bg-[#20212c] overflow-x-scroll gap-6 ml-[261px]"
: "bg-[#f4f7fd] scrollbar-hide h-screen flex dark:bg-[#20212c] overflow-x-scroll gap-6 "
}
>
{windowSize[0] >= 768 && (
<Sidebar
setIsBoardModalOpen={setIsBoardModalOpen}
isBoardModalOpen={isBoardModalOpen}
isSideBarOpen={isSideBarOpen}
setIsSideBarOpen={setIsSideBarOpen}
/>
)}
{/* Columns Section */}
{columns.length > 0 ? (
<>
{columns.map((col, index) => (
<Column key={index} colIndex={index} />
))}
<div
onClick={() => {
setIsBoardModalOpen(true);
}}
className=" h-screen dark:bg-[#2b2c3740] flex justify-center items-center font-bold text-2xl hover:text-[#635FC7] transition duration-300 cursor-pointer bg-[#E9EFFA] scrollbar-hide mb-2 mx-5 pt-[90px] min-w-[280px] text-[#828FA3] mt-[135px] rounded-lg "
>
+ New Column
</div>
</>
) : (
<>
<EmptyBoard type="edit" />
</>
)}
{isBoardModalOpen && (
<AddEditBoardModal
type="edit"
setIsBoardModalOpen={setIsBoardModalOpen}
/>
)}
</div>
);
}
export default Home;
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Switch } from "@headlessui/react";
import boardIcon from "../assets/icon-board.svg";
import useDarkMode from "../hooks/useDarkMode";
import darkIcon from "../assets/icon-dark-theme.svg";
import lightIcon from "../assets/icon-light-theme.svg";
import showSidebarIcon from "../assets/icon-show-sidebar.svg";
import hideSidebarIcon from "../assets/icon-hide-sidebar.svg";
import boardsSlice from "../redux/boardsSlice";
import AddEditBoardModal from "../modals/AddEditBoardModal";
function Sidebar({ isSideBarOpen, setIsSideBarOpen }) {
const dispatch = useDispatch();
const [isBoardModalOpen, setIsBoardModalOpen] = useState(false);
const [colorTheme, setTheme] = useDarkMode();
const [darkSide, setDarkSide] = useState(
colorTheme === "light" ? true : false
);
const toggleDarkMode = (checked) => {
setTheme(colorTheme);
setDarkSide(checked);
};
const boards = useSelector((state) => state.boards);
const toggleSidebar = () => {
setIsSideBarOpen((curr) => !curr);
};
return (
<div>
<div
className={
isSideBarOpen
? `min-w-[261px] bg-white dark:bg-[#0b0b45] fixed top-[72px] h-screen items-center left-0 z-20`
: ` bg-[#635FC7] dark:bg-[#0b0b45] dark:hover:bg-[#635FC7] top-auto bottom-10 justify-center items-center hover:opacity-80 cursor-pointer p-0 transition duration-300 transform fixed felx w-[56px] h-[48px] rounded-r-full `
}
>
<div>
{/* reWrite modal */}
{isSideBarOpen && (
<div className=" bg-white dark:bg-[#2b2c37] w-full py-4 rounded-xl">
<h3 className=" dark:text-gray-300 text-gray-600 font-semibold mx-4 mb-8 ">
ALL BOARDS ({boards?.length})
</h3>
<div className=" dropdown-borad flex flex-col h-[70vh] justify-between ">
<div>
{boards.map((board, index) => (
<div
className={` flex items-baseline space-x-2 px-5 mr-8 rounded-r-full duration-500 ease-in-out py-4 cursor-pointer hover:bg-[#635fc71a] hover:text-[#12086F] dark:hover:bg-white dark:hover:text-[#12086F] dark:text-white ${
board.isActive &&
" bg-[#12086F] rounded-r-full text-white mr-8 "
} `}
key={index}
onClick={() => {
dispatch(boardsSlice.actions.setBoardActive({ index }));
}}
>
<img src={boardIcon} className=" filter-white h-4 " />{" "}
<p className=" text-lg font-bold ">{board.name}</p>
</div>
))}
<div
className=" flex items-baseline space-x-2 mr-8 rounded-r-full duration-500 ease-in-out cursor-pointer text-[#635fc7] px-5 py-4 hover:bg-[#635fc71a] hover:text-[#635fc7] dark:hover:bg-white "
onClick={() => {
setIsBoardModalOpen(true);
}}
>
<img src={boardIcon} className=" filter-white h-4 " />
<p className=" text-lg font-bold ">Create New Board </p>
</div>
</div>
<div className=" mx-2 p-4 relative space-x-2 bg-slate-100 dark:bg-[#20212c] flex justify-center items-center rounded-lg">
<img src={lightIcon} alt="sun indicating light mode" />
<Switch
checked={darkSide}
onChange={toggleDarkMode}
className={`${
darkSide ? "bg-[#635fc7]" : "bg-gray-200"
} relative inline-flex h-6 w-11 items-center rounded-full`}
>
<span
className={`${
darkSide ? "translate-x-6" : "translate-x-1"
} inline-block h-4 w-4 transform rounded-full bg-white transition`}
/>
</Switch>
<img src={darkIcon} alt="moon indicating dark mode" />
</div>
</div>
</div>
)}
{/* Sidebar hide/show toggle */}
{isSideBarOpen ? (
<div
onClick={() => toggleSidebar()}
className=" flex items-center mt-2 absolute bottom-16 text-lg font-bold rounded-r-full hover:text-[#635FC7] cursor-pointer mr-6 mb-8 px-8 py-4 hover:bg-[#635fc71a] dark:hover:bg-white space-x-2 justify-center my-4 text-gray-500 "
>
<img
className=" min-w-[20px]"
src={hideSidebarIcon}
alt=" side bar show/hide"
/>
{isSideBarOpen && <p> Hide Sidebar </p>}
</div>
) : (
<div className=" absolute p-5 " onClick={() => toggleSidebar()}>
<img src={showSidebarIcon} alt="showSidebarIcon" />
</div>
)}
</div>
</div>
{isBoardModalOpen && (
<AddEditBoardModal
type="add"
setIsBoardModalOpen={setIsBoardModalOpen}
/>
)}
</div>
);
}
export default Sidebar;
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import boardsSlice from "../redux/boardsSlice";
function Subtask({ index, taskIndex, colIndex }) {
const dispatch = useDispatch();
const boards = useSelector((state) => state.boards);
const board = boards.find((board) => board.isActive === true);
const col = board.columns.find((col, i) => i === colIndex);
const task = col.tasks.find((task, i) => i === taskIndex);
const subtask = task.subtasks.find((subtask, i) => i === index);
const checked = subtask.isCompleted;
const onChange = (e) => {
dispatch(
boardsSlice.actions.setSubtaskCompleted({ index, taskIndex, colIndex })
);
};
return (
<div className=" w-full flex hover:bg-[#635fc740] dark:hover:bg-[#635fc740] rounded-md relative items-center justify-start dark:bg-[#20212c] p-3 gap-4 bg-[#f4f7fd]">
<input
className=" w-4 h-4 accent-[#635fc7] cursor-pointer "
type="checkbox"
checked={checked}
onChange={onChange}
/>
<p className={checked && " line-through opacity-30 "}>
{subtask.title}
</p>
</div>
);
}
export default Subtask;
import React, { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import TaskModal from "../modals/TaskModal";
import axios from "axios"; // Don't forget to import axios if it's used in your component
function Task({ colIndex, taskIndex }) {
const boards = useSelector((state) => state.boards);
const board = boards.find((board) => board.isActive === true);
const columns = board.columns;
const col = columns.find((col, i) => i === colIndex);
const task = col.tasks.find((task, i) => i === taskIndex);
const [isTaskModalOpen, setIsTaskModalOpen] = useState(false);
let completed = 0;
let subtasks = task.subtasks;
subtasks.forEach((subtask) => {
if (subtask.isCompleted) {
completed++;
}
});
const handleOnDrag = (e) => {
e.dataTransfer.setData(
"text",
JSON.stringify({ taskIndex, prevColIndex: colIndex })
);
};
useEffect(() => {
axios.get("http://127.0.0.1:5000/get_all")
.then((response) => {
console.log("API response:", response.data); // Log the response data
// You can update your component's state with the data you received
})
.catch((error) => {
console.error("API call error:", error);
});
}, []);
return (
<div>
<div
onClick={() => {
setIsTaskModalOpen(true);
}}
draggable
onDragStart={handleOnDrag}
className="w-[280px] first:my-5 rounded-lg bg-white dark:bg-[#2b2c37] shadow-[#364e7e1a] py-6 px-3 shadow-lg hover:text-[#12086F] dark:text-white dark:hover:text-[#12086F] cursor-pointer"
>
<p className="font-bold tracking-wide">{task.task_desc}</p>
<p className="font-bold text-xs tracking-tighter mt-2 text-gray-500">
{completed} of {subtasks.length} completed tasks
</p>
</div>
{isTaskModalOpen && (
<TaskModal
colIndex={colIndex}
taskIndex={taskIndex}
setIsTaskModalOpen={setIsTaskModalOpen}
/>
)}
</div>
);
}
export default Task;
This diff is collapsed.
import React, { useEffect, useState } from 'react'
function useDarkMode() {
const [theme, setTheme] = useState(localStorage.theme)
const colorTheme = theme === "dark" ? "light" : "dark";
useEffect(() => {
const root = window.document.documentElement;
root.classList.remove(colorTheme);
root.classList.add(theme);
localStorage.setItem('theme', theme);
}, [theme, colorTheme]);
return [colorTheme, setTheme]
}
export default useDarkMode
\ No newline at end of file
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
@layer components {
.button {
@apply bg-[#12086F] py-2 px-4 rounded-full text-white text-lg font-semibold hover:opacity-80 duration-200
}
}
.dropdown{
background-color: #00000080;
}
.filter-white {
filter: invert(100%) sepia(96%) saturate(16%) hue-rotate(350deg)
brightness(104%) contrast(100%);
}
.select-status{
background-image: url("./assets/icon-chevron-down.svg");
appearance: none;
background-repeat: no-repeat;
background-position: right 16px top 50%;
}
\ No newline at end of file
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import store from './redux/store';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
\ No newline at end of file
import React, { useState } from "react";
import crossIcon from "../assets/icon-cross.svg";
import boardsSlice from "../redux/boardsSlice";
import { v4 as uuidv4 } from "uuid";
import { useDispatch, useSelector } from "react-redux";
function AddEditBoardModal({ setIsBoardModalOpen, type , }) {
const dispatch = useDispatch();
const [isFirstLoad, setIsFirstLoad] = useState(true);
const [name, setName] = useState("");
const [newColumns, setNewColumns] = useState([
{ name: "Todo", tasks: [], id: uuidv4() },
{ name: "Doing", tasks: [], id: uuidv4() },
]);
const [isValid, setIsValid] = useState(true);
const board = useSelector((state) => state.boards).find(
(board) => board.isActive
);
if (type === "edit" && isFirstLoad) {
setNewColumns(
board.columns.map((col) => {
return { ...col, id: uuidv4() };
})
);
setName(board.name);
setIsFirstLoad(false);
}
const validate = () => {
setIsValid(false);
if (!name.trim()) {
return false;
}
for (let i = 0 ; i < newColumns.length ; i++) {
if (!newColumns[i].name.trim()) {
return false;
}
}
setIsValid(true);
return true;
};
const onChange = (id, newValue) => {
setNewColumns((prevState) => {
const newState = [...prevState];
const column = newState.find((col) => col.id === id);
column.name = newValue;
return newState;
});
};
const onDelete = (id) => {
setNewColumns((prevState) => prevState.filter((el) => el.id !== id));
};
const onSubmit = (type) => {
setIsBoardModalOpen(false);
if (type === "add") {
dispatch(boardsSlice.actions.addBoard({ name, newColumns }));
} else {
dispatch(boardsSlice.actions.editBoard({ name, newColumns }));
}
};
return (
<div
className=" fixed right-0 top-0 px-2 py-4 overflow-scroll scrollbar-hide z-50 left-0 bottom-0 justify-center items-center flex dropdown "
onClick={(e) => {
if (e.target !== e.currentTarget) {
return;
}
setIsBoardModalOpen(false);
}}
>
<div
className=" scrollbar-hide overflow-y-scroll max-h-[95vh] bg-white dark:bg-[#2b2c37] text-black dark:text-white font-bold
shadow-md shadow-[#364e7e1a] max-w-md mx-auto my-auto w-full px-8 py-8 rounded-xl"
>
<h3 className=" text-lg ">
{type === "edit" ? "Edit" : "Add New"} Board
</h3>
{/* Task Name */}
<div className="mt-8 flex flex-col space-y-1">
<label className=" text-sm dark:text-white text-gray-500">
Board Name
</label>
<input
className=" bg-transparent px-4 py-2 rounded-md text-sm border-[0.5px] border-gray-600 focus:outline-[#635fc7] outline-1 ring-0 "
placeholder=" e.g Web Design"
value={name}
onChange={(e) => setName(e.target.value)}
id="board-name-input"
/>
</div>
{/* Board Columns */}
<div className="mt-8 flex flex-col space-y-3">
<label className=" text-sm dark:text-white text-gray-500">
Board Columns
</label>
{newColumns.map((column, index) => (
<div key={index} className=" flex items-center w-full ">
<input
className=" bg-transparent flex-grow px-4 py-2 rounded-md text-sm border-[0.5px] border-gray-600 focus:outline-[#635fc7] outline-[1px] "
onChange={(e) => {
onChange(column.id, e.target.value);
}}
type="text"
value={column.name}
/>
<img
src={crossIcon}
onClick={() => {
onDelete(column.id);
}}
className=" m-4 cursor-pointer "
/>
</div>
))}
<div>
<button
className=" w-full items-center hover:opacity-70 dark:text-[#635fc7] dark:bg-white text-white bg-[#635fc7] py-2 rounded-full "
onClick={() => {
setNewColumns((state) => [
...state,
{ name: "", tasks: [], id: uuidv4() },
]);
}}
>
+ Add New Column
</button>
<button
onClick={() => {
const isValid = validate();
if (isValid === true) onSubmit(type);
}}
className=" w-full items-center hover:opacity-70 dark:text-white dark:bg-[#635fc7] mt-8 relative text-white bg-[#635fc7] py-2 rounded-full"
>
{type === "add" ? "Create New Board" : "Save Changes"}
</button>
</div>
</div>
</div>
</div>
);
}
export default AddEditBoardModal;
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { v4 as uuidv4 } from "uuid";
import crossIcon from "../assets/icon-cross.svg";
import boardsSlice from "../redux/boardsSlice";
function AddEditTaskModal({
type,
device,
setIsTaskModalOpen,
setIsAddTaskModalOpen,
taskIndex,
prevColIndex = 0,
}) {
const dispatch = useDispatch();
const [isFirstLoad, setIsFirstLoad] = useState(true);
const [isValid, setIsValid] = useState(true);
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const board = useSelector((state) => state.boards).find(
(board) => board.isActive
);
const columns = board.columns;
const col = columns.find((col, index) => index === prevColIndex);
const task = col ? col.tasks.find((task, index) => index === taskIndex) : [];
const [status, setStatus] = useState(columns[prevColIndex].name);
const [newColIndex, setNewColIndex] = useState(prevColIndex);
const [subtasks, setSubtasks] = useState([
{ title: "", isCompleted: false, id: uuidv4() },
{ title: "", isCompleted: false, id: uuidv4() },
]);
const onChangeSubtasks = (id, newValue) => {
setSubtasks((prevState) => {
const newState = [...prevState];
const subtask = newState.find((subtask) => subtask.id === id);
subtask.title = newValue;
return newState;
});
};
const onChangeStatus = (e) => {
setStatus(e.target.value);
setNewColIndex(e.target.selectedIndex);
};
const validate = () => {
setIsValid(false);
if (!title.trim()) {
return false;
}
for (let i = 0; i < subtasks.length; i++) {
if (!subtasks[i].title.trim()) {
return false;
}
}
setIsValid(true);
return true;
};
if (type === "edit" && isFirstLoad) {
setSubtasks(
task.subtasks.map((subtask) => {
return { ...subtask, id: uuidv4() };
})
);
setTitle(task.title);
setDescription(task.description);
setIsFirstLoad(false);
}
const onDelete = (id) => {
setSubtasks((prevState) => prevState.filter((el) => el.id !== id));
};
const onSubmit = async (type) => {
if (type === "add") {
// dispatch(
// boardsSlice.actions.addTask({
// title,
// description,
// subtasks,
// status,
// newColIndex,
// })
// );
let body = {
"task_description" : description
}
try {
const response = await fetch("http://127.0.0.1:5000/predict", {
method: "POST", // or 'PUT'
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
const result = await response.json();
console.log("Success:", result);
// Initialize empty arrays for "description" and "level"
const descriptions = [];
const levels = [];
// Iterate through the data array
result.forEach(item => {
descriptions.push(item.task); // Add the task to the "description" array
levels.push(item.prediction); // Add the prediction to the "level" array
});
// Join the arrays into comma-separated strings
const descriptionString = descriptions.join(', ');
const levelString = levels.join(', ');
console.log("descriptionString",descriptionString,levelString)
let reqbody = {
"title" : title,
"description" : descriptionString,
"level": levelString
}
try {
const response = await fetch("http://127.0.0.1:5000/submit_data", {
method: "POST", // or 'PUT'
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(reqbody),
});
const result = await response.json();
console.log("Success:", result);
} catch (error) {
console.error("Error:", error);
}
} catch (error) {
console.error("Error:", error);
}
} else {
dispatch(
boardsSlice.actions.editTask({
title,
description,
subtasks,
status,
taskIndex,
prevColIndex,
newColIndex,
})
);
}
};
return (
<div
className={
device === "mobile"
? " py-6 px-6 pb-40 absolute overflow-y-scroll left-0 flex right-0 bottom-[-100vh] top-0 dropdown "
: " py-6 px-6 pb-40 absolute overflow-y-scroll left-0 flex right-0 bottom-0 top-0 dropdown "
}
onClick={(e) => {
if (e.target !== e.currentTarget) {
return;
}
setIsAddTaskModalOpen(false);
}}
>
{/* Modal Section */}
<div
className=" scrollbar-hide overflow-y-scroll max-h-[95vh] my-auto bg-white dark:bg-[#2b2c37] text-black dark:text-white font-bold
shadow-md shadow-[#364e7e1a] max-w-md mx-auto w-full px-8 py-8 rounded-xl"
>
<h3 className=" text-lg ">
{type === "edit" ? "Edit" : "Add New"} Task
</h3>
{/* Task Name */}
<div className="mt-8 flex flex-col space-y-1">
<label className=" text-sm dark:text-white text-gray-500">
Task Name
</label>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
id="task-name-input"
type="text"
className=" bg-transparent px-4 py-2 outline-none focus:border-0 rounded-md text-sm border-[0.5px] border-gray-600 focus:outline-[#635fc7] outline-1 ring-0 "
placeholder=" e.g Take coffee break"
/>
</div>
{/* Description */}
<div className="mt-8 flex flex-col space-y-1">
<label className=" text-sm dark:text-white text-gray-500">
Description
</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
id="task-description-input"
className=" bg-transparent outline-none min-h-[200px] focus:border-0 px-4 py-2 rounded-md text-sm border-[0.5px] border-gray-600 focus:outline-[#635fc7] outline-[1px] "
placeholder="e.g. It's always good to take a break. This
15 minute break will recharge the batteries
a little."
/>
</div>
{/* Subtasks */}
<div className="mt-8 flex flex-col space-y-3">
<label className=" text-sm dark:text-white text-gray-500">
Subtasks
</label>
{subtasks.map((subtask, index) => (
<div key={index} className=" flex items-center w-full ">
<input
onChange={(e) => {
onChangeSubtasks(subtask.id, e.target.value);
}}
type="text"
value={subtask.title}
className=" bg-transparent outline-none focus:border-0 flex-grow px-4 py-2 rounded-md text-sm border-[0.5px] border-gray-600 focus:outline-[#635fc7] outline-[1px] "
placeholder=" e.g Take coffee break"
/>
<img
src={crossIcon}
onClick={() => {
onDelete(subtask.id);
}}
className=" m-4 cursor-pointer "
/>
</div>
))}
<button
className=" w-full items-center dark:text-[#635fc7] dark:bg-white text-white bg-[#635fc7] py-2 rounded-full "
onClick={() => {
setSubtasks((state) => [
...state,
{ title: "", isCompleted: false, id: uuidv4() },
]);
}}
>
+ Add New Subtask
</button>
</div>
{/* current Status */}
<div className="mt-8 flex flex-col space-y-3">
<label className=" text-sm dark:text-white text-gray-500">
Current Status
</label>
<select
value={status}
onChange={onChangeStatus}
className=" select-status flex-grow px-4 py-2 rounded-md text-sm bg-transparent focus:border-0 border-[1px] border-gray-300 focus:outline-[#635fc7] outline-none"
>
{columns.map((column, index) => (
<option key={index}>{column.name}</option>
))}
</select>
<button
onClick={() => {
const isValid = validate();
if (isValid) {
onSubmit(type);
setIsAddTaskModalOpen(false);
type === "edit" && setIsTaskModalOpen(false);
}
}}
className=" w-full items-center text-white bg-[#635fc7] py-2 rounded-full "
>
{type === "edit" ? " save edit" : "Assign tasks"}
</button>
</div>
</div>
</div>
);
}
export default AddEditTaskModal;
import React from "react";
function DeleteModal({ type, title, onDeleteBtnClick, setIsDeleteModalOpen }) {
return (
// Modal Container
<div
onClick={(e) => {
if (e.target !== e.currentTarget) {
return;
}
setIsDeleteModalOpen(false);
}}
className="fixed right-0 top-0 px-2 py-4 overflow-scroll scrollbar-hide z-50 left-0 bottom-0 justify-center items-center flex dropdown"
>
{/* Delete Modal */}
<div className=" scrollbar-hide overflow-y-scroll max-h-[95vh] my-auto bg-white dark:bg-[#2b2c37] text-black dark:text-white font-bold shadow-md shadow-[#364e7e1a] max-w-md mx-auto w-full px-8 py-8 rounded-xl ">
<h3 className=" font-bold text-red-500 text-xl ">
Delete this {type}?
</h3>
{type === "task" ? (
<p className="text-gray-500 font-[600] tracking-wide text-xs pt-6">
Are you sure you want to delete the "{title}" task and its subtasks?
This action cannot be reversed.
</p>
) : (
<p className="text-gray-500 font-[600] tracking-wide text-xs pt-6">
Are you sure you want to delete the "{title}" board? This action
will remove all columns and tasks and cannot be reversed.
</p>
)}
<div className=" flex w-full mt-4 items-center justify-center space-x-4 ">
<button
onClick={onDeleteBtnClick}
className="w-full items-center text-white hover:opacity-75 bg-red-500 py-2 rounded-full"
>
Delete
</button>
<button
onClick={() => {
setIsDeleteModalOpen(false)
}}
className="w-full items-center text-[#635fc7] dark:bg-white hover:opacity-75 bg-[#635fc71a] py-2 rounded-full"
>
Cancel
</button>
</div>
</div>
</div>
);
}
export default DeleteModal;
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import ElipsisMenu from "../components/ElipsisMenu";
import elipsis from "../assets/icon-vertical-ellipsis.svg";
import boardsSlice from "../redux/boardsSlice";
import Subtask from "../components/Subtask";
import AddEditTaskModal from "./AddEditTaskModal";
import DeleteModal from "./DeleteModal";
function TaskModal({ taskIndex, colIndex, setIsTaskModalOpen }) {
const dispatch = useDispatch();
const [isElipsisMenuOpen, setIsElipsisMenuOpen] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const boards = useSelector((state) => state.boards);
const board = boards.find((board) => board.isActive === true);
const columns = board.columns;
const col = columns.find((col, i) => i === colIndex);
const task = col.tasks.find((task, i) => i === taskIndex);
const subtasks = task.subtasks;
let completed = 0;
subtasks.forEach((subtask) => {
if (subtask.isCompleted) {
completed++;
}
});
const [status, setStatus] = useState(task.status);
const [newColIndex, setNewColIndex] = useState(columns.indexOf(col));
const onChange = (e) => {
setStatus(e.target.value);
setNewColIndex(e.target.selectedIndex);
};
const onClose = (e) => {
if (e.target !== e.currentTarget) {
return;
}
dispatch(
boardsSlice.actions.setTaskStatus({
taskIndex,
colIndex,
newColIndex,
status,
})
);
setIsTaskModalOpen(false);
};
const onDeleteBtnClick = (e) => {
if (e.target.textContent === "Delete") {
dispatch(boardsSlice.actions.deleteTask({ taskIndex, colIndex }));
setIsTaskModalOpen(false);
setIsDeleteModalOpen(false);
} else {
setIsDeleteModalOpen(false);
}
};
const [isAddTaskModalOpen, setIsAddTaskModalOpen] = useState(false);
const setOpenEditModal = () => {
setIsAddTaskModalOpen(true);
setIsElipsisMenuOpen(false);
};
const setOpenDeleteModal = () => {
setIsElipsisMenuOpen(false);
setIsDeleteModalOpen(true);
};
return (
<div
onClick={onClose}
className=" fixed right-0 top-0 px-2 py-4 overflow-scroll scrollbar-hide z-50 left-0 bottom-0 justify-center items-center flex dropdown "
>
{/* MODAL SECTION */}
<div className=" scrollbar-hide overflow-y-scroll max-h-[95vh] my-auto bg-white dark:bg-[#2b2c37] text-black dark:text-white font-bold shadow-md shadow-[#364e7e1a] max-w-md mx-auto w-full px-8 py-8 rounded-xl">
<div className=" relative flex justify-between w-full items-center">
<h1 className=" text-lg">{task.title}</h1>
<img
onClick={() => {
setIsElipsisMenuOpen((prevState) => !prevState);
}}
src={elipsis}
alt="elipsis"
className=" cursor-pointer h-6"
/>
{isElipsisMenuOpen && (
<ElipsisMenu
setOpenEditModal={setOpenEditModal}
setOpenDeleteModal={setOpenDeleteModal}
type="Task"
/>
)}
</div>
<p className=" text-gray-500 font-[600] tracking-wide text-xs pt-6">
{task.description}
</p>
<p className=" pt-6 text-gray-500 tracking-widest text-sm">
Subtasks ({completed} of {subtasks.length})
</p>
{/* subtasks section */}
<div className=" mt-3 space-y-2">
{subtasks.map((subtask, index) => {
return (
<Subtask
index={index}
taskIndex={taskIndex}
colIndex={colIndex}
key={index}
/>
);
})}
</div>
{/* Current Status Section */}
<div className="mt-8 flex flex-col space-y-3">
<label className=" text-sm dark:text-white text-gray-500">
Current Status
</label>
<select
className=" select-status flex-grow px-4 py-2 rounded-md text-sm bg-transparent focus:border-0 border-[1px] border-gray-300 focus:outline-[#635fc7] outline-none"
value={status}
onChange={onChange}
>
{columns.map((col, index) => (
<option className="status-options" key={index}>
{col.name}
</option>
))}
</select>
</div>
</div>
{isDeleteModalOpen && (
<DeleteModal
onDeleteBtnClick={onDeleteBtnClick}
type="task"
title={task.title}
/>
)}
{isAddTaskModalOpen && (
<AddEditTaskModal
setIsAddTaskModalOpen={setIsAddTaskModalOpen}
setIsTaskModalOpen={setIsTaskModalOpen}
type="edit"
taskIndex={taskIndex}
prevColIndex={colIndex}
/>
)}
</div>
);
}
export default TaskModal;
import { createSlice } from "@reduxjs/toolkit";
import data from "../data.json";
const boardsSlice = createSlice({
name: "boards",
initialState: data.boards,
reducers: {
addBoard: (state, action) => {
const isActive = state.length > 0 ? false : true;
const payload = action.payload;
const board = {
name: payload.name,
isActive,
columns: [],
};
board.columns = payload.newColumns;
state.push(board);
},
editBoard: (state, action) => {
const payload = action.payload;
const board = state.find((board) => board.isActive);
board.name = payload.name;
board.columns = payload.newColumns;
},
deleteBoard: (state) => {
const board = state.find((board) => board.isActive);
state.splice(state.indexOf(board), 1);
},
setBoardActive: (state, action) => {
state.map((board, index) => {
index === action.payload.index
? (board.isActive = true)
: (board.isActive = false);
return board;
});
},
addTask: (state, action) => {
const { title, status, description, subtasks, newColIndex } =
action.payload;
const task = { title, description, subtasks, status };
const board = state.find((board) => board.isActive);
const column = board.columns.find((col, index) => index === newColIndex);
column.tasks.push(task);
},
editTask: (state, action) => {
const {
title,
status,
description,
subtasks,
prevColIndex,
newColIndex,
taskIndex,
} = action.payload;
const board = state.find((board) => board.isActive);
const column = board.columns.find((col, index) => index === prevColIndex);
const task = column.tasks.find((task, index) => index === taskIndex);
task.title = title;
task.status = status;
task.description = description;
task.subtasks = subtasks;
if (prevColIndex === newColIndex) return;
column.tasks = column.tasks.filter((task, index) => index !== taskIndex);
const newCol = board.columns.find((col, index) => index === newColIndex);
newCol.tasks.push(task);
},
dragTask: (state, action) => {
const { colIndex, prevColIndex, taskIndex } = action.payload;
const board = state.find((board) => board.isActive);
const prevCol = board.columns.find((col, i) => i === prevColIndex);
const task = prevCol.tasks.splice(taskIndex, 1)[0];
board.columns.find((col, i) => i === colIndex).tasks.push(task);
},
setSubtaskCompleted: (state, action) => {
const payload = action.payload;
const board = state.find((board) => board.isActive);
const col = board.columns.find((col, i) => i === payload.colIndex);
const task = col.tasks.find((task, i) => i === payload.taskIndex);
const subtask = task.subtasks.find((subtask, i) => i === payload.index);
subtask.isCompleted = !subtask.isCompleted;
},
setTaskStatus: (state, action) => {
const payload = action.payload;
const board = state.find((board) => board.isActive);
const columns = board.columns;
const col = columns.find((col, i) => i === payload.colIndex);
if (payload.colIndex === payload.newColIndex) return;
const task = col.tasks.find((task, i) => i === payload.taskIndex);
task.status = payload.status;
col.tasks = col.tasks.filter((task, i) => i !== payload.taskIndex);
const newCol = columns.find((col, i) => i === payload.newColIndex);
newCol.tasks.push(task);
},
deleteTask: (state, action) => {
const payload = action.payload;
const board = state.find((board) => board.isActive);
const col = board.columns.find((col, i) => i === payload.colIndex);
col.tasks = col.tasks.filter((task, i) => i !== payload.taskIndex);
},
},
});
export default boardsSlice;
import { configureStore } from "@reduxjs/toolkit";
import boardsSlice from "./boardsSlice";
const store = configureStore({
reducer: {
boards: boardsSlice.reducer,
}
})
export default store
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{js,jsx,ts,tsx}"],
darkMode: "class",
theme: {
extend: {},
},
plugins: [require("tailwind-scrollbar-hide")],
};
const path = require('path');
module.exports = {
entry: './src/index.js', // Replace with the path to your entry JavaScript file
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'), // Output directory
},
resolve: {
fallback: {
"http": require.resolve("stream-http"),
"https": require.resolve("https-browserify"),
"util": require.resolve("util"),
"zlib": require.resolve("browserify-zlib"),
"stream": require.resolve("stream-browserify"),
"assert": require.resolve("assert/"),
"url": require.resolve("url/")
}
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader', // You may need to install babel-loader if you haven't already.
options: {
presets: ['@babel/preset-env'],
},
},
},
],
},
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment