├── public
├── _redirects
├── favicon.ico
├── robots.txt
├── manifest.json
└── index.html
├── src
├── styles
│ ├── plan.png
│ ├── Circular.ttf
│ ├── index.css
│ └── custom.css
├── index.js
├── components
│ ├── Modal.jsx
│ ├── ChecklistProgress.jsx
│ ├── Task.jsx
│ ├── Checklist.jsx
│ ├── BoardList.jsx
│ ├── Column.jsx
│ └── Icons.jsx
├── firebase
│ └── fbConfig.js
├── hooks
│ ├── useBoards.js
│ ├── useAuth.js
│ └── useKanbanData.js
├── App.js
├── screens
│ ├── Login.jsx
│ ├── Home.jsx
│ ├── AddTask.jsx
│ ├── TaskDetails.jsx
│ └── Kanban.jsx
└── utils.js
├── screenshots
├── details.png
├── kanban.png
├── landing.png
└── board-list.png
├── craco.config.js
├── .gitignore
├── tailwind.config.js
├── LICENSE
├── package.json
└── README.md
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drkPrince/agilix/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/styles/plan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drkPrince/agilix/HEAD/src/styles/plan.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/screenshots/details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drkPrince/agilix/HEAD/screenshots/details.png
--------------------------------------------------------------------------------
/screenshots/kanban.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drkPrince/agilix/HEAD/screenshots/kanban.png
--------------------------------------------------------------------------------
/screenshots/landing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drkPrince/agilix/HEAD/screenshots/landing.png
--------------------------------------------------------------------------------
/src/styles/Circular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drkPrince/agilix/HEAD/src/styles/Circular.ttf
--------------------------------------------------------------------------------
/screenshots/board-list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drkPrince/agilix/HEAD/screenshots/board-list.png
--------------------------------------------------------------------------------
/craco.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | style: {
3 | postcss: {
4 | plugins: [
5 | require('tailwindcss'),
6 | require('autoprefixer'),
7 | ],
8 | },
9 | },
10 | }
--------------------------------------------------------------------------------
/src/styles/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer components {
6 | .select {
7 | @apply px-1 py-2 mr-3 outline-none bg-gray-300 rounded-sm hover:bg-gray-400;
8 | }
9 |
10 | .option {
11 | @apply bg-gray-200 outline-none my-6 border-none py-3;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './styles/index.css';
4 | import './styles/custom.css';
5 | import App from './App';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Agilix",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .env
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
3 | darkMode: false, // or 'media' or 'class'
4 | theme: {
5 | extend: {
6 | colors: {
7 | primary: '#5222d0',
8 | secondary: '#ec615b'
9 | },
10 | gridAutoColumns: {
11 | '270': '270px',
12 | '220': '220px',
13 | }
14 | },
15 | },
16 | variants: {
17 | extend: {},
18 | },
19 | plugins: [require('@tailwindcss/typography')],
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/Modal.jsx:
--------------------------------------------------------------------------------
1 | import { Dialog } from "@reach/dialog"
2 | import { Cross } from './Icons'
3 |
4 | const Modal = ({ modal, setModal, children, ariaText }) => {
5 |
6 | return (
7 |
17 | )
18 | }
19 |
20 | export default Modal
--------------------------------------------------------------------------------
/src/components/ChecklistProgress.jsx:
--------------------------------------------------------------------------------
1 | import {CheckedOutline} from './Icons'
2 |
3 | const ChecklistProgress = ({todos}) => {
4 | const tasksCompleted = todos.filter(todo => todo.done === true)
5 |
6 | return (
7 |
8 |
9 |
10 |
{`${tasksCompleted.length}/${todos.length}`}
11 |
12 |
13 |
14 | )
15 | }
16 |
17 | export default ChecklistProgress
--------------------------------------------------------------------------------
/src/firebase/fbConfig.js:
--------------------------------------------------------------------------------
1 | import firebase from 'firebase/app'
2 | import 'firebase/firestore'
3 | import 'firebase/auth'
4 |
5 |
6 | const APIKEY = process.env.REACT_APP_APIKEY
7 |
8 | const firebaseConfig =
9 | {
10 | apiKey: APIKEY,
11 | authDomain: "kanban-42358.firebaseapp.com",
12 | projectId: "kanban-42358",
13 | storageBucket: "kanban-42358.appspot.com",
14 | messagingSenderId: "300388039581",
15 | appId: "1:300388039581:web:dc35b313665b4c310d8d74",
16 | measurementId: "G-KCLK585LB9"
17 | };
18 |
19 | // Initialize Firebase
20 | firebase.initializeApp(firebaseConfig)
21 |
22 |
23 | const db = firebase.firestore()
24 |
25 | // firebase.firestore().enablePersistence()
26 |
27 | export {firebase, db}
28 |
--------------------------------------------------------------------------------
/src/hooks/useBoards.js:
--------------------------------------------------------------------------------
1 | import {useState, useEffect} from 'react'
2 | import {db} from '../firebase/fbConfig'
3 |
4 | const useBoards = (userId) => {
5 | const [boards, setBoards] = useState(null)
6 |
7 | useEffect(() => {
8 | return db.collection(`users`).doc(userId).get()
9 | .then(doc => {
10 | try {
11 | if(doc){
12 | return db.collection(`users/${doc.id}/boards`).onSnapshot(snap => {
13 | const documents = []
14 | snap.forEach(doc => documents.push({id: doc.id, ...doc.data()}))
15 | setBoards(documents)
16 | })
17 | }
18 | else return
19 | }
20 |
21 | catch(e) {
22 | console.log(e)
23 | }
24 | })
25 | }, [userId])
26 |
27 |
28 | return boards
29 | }
30 |
31 | export default useBoards
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Prince Kumar Singh
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | import useAuth from './hooks/useAuth'
4 | import Home from './screens/Home'
5 | import Login from './screens/Login'
6 |
7 |
8 | const App = () =>
9 | {
10 |
11 | const [user, loginWithGoogle, logOut, error, anon] = useAuth()
12 |
13 | if(navigator.onLine !== true)
14 | {
15 | return (
16 |
17 |
18 |
The network is disconnected. Connect and try again
19 |
20 |
21 | )
22 | }
23 |
24 | //error while logging in
25 | if (error)
26 | return (
27 |
28 |
{error}
29 |
30 |
31 | )
32 |
33 |
34 | //Not logged in
35 | if (user === false) {
36 | return
37 | }
38 |
39 | //state of loading
40 | if (user === null) {
41 | return
42 | }
43 |
44 | //logged in
45 | else return
46 | }
47 |
48 |
49 |
50 | export default App;
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "agility",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@craco/craco": "^6.1.1",
7 | "@reach/dialog": "^0.13.2",
8 | "@tailwindcss/typography": "^0.4.0",
9 | "@testing-library/jest-dom": "^5.11.4",
10 | "@testing-library/react": "^11.1.0",
11 | "@testing-library/user-event": "^12.1.10",
12 | "firebase": "^8.3.0",
13 | "react": "^17.0.1",
14 | "react-beautiful-dnd": "^13.0.0",
15 | "react-dom": "^17.0.1",
16 | "react-markdown": "^5.0.3",
17 | "react-router-dom": "^5.2.0",
18 | "react-scripts": "4.0.3",
19 | "remark-gfm": "^1.0.0",
20 | "uuid": "^8.3.2",
21 | "web-vitals": "^1.0.1"
22 | },
23 | "scripts": {
24 | "start": "craco start",
25 | "build": "craco build",
26 | "test": "craco test",
27 | "eject": "react-scripts eject"
28 | },
29 | "eslintConfig": {
30 | "extends": [
31 | "react-app",
32 | "react-app/jest"
33 | ]
34 | },
35 | "browserslist": {
36 | "production": [
37 | ">0.2%",
38 | "not dead",
39 | "not op_mini all"
40 | ],
41 | "development": [
42 | "last 1 chrome version",
43 | "last 1 firefox version",
44 | "last 1 safari version"
45 | ]
46 | },
47 | "devDependencies": {
48 | "@tailwindcss/postcss7-compat": "^2.0.3",
49 | "autoprefixer": "^9",
50 | "postcss": "^7",
51 | "tailwindcss": "npm:@tailwindcss/postcss7-compat"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/screens/Login.jsx:
--------------------------------------------------------------------------------
1 |
2 |
3 | const Login = ({loginWithGoogle, signInAnon}) =>
4 | {
5 | return (
6 | <>
7 |
8 |
9 |
Stay on top of the game called life with Agilix.
10 |
Agilix is an opinionated, simplified Kanban planner for personal use that helps you organise your life and accomplish more.
11 |
12 |
13 |
14 |
15 |
* Your data will be deleted once you log out.
16 |
17 |
18 |
.default})
19 |
20 |
21 | >
22 | )
23 | }
24 |
25 | export default Login
--------------------------------------------------------------------------------
/src/hooks/useAuth.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { firebase, db } from '../firebase/fbConfig'
3 |
4 | import {createBoardForAnons} from '../utils'
5 |
6 | const useAuth = () =>
7 | {
8 | const [user, setUser] = useState(null)
9 | const [error, setError] = useState(null)
10 |
11 | const loginWithGoogle = async () => {
12 | try {
13 | const provider = new firebase.auth.GoogleAuthProvider()
14 | await firebase.auth().signInWithRedirect(provider)
15 | setError(null)
16 | } catch (err) {
17 | console.log(err)
18 | setError(err.message)
19 | }
20 | }
21 |
22 | const loginAnonymously = () => {
23 | firebase.auth().signInAnonymously()
24 | .then((user) => {
25 | console.log('Welcome Anon')
26 | createBoardForAnons(user.user.uid)
27 | })
28 | }
29 |
30 | const logOut = () => {
31 | firebase.auth().signOut()
32 | }
33 |
34 | useEffect(() => {
35 | return firebase.auth().onAuthStateChanged(user => {
36 | if (user) {
37 | setUser(user)
38 | db.collection('users')
39 | .doc(user.uid)
40 | .set({ id: user.uid, name: user.displayName, email: user.email }, { merge: true })
41 | } else setUser(false)
42 | })
43 | }, [user])
44 |
45 | return [user, loginWithGoogle, logOut, error, loginAnonymously]
46 | }
47 |
48 |
49 | export default useAuth
--------------------------------------------------------------------------------
/src/screens/Home.jsx:
--------------------------------------------------------------------------------
1 |
2 | import {db} from '../firebase/fbConfig'
3 | import {BrowserRouter, Route} from 'react-router-dom'
4 | import useBoards from '../hooks/useBoards'
5 |
6 | import BoardList from '../components/BoardList'
7 |
8 | import Kanban from './Kanban'
9 |
10 | import {v4 as uuidv4} from 'uuid';
11 |
12 |
13 | const Home = ({logOut, userId, loginWithGoogle, name, isAnon}) =>
14 | {
15 |
16 | const boards = useBoards(userId)
17 |
18 | const addNewBoard = (e) => {
19 | e.preventDefault()
20 | const uid = uuidv4()
21 |
22 | db.collection(`users/${userId}/boards`)
23 | .doc(uid)
24 | .set({name: e.target.elements.boardName.value})
25 |
26 | const columnOrder = {id: 'columnOrder', order: []}
27 |
28 | db.collection(`users/${userId}/boards/${uid}/columns`)
29 | .doc('columnOrder')
30 | .set(columnOrder)
31 |
32 | e.target.elements.boardName.value = ''
33 |
34 | }
35 |
36 | const deleteBoard = (id) => {
37 | db.collection(`users/${userId}/boards`)
38 | .doc(id)
39 | .delete()
40 | }
41 |
42 | return boards !== null ? (
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | ) :
55 | }
56 |
57 | export default Home
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
16 |
17 |
26 | Agilix
27 |
28 |
29 |
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🚀 Agilix - A simplified Kanban Planner.
2 |
3 | 
4 | 
5 | 
6 | 
7 | 
8 | [](https://app.netlify.com/sites/agilix/deploys)
9 |
10 | Agilix is an agile planner web app made with **React, Firebase, React Beautiful DND, TailwindCSS and React Markdown**. It is heavily inspired by Trello.
11 |
12 | [Checkout the website](http://agilix.netlify.app). If you like it, leave a 🌟 because it keeps a beginner motivated. 😊
13 |
14 | ## Features
15 | - Add multiple boards.
16 | - Google sign-in.
17 | - Ability to try it out with Guest mode.
18 | - Reorderable tasks and columns.
19 | - Add subtasks to tasks.
20 | - Reorderable subtasks.
21 | - Write Descriptions using Markdown.
22 | - Fully Responsive on mobile screens.
23 |
24 | ## Screenshots
25 |
26 | ### Landing Page
27 |
28 |
29 |
30 | ### After the user logs in
31 |
32 |
33 |
34 | ### The Kanban Board
35 |
36 |
37 |
38 | ### Task Details
39 |
40 |
41 |
42 |
43 | ## Todo
44 |
45 | - Implement WIP limitation.
46 | - Dark Mode.
47 |
48 |
49 | ## Author
50 | [Prince Kumar Singh](http://twitter.com/drkPrns)
51 |
52 |
--------------------------------------------------------------------------------
/src/hooks/useKanbanData.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import { db } from '../firebase/fbConfig'
3 |
4 | const useKanban = (userId, boardId) => {
5 | const [tasks, setTasks] = useState(null)
6 | const [columns, setColumns] = useState(null)
7 | const [final, setFinal] = useState(null)
8 | const [boardName, setBoardName] = useState('')
9 |
10 |
11 | useEffect(() => {
12 | return db.collection(`users/${userId}/boards/${boardId}/tasks`)
13 | .onSnapshot(snap => {
14 | const documents = []
15 | snap.forEach(d => {
16 | documents.push({ id: d.id, ...d.data() })
17 | })
18 | setTasks(documents)
19 | })
20 | }, [userId, boardId])
21 |
22 |
23 | useEffect(() => {
24 | return db.collection(`users/${userId}/boards`)
25 | .doc(boardId)
26 | .get()
27 | .then(d => setBoardName(d.data().name))
28 | }, [userId, boardId])
29 |
30 |
31 | useEffect(() => {
32 | return db.collection(`users/${userId}/boards/${boardId}/columns`)
33 | .onSnapshot(snap => {
34 | const documents = []
35 | snap.forEach(d => {
36 | documents.push({ id: d.id, ...d.data() })
37 | })
38 | setColumns(documents)
39 | })
40 | }, [userId, boardId])
41 |
42 |
43 | useEffect(() => {
44 | if (tasks && columns) {
45 | const finalObject = {}
46 |
47 | const co = columns.find(c => c.id === 'columnOrder')
48 | const cols = columns.filter(c => c.id !== 'columnOrder')
49 |
50 | finalObject.columnOrder = co?.order
51 | finalObject.columns = {}
52 | finalObject.tasks = {}
53 |
54 | tasks.forEach(t => finalObject.tasks[t.id] = t)
55 | cols.forEach(c => finalObject.columns[c.id] = c)
56 |
57 | setFinal(finalObject)
58 | }
59 | }, [tasks, columns])
60 |
61 |
62 | return { initialData: final, setInitialData: setFinal, boardName }
63 |
64 | }
65 |
66 | export default useKanban
--------------------------------------------------------------------------------
/src/components/Task.jsx:
--------------------------------------------------------------------------------
1 |
2 | import { Draggable } from 'react-beautiful-dnd'
3 | import ChecklistProgress from './ChecklistProgress'
4 | import { extractPriority } from '../utils'
5 |
6 | import Modal from './Modal'
7 | import TaskDetails from '../screens/TaskDetails'
8 | import {Description} from './Icons'
9 | import {useState} from 'react'
10 |
11 |
12 | const Task = ({ allData, id, index, boardId, userId, columnDetails, filterBy }) => {
13 |
14 |
15 | const [modal, setModal] = useState(false)
16 |
17 | const theTask = allData.tasks[id]
18 |
19 | let matched = ''
20 |
21 | if (filterBy === null) {
22 | matched = 'all'
23 | } else {
24 | matched = theTask.priority === filterBy
25 | }
26 |
27 | return (
28 |
29 |
30 |
31 | setModal(false)} boardId={boardId} userId={userId} columnDetails={columnDetails} />
32 |
33 |
34 |
35 | {(provided, snapshot) =>
36 | setModal(true)} {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef} className={`shadow-lg transition-shadow duration-300 hover:shadow-xl mb-4 rounded px-1.5 py-2.5 ${snapshot.isDragging ? 'bg-gradient-to-r from-red-100 to-blue-100 text-gray-900' : 'bg-white text-gray-800'}`}>
37 |
38 |
{theTask.title}
39 |
40 | {extractPriority(theTask.priority)}
41 | {theTask.todos.length >= 1 && }
42 | {(theTask.description !== null && theTask.description?.length > 1) ? : null }
43 |
44 |
45 |
46 | }
47 |
48 |
49 |
50 | )
51 | }
52 |
53 |
54 |
55 | export default Task
--------------------------------------------------------------------------------
/src/screens/AddTask.jsx:
--------------------------------------------------------------------------------
1 |
2 | import {useState} from 'react'
3 | import {db, firebase} from '../firebase/fbConfig'
4 | import {v4 as uuidv4} from 'uuid';
5 |
6 | const AddTask = ({boardId, userId, close, allCols}) =>
7 | {
8 | const [description, setDescription] = useState(null)
9 |
10 | const addTask = (e) => {
11 | e.preventDefault()
12 |
13 | const uid = uuidv4()
14 | const title = e.target.elements.newTaskTitle.value
15 | const priority = e.target.elements.priority.value
16 | const column = e.target.elements.column.value
17 |
18 | db.collection(`users/${userId}/boards/${boardId}/tasks`)
19 | .doc(uid)
20 | .set({title, priority , description, todos: [], dateAdded: firebase.firestore.FieldValue.serverTimestamp() })
21 |
22 | db.collection(`users/${userId}/boards/${boardId}/columns`)
23 | .doc(column)
24 | .update({taskIds: firebase.firestore.FieldValue.arrayUnion(uid)})
25 |
26 | close()
27 | }
28 |
29 |
30 | return (
31 |
71 | )
72 | }
73 |
74 | export default AddTask
--------------------------------------------------------------------------------
/src/components/Checklist.jsx:
--------------------------------------------------------------------------------
1 |
2 |
3 | import {DragDropContext, Droppable, Draggable} from 'react-beautiful-dnd'
4 | import {Checked, Unchecked, Cross, Dragger} from './Icons'
5 | import {firebase, db} from '../firebase/fbConfig'
6 | import {useState, useRef} from 'react'
7 | import {v4 as uuidv4} from 'uuid';
8 |
9 |
10 | const Checklist = ({todos, taskId, boardId, userId}) => {
11 |
12 | const [todoList, setList] = useState(todos)
13 | const newTaskRef = useRef(null)
14 |
15 | const addSubTask = (e) => {
16 | if(e.key === 'Enter' && e.target.value !== ''){
17 | const uid = uuidv4()
18 | setList([...todoList, {id: uid, task: e.target.value, done: false}])
19 | db.collection(`users/${userId}/boards/${boardId}/tasks`)
20 | .doc(taskId)
21 | .update({todos: firebase.firestore.FieldValue.arrayUnion({id: uid, task: e.target.value, done: false})})
22 | newTaskRef.current.value = ''
23 | }
24 | }
25 |
26 | const checkMark = (e, todo) => {
27 | const toBeChanged = todoList.filter(t => t.task === todo.task)[0]
28 | const rest = todoList.filter(t => t.task !== todo.task)
29 | toBeChanged.done = !toBeChanged.done
30 |
31 | db.collection(`users/${userId}/boards/${boardId}/tasks`)
32 | .doc(taskId)
33 | .update({todos: [...rest, toBeChanged]})
34 | }
35 |
36 | const deleteSubTask = (taskName) => {
37 | const filtered = todoList.filter(t => t.task !== taskName)
38 | setList(filtered)
39 | db.collection(`users/${userId}/boards/${boardId}/tasks`)
40 | .doc(taskId)
41 | .update({todos: filtered})
42 | }
43 |
44 | const endOfDrag = (result) => {
45 | const {destination, source} = result
46 | if(!destination) return
47 | const toBeMoved = todoList[source.index]
48 | const newOrder = [...todoList]
49 | newOrder.splice(source.index, 1)
50 | newOrder.splice(destination.index, 0, toBeMoved)
51 | setList(newOrder)
52 | db.collection(`users/${userId}/boards/${boardId}/tasks`)
53 | .doc(taskId)
54 | .update({todos: newOrder})
55 | }
56 |
57 |
58 | return (
59 |
60 |
61 |
62 | { (provided, snapshot) =>
63 |
64 | {todoList.map((t, i) =>
65 |
66 | {(provided, snapshot) =>
67 |
68 |
69 |
checkMark(e, t)} >
70 | {t.done ? < Checked/> : }
71 |
72 |
{t.task}
73 |
74 |
deleteSubTask(t.task)}>
75 |
76 |
77 |
78 |
79 |
80 |
81 | }
82 |
83 | )}
84 | {provided.placeholder}
85 |
86 | }
87 |
88 |
89 |
90 |
91 | )
92 | }
93 |
94 | export default Checklist
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 |
2 | import { db } from './firebase/fbConfig'
3 |
4 | import {Low, Medium, High} from './components/Icons'
5 |
6 | export const extractPriority = (priority) => {
7 | switch(priority)
8 | {
9 | case 'low':
10 | {
11 | return
12 | }
13 |
14 | case 'medium':
15 | {
16 | return
17 | }
18 |
19 | case 'high':
20 | {
21 | return
22 | }
23 |
24 | default: return null
25 | }
26 | }
27 |
28 |
29 |
30 | export const debounce = (callback, wait) => {
31 | let timeoutId = null;
32 | return (...args) => {
33 | window.clearTimeout(timeoutId);
34 | timeoutId = window.setTimeout(() => {
35 | callback.apply(null, args);
36 | }, wait);
37 | }
38 | }
39 |
40 |
41 | export const createBoardForAnons = (userId) => {
42 | const tasks = [
43 | {
44 | id: '1',
45 | title: 'Welcome to Agilix 🙌',
46 | description: 'Agilix is a Kanban planner that helps you to focus on what matters most.',
47 | priority: 'low',
48 | dateAdded: new Date(),
49 | todos: []
50 | },
51 |
52 | {
53 | id: '2',
54 | title: 'You can add detailed Descriptions.',
55 | description: '## Agilix supports Markdown too!\n- Agilix fully supports Github flavoured Markdown.\n- You can do **bold** and *italic*.\n ```\n You can write code too!\n```\n>Pretend this is a great quote.\nTo learn more about Markdown, visit [here](https://commonmark.org/help/).',
56 | priority: 'high',
57 | dateAdded: new Date(),
58 | todos: []
59 | },
60 |
61 |
62 | {
63 | id: '3',
64 | title: 'Try rearranging tasks and columns',
65 | description: null,
66 | priority: 'high',
67 | dateAdded: new Date(),
68 | todos: []
69 | },
70 |
71 |
72 | {
73 | id: '4',
74 | title: 'Breakdown big tasks into small actionable steps.',
75 | description: 'Remember to make these steps actionable, achievable and small.',
76 | priority: 'medium',
77 | dateAdded: new Date(),
78 | todos: [{id: 1, task: 'First subtask', done: false}, {id: 3, task: 'And another', done: true}, {id: 2, task: 'You can reorder these too!', done: false}]
79 | },
80 |
81 |
82 | {
83 | id: "5",
84 | title: "There are three levels of priority",
85 | priority: "low",
86 | todos:[],
87 | description :"- High\n- Medium\n- Low"
88 | },
89 |
90 | {
91 | id: "6",
92 | title: "Do you like it? 😊",
93 | priority: "medium",
94 | todos:[],
95 | description :"### Tell me your suggestions, feedback or anything at all!\n[This](http://github.com/drkPrince/agilix) is the link to the Github repo. Drop a 🌟 if you like it. \n**Keep a beginner motivated**."
96 | },
97 |
98 | {
99 | id: '7',
100 | title: 'Try changing board and Column names now.',
101 | priority: 'low',
102 | todos: [],
103 | description: ''
104 | }
105 |
106 |
107 | ]
108 |
109 | const columns = [
110 | {title: 'Backlog', taskIds: ['1', '2']},
111 | {title: 'In Progress', taskIds: ['3', '5', '7']},
112 | {title: 'Done', taskIds: ['6']},
113 | {title: 'Waiting', taskIds: ['4']}
114 | ]
115 |
116 | const columnOrder = {id: 'columnOrder', order: ['Backlog', 'Waiting', 'In Progress', 'Done']}
117 |
118 | db.collection(`users/${userId}/boards/first/columns`)
119 | .doc('columnOrder')
120 | .set(columnOrder)
121 |
122 | db.collection(`users/${userId}/boards`)
123 | .doc('first')
124 | .set({name: 'Main Board'})
125 |
126 | columns.forEach(c => {
127 | db.collection(`users/${userId}/boards/first/columns`)
128 | .doc(c.title)
129 | .set({title: c.title, taskIds: c.taskIds})
130 | })
131 |
132 | tasks.forEach(t => {
133 | db.collection(`users/${userId}/boards/first/tasks`)
134 | .doc(t.id)
135 | .set(t)
136 | })
137 | }
138 |
--------------------------------------------------------------------------------
/src/components/BoardList.jsx:
--------------------------------------------------------------------------------
1 |
2 | import { Link } from 'react-router-dom'
3 | import {useState} from 'react'
4 |
5 | import Modal from './Modal'
6 | import {Exclaim, Bin} from './Icons'
7 |
8 | const BoardList = ({ logOut, boards, addNewBoard, deleteBoard, name }) =>
9 | {
10 | const [modal, setModal] = useState(false)
11 | const [idToBeDeleted, setId] = useState(null)
12 |
13 | const removeBoard = (id) => {
14 | setModal(false)
15 | deleteBoard(id)
16 | }
17 |
18 | const openDeleteModal = (id) => {
19 | setId(id)
20 | setModal(true)
21 | }
22 |
23 | if(navigator.onLine !== true)
24 | {
25 | return (
26 |
27 |
28 |
The network is disconnected. Connect and try again
29 |
30 |
31 | )
32 | }
33 |
34 |
35 |
36 | else return (
37 |
38 |
39 |
40 |
41 |
42 |
43 |
Are you sure you want to delete this Board?
44 |
All of it's data will be permanently deleted and it cannot be undone.
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
Welcome, {name ? name.split(' ')[0] : 'Stranger'}
54 |
55 |
56 |
57 |
Your Boards
58 |
59 | {boards.map(b =>
60 |
61 |
62 |
{b.name}
63 |
openDeleteModal(b.id)} className='text-red-500 ml-6 cursor-pointer hover:text-red-700'>
64 |
65 |
66 |
67 |
68 | )}
69 | {boards.length === 0 ?
No Boards created yet. Why don't you go ahead and create one?
: null}
70 |
71 |
72 |
73 |
80 |
81 | )
82 | }
83 |
84 | export default BoardList
--------------------------------------------------------------------------------
/src/components/Column.jsx:
--------------------------------------------------------------------------------
1 | import { Droppable, Draggable } from 'react-beautiful-dnd'
2 | import Task from './Task'
3 |
4 | import {Bin, Exclaim} from './Icons'
5 |
6 | import {db, firebase} from '../firebase/fbConfig'
7 | import {debounce} from '../utils'
8 | import {useState, useRef} from 'react'
9 | import Modal from './Modal'
10 |
11 |
12 | const Column = ({ column, tasks, allData, boardId, userId, filterBy, index }) => {
13 |
14 | const [modal, setModal] = useState(false)
15 | const [editingCol, setEditing] = useState(false)
16 | const colInput = useRef(null)
17 |
18 | const deleteCol = (colId, tasks) => {
19 | db.collection(`users/${userId}/boards/${boardId}/columns`)
20 | .doc('columnOrder')
21 | .update({order: firebase.firestore.FieldValue.arrayRemove(colId)})
22 |
23 | db.collection(`users/${userId}/boards/${boardId}/columns`)
24 | .doc(colId)
25 | .delete()
26 |
27 | //Extract and delete its tasks
28 | tasks.forEach(t => {
29 | db.collection(`users/${userId}/boards/${boardId}/tasks`)
30 | .doc(t)
31 | .delete()
32 | })
33 | }
34 |
35 | const changeColName = debounce((e, colId) => {
36 | db.collection(`users/${userId}/boards/${boardId}/columns`)
37 | .doc(colId)
38 | .update({title: e.target.value})
39 | }, 7000)
40 |
41 | const moveToInp = () => {
42 | setEditing(true)
43 | setTimeout(()=>{
44 | colInput.current.focus()
45 | }, 50)
46 | }
47 |
48 |
49 | return (
50 | <>
51 |
52 | {provided =>
53 |
54 |
55 |
56 |
setEditing(false)} type="text" defaultValue={column.title} onChange={(e)=>changeColName(e, column.id)} />
57 |
{column.title}
58 |
setModal(true)}>
59 |
60 |
61 |
62 |
63 | {(provided, snapshot) =>
64 |
65 | {tasks.map((t, i) => )}
66 | {provided.placeholder}
67 |
68 | }
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
Are you sure you want to delete this column?
77 |
This column and its tasks will be permanently deleted and it cannot be undone.
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | }
86 |
87 | >
88 | )
89 | }
90 |
91 | export default Column
92 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/src/styles/custom.css:
--------------------------------------------------------------------------------
1 | @font-face{
2 | font-family: 'Circular';
3 | src: url('./Circular.ttf');
4 | }
5 |
6 | body{
7 | font-family: 'Circular';
8 | }
9 |
10 | progress {
11 | height: 4px;
12 | border-radius: 3px;
13 | }
14 |
15 | progress::-webkit-progress-bar {
16 | background-color: rgba( 0, 0, 0, 0.18 );
17 | border-radius: 7px;
18 | }
19 |
20 | progress::-webkit-progress-value {
21 | background-color: #2d69e8;
22 | border-radius: 2px;
23 | }
24 |
25 | progress::-moz-progress-bar {
26 | /* style rules */
27 | }
28 |
29 |
30 | *::-webkit-scrollbar {
31 | width: 10px; /* width of the entire scrollbar */
32 | }
33 |
34 | *::-webkit-scrollbar-track {
35 | background: white; /* color of the tracking area */
36 | }
37 |
38 | *::-webkit-scrollbar-thumb {
39 | background-color: rgba( 0, 0, 0, 0.15 ) ; /* color of the scroll thumb */
40 | border-radius: 20px; /* roundness of the scroll thumb */
41 | border: 2px solid white; /* creates padding around scroll thumb */
42 | }
43 |
44 |
45 |
46 | /* Reach UI Modal */
47 |
48 | :root {
49 | --reach-dialog: 1;
50 | }
51 |
52 | [data-reach-dialog-overlay] {
53 | background: hsla(0, 0%, 0%, 0.33);
54 | position: fixed;
55 | top: 0;
56 | right: 0;
57 | bottom: 0;
58 | left: 0;
59 | overflow: auto;
60 | z-index: 40;
61 | }
62 |
63 | @keyframes fade-in {
64 | 0% {
65 | opacity: 0;
66 | transform: translateY(-10px);
67 | }
68 |
69 | 100% {
70 | opacity: 1;
71 | transform: translateY(0px);
72 | }
73 | }
74 |
75 | .fade-in {
76 | animation: fade-in 0.2s ease-out;
77 | }
78 |
79 | [data-reach-dialog-content] {
80 | width: 85vw;
81 | margin: 10vh auto;
82 | background: white;
83 | padding: 1.5rem;
84 | outline: none;
85 | box-shadow: 0 8px 32px 0 rgba( 31, 38, 135, 0.37 );
86 |
87 | }
88 |
89 |
90 |
91 | .spinner {
92 | color: #5222d0;
93 | height: 100vh;
94 | display: flex;
95 | justify-content: center;
96 | align-items: center;
97 | font-size: 70px;
98 | text-indent: -9999em;
99 | overflow: hidden;
100 | width: 1em;
101 | height: 1em;
102 | border-radius: 50%;
103 | margin: 72px auto;
104 | margin-top: 200px;
105 | position: relative;
106 | -webkit-transform: translateZ(0);
107 | -ms-transform: translateZ(0);
108 | transform: translateZ(0);
109 | -webkit-animation: load6 1.7s infinite ease, round 1.7s infinite ease;
110 | animation: load6 1.7s infinite ease, round 1.7s infinite ease;
111 | }
112 |
113 |
114 | @-webkit-keyframes load6 {
115 | 0% {
116 | box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
117 | }
118 | 5%,
119 | 95% {
120 | box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
121 | }
122 | 10%,
123 | 59% {
124 | box-shadow: 0 -0.83em 0 -0.4em, -0.087em -0.825em 0 -0.42em, -0.173em -0.812em 0 -0.44em, -0.256em -0.789em 0 -0.46em, -0.297em -0.775em 0 -0.477em;
125 | }
126 | 20% {
127 | box-shadow: 0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em, -0.555em -0.617em 0 -0.44em, -0.671em -0.488em 0 -0.46em, -0.749em -0.34em 0 -0.477em;
128 | }
129 | 38% {
130 | box-shadow: 0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em, -0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em, -0.82em -0.09em 0 -0.477em;
131 | }
132 | 100% {
133 | box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
134 | }
135 | }
136 | @keyframes load6 {
137 | 0% {
138 | box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
139 | }
140 | 5%,
141 | 95% {
142 | box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
143 | }
144 | 10%,
145 | 59% {
146 | box-shadow: 0 -0.83em 0 -0.4em, -0.087em -0.825em 0 -0.42em, -0.173em -0.812em 0 -0.44em, -0.256em -0.789em 0 -0.46em, -0.297em -0.775em 0 -0.477em;
147 | }
148 | 20% {
149 | box-shadow: 0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em, -0.555em -0.617em 0 -0.44em, -0.671em -0.488em 0 -0.46em, -0.749em -0.34em 0 -0.477em;
150 | }
151 | 38% {
152 | box-shadow: 0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em, -0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em, -0.82em -0.09em 0 -0.477em;
153 | }
154 | 100% {
155 | box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em;
156 | }
157 | }
158 | @-webkit-keyframes round {
159 | 0% {
160 | -webkit-transform: rotate(0deg);
161 | transform: rotate(0deg);
162 | }
163 | 100% {
164 | -webkit-transform: rotate(360deg);
165 | transform: rotate(360deg);
166 | }
167 | }
168 | @keyframes round {
169 | 0% {
170 | -webkit-transform: rotate(0deg);
171 | transform: rotate(0deg);
172 | }
173 | 100% {
174 | -webkit-transform: rotate(360deg);
175 | transform: rotate(360deg);
176 | }
177 | }
178 |
179 |
180 |
181 |
182 |
--------------------------------------------------------------------------------
/src/components/Icons.jsx:
--------------------------------------------------------------------------------
1 |
2 | export const Medium = () =>
3 |
4 | export const High = () =>
5 |
6 | export const Low = () =>
7 |
8 |
9 |
10 | export const Unchecked = () =>
11 |
12 | export const Checked = () =>
13 |
14 | export const CheckedOutline = () =>
15 |
16 | export const More = () =>
17 |
18 | export const Exclaim = () =>
19 |
20 |
21 |
22 | export const Add = () =>
23 |
24 | export const Home = () =>
25 |
26 | export const Bin = () =>
27 |
28 |
29 | export const Cross = () =>
30 |
31 | export const Edit = () =>
32 |
33 |
34 |
35 | export const Description = () =>
36 |
37 |
38 | export const Github = () =>
39 |
40 |
41 | export const Save = () =>
42 |
43 |
44 | export const Dragger = () =>
--------------------------------------------------------------------------------
/src/screens/TaskDetails.jsx:
--------------------------------------------------------------------------------
1 | import {useState} from 'react'
2 | import Checklist from '../components/Checklist'
3 | import {db, firebase} from '../firebase/fbConfig'
4 | import {extractPriority} from '../utils'
5 | import ReactMarkdown from 'react-markdown'
6 | import gfm from 'remark-gfm'
7 |
8 | import Modal from '../components/Modal'
9 | import {Exclaim} from '../components/Icons'
10 |
11 |
12 | const TaskDetails = ({taskDetails, boardId, userId, columnDetails, closeModal}) => {
13 |
14 | const [updatedTitle, setTitle] = useState(taskDetails.title)
15 | const [updatedPriority, setPriority] = useState(taskDetails.priority)
16 | const [updatedDesc, setNewDesc] = useState(taskDetails.description)
17 | const [modal, setModal] = useState(false)
18 |
19 | const [editing, setEditing] = useState(false)
20 |
21 | const updateTask = (e) => {
22 | e.preventDefault()
23 | closeModal()
24 | db.collection(`users/${userId}/boards/${boardId}/tasks`)
25 | .doc(taskDetails.id)
26 | .update({title: updatedTitle, priority: updatedPriority, description: updatedDesc})
27 | }
28 |
29 | const deleteTask = (e) => {
30 | setModal(false)
31 | closeModal()
32 | db.collection(`users/${userId}/boards/${boardId}/columns`)
33 | .doc(columnDetails.id)
34 | .update({taskIds: firebase.firestore.FieldValue.arrayRemove(taskDetails.id)})
35 | db.collection(`users/${userId}/boards/${boardId}/tasks`)
36 | .doc(taskDetails.id)
37 | .delete()
38 | }
39 |
40 | return (
41 |
42 |
43 |
44 |
45 |
46 |
47 |
Are you sure you want to delete this task?
48 |
This cannot be undone.
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
150 |
151 |
152 | )
153 | }
154 |
155 |
156 | export default TaskDetails
157 |
158 |
--------------------------------------------------------------------------------
/src/screens/Kanban.jsx:
--------------------------------------------------------------------------------
1 |
2 |
3 | import {useState} from 'react'
4 | import {useParams} from 'react-router-dom'
5 | import {DragDropContext, Droppable} from 'react-beautiful-dnd'
6 |
7 | import {db, firebase} from '../firebase/fbConfig'
8 | import {Link} from 'react-router-dom'
9 | import Column from '../components/Column'
10 | import Modal from '../components/Modal'
11 | import AddTask from '../screens/AddTask'
12 | import {Add, Github} from '../components/Icons'
13 |
14 | import useKanbanData from '../hooks/useKanbanData'
15 | import {debounce} from '../utils'
16 |
17 |
18 | const Kanban = ({userId}) => {
19 |
20 | const {boardId} = useParams()
21 | const [modal, setModal] = useState(false)
22 | const {initialData, setInitialData, boardName} = useKanbanData(userId, boardId)
23 | const [filter, setFilter] = useState(null)
24 | const filters = ['high', 'medium', 'low']
25 |
26 |
27 | const onDragEnd = (result) => {
28 |
29 | const {destination, source, draggableId} = result
30 |
31 | if(!destination) return
32 |
33 | if(result.type === 'task') {
34 |
35 |
36 |
37 | const startColumn = initialData.columns[source.droppableId]
38 | const endColumn = initialData.columns[destination.droppableId]
39 |
40 | if(startColumn === endColumn){
41 | const newTaskIds = Array.from(endColumn.taskIds)
42 |
43 | newTaskIds.splice(source.index, 1)
44 | newTaskIds.splice(destination.index, 0, draggableId)
45 |
46 |
47 | const newColumn = {
48 | ...endColumn, taskIds: newTaskIds
49 | }
50 |
51 | const newState = {
52 | ...initialData,
53 | columns: {...initialData.columns, [endColumn.id]: newColumn}
54 | }
55 |
56 | setInitialData(newState)
57 | db.collection(`users/${userId}/boards/${boardId}/columns`).doc(startColumn.id)
58 | .update({taskIds: newTaskIds})
59 | return
60 | }
61 |
62 |
63 | const startTaskIDs = Array.from(startColumn.taskIds)
64 | startTaskIDs.splice(source.index, 1)
65 | const newStart = {
66 | ...startColumn, taskIds: startTaskIDs
67 | }
68 |
69 |
70 | const finishTaskIDs = Array.from(endColumn.taskIds)
71 | finishTaskIDs.splice(destination.index, 0, draggableId)
72 | const newFinish = {
73 | ...endColumn, taskIds: finishTaskIDs
74 | }
75 |
76 |
77 | const newState = {
78 | ...initialData,
79 | columns: {
80 | ...initialData.columns,
81 | [startColumn.id]: newStart,
82 | [endColumn.id]: newFinish
83 | }
84 | }
85 |
86 | setInitialData(newState)
87 |
88 | db.collection(`users/${userId}/boards/${boardId}/columns`).doc(newStart.id)
89 | .update({taskIds: startTaskIDs})
90 |
91 | db.collection(`users/${userId}/boards/${boardId}/columns`).doc(newFinish.id)
92 | .update({taskIds: finishTaskIDs})
93 | }
94 |
95 | else {
96 | const newColumnOrder = Array.from(initialData.columnOrder)
97 | newColumnOrder.splice(source.index, 1)
98 | newColumnOrder.splice(destination.index, 0, draggableId)
99 | setInitialData({...initialData, columnOrder: newColumnOrder})
100 | db.collection(`users/${userId}/boards/${boardId}/columns`)
101 | .doc('columnOrder')
102 | .update({order: newColumnOrder})
103 | }
104 | }
105 |
106 |
107 | const addCol = (e) => {
108 | e.preventDefault()
109 | const newColumnName = e.target.elements.newCol.value
110 | db.collection(`users/${userId}/boards/${boardId}/columns`)
111 | .doc(newColumnName)
112 | .set({title: newColumnName, taskIds: []})
113 |
114 | db.collection(`users/${userId}/boards/${boardId}/columns`)
115 | .doc('columnOrder')
116 | .update({order: firebase.firestore.FieldValue.arrayUnion(newColumnName)})
117 |
118 | e.target.elements.newCol.value = ''
119 | }
120 |
121 | const changeBoardName = debounce((ev) => {
122 | db.collection(`users/${userId}/boards`)
123 | .doc(boardId)
124 | .update({name: ev})
125 | }, 7000);
126 |
127 |
128 | return (
129 | <>
130 | {initialData ?
131 | (
132 | <>
133 |
134 | setModal(false)} />
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 | Boards
144 | /
145 | changeBoardName(e.target.value)} />
146 |
147 |
148 |
149 |
Show Priority:
150 |
151 | {filters.map(f =>
setFilter(f==='all' ? null : f)}>{f}
)}
152 | {filter ?
setFilter(null)}>All
: null}
153 |
154 |
155 |
159 |
setModal(true)}>
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 | {provided =>
170 |
171 | {
172 | initialData?.columnOrder.map((col, i) => {
173 | const column = initialData?.columns[col]
174 | const tasks = column.taskIds?.map(t => t)
175 | return
176 | })
177 | }
178 | {provided.placeholder}
179 |
182 |
183 | }
184 |
185 |
186 |
187 |
188 |
189 | >
190 | )
191 | :
192 |
193 | }
194 | >
195 | )
196 | }
197 |
198 | export default Kanban
199 |
200 |
201 |
--------------------------------------------------------------------------------