├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode ├── extensions.json └── settings.json ├── README.md ├── components ├── GanttChart │ ├── AddButton.js │ ├── AddTask.js │ ├── AddTaskDuration.js │ ├── GanttChart.js │ ├── Grid.js │ ├── Settings.js │ ├── Tasks.js │ ├── TimeRange.js │ └── TimeTable.js └── Layout.js ├── constants.js ├── helpers └── dateFunctions.js ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.js ├── _document.js └── index.js ├── public ├── data.json ├── data │ └── gantt-data.json └── favicon.ico ├── styles └── global.js └── utils └── fetchWrapper.js /.eslintignore: -------------------------------------------------------------------------------- 1 | .next 2 | next-env.d.ts 3 | node_modules 4 | yarn.lock 5 | package-lock.json 6 | public -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /.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 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | .idea 34 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .next 2 | next-env.d.ts 3 | node_modules 4 | yarn.lock 5 | package-lock.json 6 | public -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "printWidth": 80, 4 | "tabWidth": 2, 5 | "trailingComma": "es5", 6 | "singleQuote": true 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "titleBar.activeForeground": "#000", 4 | "titleBar.inactiveForeground": "#000", 5 | "titleBar.activeBackground": "#4dbef6", 6 | "titleBar.inactiveBackground": "#4dbef6" 7 | }, 8 | "editor.formatOnPaste": true, 9 | "editor.formatOnSave": true, 10 | "editor.defaultFormatter": "esbenp.prettier-vscode", 11 | "editor.codeActionsOnSave": { 12 | "source.fixAll.eslint": true, 13 | "source.fixAll.format": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tutorial: Creating a Gantt chart with React using Next.js 2 | 3 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 4 | 5 | ## Getting Started 6 | 7 | - Install dependencies: 8 | 9 | ```bash 10 | npm install 11 | ``` 12 | 13 | - To run the development server: 14 | 15 | ```bash 16 | npm run dev 17 | ``` 18 | 19 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 20 | 21 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 22 | 23 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/createResponse.js`. 24 | 25 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 26 | 27 | ## Deploy on Vercel 28 | 29 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 30 | 31 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 32 | 33 | ## Resources 34 | 35 | To learn more about Next.js, take a look at the following resources: 36 | 37 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 38 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 39 | 40 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 41 | -------------------------------------------------------------------------------- /components/GanttChart/AddButton.js: -------------------------------------------------------------------------------- 1 | export default function AddButton() { 2 | return ( 3 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /components/GanttChart/AddTask.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import AddButton from './AddButton'; 3 | 4 | export default function AddTask() { 5 | const [task, setTask] = useState(''); 6 | 7 | function onChange(e) { 8 | setTask(e.target.value); 9 | } 10 | 11 | function handleSubmit(e) { 12 | e.preventDefault(); 13 | setTask(''); 14 | } 15 | 16 | return ( 17 |
18 |

Add Task

19 | 20 | 21 | 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /components/GanttChart/AddTaskDuration.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import AddButton from './AddButton'; 3 | 4 | export default function AddTaskDuration() { 5 | const [task, setTask] = useState(''); 6 | const [startDate, setStartDate] = useState('2022-01-01'); 7 | const [endDate, setEndDate] = useState('2022-01-03'); 8 | 9 | function onChange(e) { 10 | const { value, id } = e.target; 11 | 12 | if (id === 'select-task') { 13 | setTask(value); 14 | } 15 | if (id === 'start-date') { 16 | setStartDate(value); 17 | } 18 | if (id === 'end-date') { 19 | setEndDate(value); 20 | } 21 | } 22 | 23 | function handleSubmit(e) { 24 | e.preventDefault(); 25 | } 26 | 27 | return ( 28 |
29 |

Add Task Duration

30 |
31 |
32 | 33 | 41 |
42 |
43 |
44 | 45 | 54 |
55 |
56 | 57 | 66 |
67 |
68 |
69 | 70 | 100 | 101 | ); 102 | } 103 | -------------------------------------------------------------------------------- /components/GanttChart/GanttChart.js: -------------------------------------------------------------------------------- 1 | export default function GanttChart() { 2 | return ( 3 |
4 | 18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /components/GanttChart/Grid.js: -------------------------------------------------------------------------------- 1 | export default function Grid({ children }) { 2 | return ( 3 |
4 | {children} 5 | 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /components/GanttChart/Settings.js: -------------------------------------------------------------------------------- 1 | export default function Settings({ children }) { 2 | return ( 3 |
4 | {children} 5 | 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /components/GanttChart/Tasks.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | export default function Tasks() { 4 | return ( 5 |
6 |
7 |
8 |
9 | 10 | 44 |
45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /components/GanttChart/TimeRange.js: -------------------------------------------------------------------------------- 1 | import { months } from '../../constants'; 2 | 3 | export default function TimeRange() { 4 | // add date selector values 5 | let monthsOptions = []; 6 | for (let i = 0; i < months.length; i++) { 7 | monthsOptions.push( 8 | 11 | ); 12 | } 13 | 14 | const yearsOptions = []; 15 | for (let i = 2022; i <= 2050; i++) { 16 | yearsOptions.push( 17 | 20 | ); 21 | } 22 | 23 | function onChange(e) { 24 | const { value, id } = e.target; 25 | } 26 | 27 | return ( 28 |
29 |

Tracker Period

30 |
31 |
32 | From 33 | 41 | 50 |
51 | 52 |
53 | To 54 | 62 | 71 |
72 |
73 | 107 |
108 | ); 109 | } 110 | -------------------------------------------------------------------------------- /components/GanttChart/TimeTable.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { monthDiff } from '../../helpers/dateFunctions'; 3 | 4 | export default function TimeTable() { 5 | // for dynamic css styling 6 | const ganttTimePeriod = { 7 | display: 'grid', 8 | gridAutoFlow: 'column', 9 | gridAutoColumns: 'minmax(30px, 1fr)', 10 | outline: '0.5px solid var(--color-outline)', 11 | textAlign: 'center', 12 | height: 'var(--cell-height)', 13 | }; 14 | 15 | const ganttTimePeriodSpan = { 16 | margin: 'auto', 17 | }; 18 | 19 | const ganttTimePeriodCell = { 20 | position: 'relative', 21 | outline: '0.5px solid var(--color-outline)', 22 | marginTop: '0.5px', 23 | }; 24 | 25 | const taskDuration = { 26 | position: 'absolute', 27 | height: 'calc(var(--cell-height) - 1px)', 28 | zIndex: '1', 29 | background: 30 | 'linear-gradient(90deg, var(--color-primary-light) 0%, var(--color-primary-dark) 100%)', 31 | borderRadius: 'var(--border-radius)', 32 | boxShadow: '3px 3px 3px rgba(0, 0, 0, 0.05)', 33 | cursor: 'move', 34 | }; 35 | 36 | // creating rows 37 | const startMonth = new Date( 38 | parseInt(timeRange.fromSelectYear), 39 | timeRange.fromSelectMonth 40 | ); 41 | const endMonth = new Date( 42 | parseInt(timeRange.toSelectYear), 43 | timeRange.toSelectMonth 44 | ); 45 | const numMonths = monthDiff(startMonth, endMonth) + 1; 46 | let month = new Date(startMonth); 47 | 48 | let monthRows = []; 49 | let dayRows = []; 50 | let dayRow = []; 51 | let weekRows = []; 52 | let weekRow = []; 53 | let taskRows = []; 54 | let taskRow = []; 55 | 56 | return ( 57 |
61 | {monthRows} 62 | {dayRows} 63 | {weekRows} 64 |
73 | {taskRows} 74 |
75 | 105 |
106 | ); 107 | } 108 | -------------------------------------------------------------------------------- /components/Layout.js: -------------------------------------------------------------------------------- 1 | import globalStyles from '../styles/global.js'; 2 | 3 | function Layout(props) { 4 | return ( 5 |
6 | {props.children} 7 | 10 |
11 | ); 12 | } 13 | 14 | export default Layout; 15 | -------------------------------------------------------------------------------- /constants.js: -------------------------------------------------------------------------------- 1 | export const months = [ 2 | 'Jan', 3 | 'Feb', 4 | 'Mar', 5 | 'Apr', 6 | 'May', 7 | 'Jun', 8 | 'Jul', 9 | 'Aug', 10 | 'Sep', 11 | 'Oct', 12 | 'Nov', 13 | 'Dec', 14 | ]; 15 | -------------------------------------------------------------------------------- /helpers/dateFunctions.js: -------------------------------------------------------------------------------- 1 | export function monthDiff(firstMonth, lastMonth) { 2 | let months; 3 | months = (lastMonth.getFullYear() - firstMonth.getFullYear()) * 12; 4 | months -= firstMonth.getMonth(); 5 | months += lastMonth.getMonth(); 6 | return months <= 0 ? 0 : months; 7 | } 8 | 9 | export function dayDiff(startDate, endDate) { 10 | const difference = 11 | new Date(endDate).getTime() - new Date(startDate).getTime(); 12 | const days = Math.ceil(difference / (1000 * 3600 * 24)) + 1; 13 | return days; 14 | } 15 | 16 | export function getDaysInMonth(year, month) { 17 | return new Date(year, month, 0).getDate(); 18 | } 19 | 20 | export function getDayOfWeek(year, month, day) { 21 | const daysOfTheWeekArr = ['M', 'T', 'W', 'T', 'F', 'S', 'S']; 22 | const dayOfTheWeekIndex = new Date(year, month, day).getDay(); 23 | return daysOfTheWeekArr[dayOfTheWeekIndex]; 24 | } 25 | 26 | export function createFormattedDateFromStr(year, month, day) { 27 | let monthStr = month.toString(); 28 | let dayStr = day.toString(); 29 | 30 | if (monthStr.length === 1) { 31 | monthStr = `0${monthStr}`; 32 | } 33 | if (dayStr.length === 1) { 34 | dayStr = `0${dayStr}`; 35 | } 36 | return `${year}-${monthStr}-${dayStr}`; 37 | } 38 | 39 | export function createFormattedDateFromDate(date) { 40 | let monthStr = (date.getMonth() + 1).toString(); 41 | let dayStr = date.getDate().toString(); 42 | 43 | if (monthStr.length === 1) { 44 | monthStr = `0${monthStr}`; 45 | } 46 | if (dayStr.length === 1) { 47 | dayStr = `0${dayStr}`; 48 | } 49 | return `${date.getFullYear()}-${monthStr}-${dayStr}`; 50 | } 51 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | swcMinify: true, 5 | } 6 | 7 | module.exports = nextConfig 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "next": "12.2.3", 13 | "react": "18.2.0", 14 | "react-dom": "18.2.0" 15 | }, 16 | "devDependencies": { 17 | "eslint": "8.20.0", 18 | "eslint-config-next": "12.2.3", 19 | "eslint-config-prettier": "^8.5.0", 20 | "prettier": "^2.7.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import Layout from '../components/Layout'; 2 | 3 | function MyApp({ Component, pageProps }) { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | } 10 | 11 | export default MyApp; 12 | -------------------------------------------------------------------------------- /pages/_document.js: -------------------------------------------------------------------------------- 1 | import Document, { Html, Head, Main, NextScript } from 'next/document'; 2 | 3 | class MyDocument extends Document { 4 | render() { 5 | return ( 6 | 7 | 8 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | ); 19 | } 20 | } 21 | 22 | export default MyDocument; 23 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import GanttChart from '../components/GanttChart/GanttChart'; 3 | 4 | export default function Home() { 5 | return ( 6 |
7 | 8 | Creating a Gantt chart with React using Next.js 9 | 10 | 11 | 12 | 13 |
14 |

Gantt Tracker

15 | 16 |
17 | 31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /public/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "id": 1, 5 | "name": "Task 1" 6 | }, 7 | { 8 | "id": 2, 9 | "name": "Task 2" 10 | }, 11 | { 12 | "id": 3, 13 | "name": "Task 3" 14 | }, 15 | { 16 | "id": 4, 17 | "name": "Task 4" 18 | }, 19 | { 20 | "id": 5, 21 | "name": "Task 5" 22 | }, 23 | { 24 | "id": 6, 25 | "name": "Task 6" 26 | }, 27 | { 28 | "id": 7, 29 | "name": "Task 7" 30 | }, 31 | { 32 | "id": 8, 33 | "name": "Task 8" 34 | } 35 | ], 36 | "taskDurations": [ 37 | { 38 | "id": 1, 39 | "start": "2022-01-02", 40 | "end": "2022-01-8", 41 | "task": 1 42 | }, 43 | { 44 | "id": 2, 45 | "start": "2022-01-10", 46 | "end": "2022-01-15", 47 | "task": 2 48 | }, 49 | { 50 | "id": 3, 51 | "start": "2022-01-11", 52 | "end": "2022-01-18", 53 | "task": 4 54 | } 55 | ] 56 | } -------------------------------------------------------------------------------- /public/data/gantt-data.json: -------------------------------------------------------------------------------- 1 | { 2 | "success": true, 3 | "project": { 4 | "calendar": "general", 5 | "startDate": "2022-03-14", 6 | "hoursPerDay": 24, 7 | "daysPerWeek": 5, 8 | "daysPerMonth": 20 9 | }, 10 | "calendars": { 11 | "rows": [ 12 | { 13 | "id": "general", 14 | "name": "General", 15 | "intervals": [ 16 | { 17 | "recurrentStartDate": "on Sat at 0:00", 18 | "recurrentEndDate": "on Mon at 0:00", 19 | "isWorking": false 20 | } 21 | ], 22 | "expanded": true, 23 | "children": [ 24 | { 25 | "id": "business", 26 | "name": "Business", 27 | "intervals": [ 28 | { 29 | "recurrentStartDate": "every weekday at 12:00", 30 | "recurrentEndDate": "every weekday at 13:00", 31 | "isWorking": false 32 | }, 33 | { 34 | "recurrentStartDate": "every weekday at 17:00", 35 | "recurrentEndDate": "every weekday at 08:00", 36 | "isWorking": false 37 | } 38 | ] 39 | }, 40 | { 41 | "id": "night", 42 | "name": "Night shift", 43 | "intervals": [ 44 | { 45 | "recurrentStartDate": "every weekday at 6:00", 46 | "recurrentEndDate": "every weekday at 22:00", 47 | "isWorking": false 48 | } 49 | ] 50 | } 51 | ] 52 | } 53 | ] 54 | }, 55 | "tasks": { 56 | "rows": [ 57 | { 58 | "id": 1000, 59 | "name": "Launch SaaS Product", 60 | "percentDone": 50, 61 | "startDate": "2022-03-14", 62 | "expanded": true, 63 | "children": [ 64 | { 65 | "id": 1, 66 | "name": "Setup web server", 67 | "percentDone": 50, 68 | "duration": 10, 69 | "startDate": "2022-03-14", 70 | "rollup": true, 71 | "endDate": "2022-03-23", 72 | "expanded": true, 73 | "children": [ 74 | { 75 | "id": 11, 76 | "name": "Install Apache", 77 | "percentDone": 50, 78 | "startDate": "2022-03-14", 79 | "rollup": true, 80 | "duration": 3, 81 | "color": "teal", 82 | "endDate": "2022-03-17", 83 | "cost": 200, 84 | "baselines": [ 85 | { 86 | "startDate": "2022-03-13T23:00:00", 87 | "endDate": "2022-03-16T23:00:00" 88 | }, 89 | { 90 | "startDate": "2022-03-13T23:00:00", 91 | "endDate": "2022-03-16T23:00:00" 92 | }, 93 | { 94 | "startDate": "2022-03-13T23:00:00", 95 | "endDate": "2022-03-16T23:00:00" 96 | } 97 | ] 98 | }, 99 | { 100 | "id": 12, 101 | "name": "Configure firewall", 102 | "percentDone": 50, 103 | "startDate": "2022-03-14", 104 | "rollup": true, 105 | "duration": 3, 106 | "endDate": "2022-03-17", 107 | "showInTimeline": true, 108 | "cost": 1000, 109 | "baselines": [ 110 | { 111 | "startDate": "2022-03-13T23:00:00", 112 | "endDate": "2022-03-16T23:00:00" 113 | }, 114 | { 115 | "startDate": "2022-03-13T23:00:00", 116 | "endDate": "2022-03-16T23:00:00" 117 | }, 118 | { 119 | "startDate": "2022-03-13T23:00:00", 120 | "endDate": "2022-03-16T23:00:00" 121 | } 122 | ] 123 | }, 124 | { 125 | "id": 13, 126 | "name": "Setup load balancer", 127 | "percentDone": 50, 128 | "startDate": "2022-03-14", 129 | "rollup": true, 130 | "duration": 3, 131 | "endDate": "2022-03-17", 132 | "cost": 1200, 133 | "baselines": [ 134 | { 135 | "startDate": "2022-03-13T23:00:00", 136 | "endDate": "2022-03-16T23:00:00" 137 | }, 138 | { 139 | "startDate": "2022-03-13T23:00:00", 140 | "endDate": "2022-03-16T23:00:00" 141 | }, 142 | { 143 | "startDate": "2022-03-13T23:00:00", 144 | "endDate": "2022-03-16T23:00:00" 145 | } 146 | ] 147 | }, 148 | { 149 | "id": 14, 150 | "name": "Configure ports", 151 | "percentDone": 50, 152 | "startDate": "2022-03-14", 153 | "rollup": true, 154 | "duration": 2, 155 | "endDate": "2022-03-16", 156 | "cost": 750, 157 | "baselines": [ 158 | { 159 | "startDate": "2022-03-13T23:00:00", 160 | "endDate": "2022-03-15T23:00:00" 161 | }, 162 | { 163 | "startDate": "2022-03-13T23:00:00", 164 | "endDate": "2022-03-15T23:00:00" 165 | }, 166 | { 167 | "startDate": "2022-03-13T23:00:00", 168 | "endDate": "2022-03-15T23:00:00" 169 | } 170 | ] 171 | }, 172 | { 173 | "id": 15, 174 | "name": "Run tests", 175 | "percentDone": 0, 176 | "startDate": "2022-03-21", 177 | "rollup": true, 178 | "duration": 2, 179 | "endDate": "2022-03-23", 180 | "cost": 5000, 181 | "baselines": [ 182 | { 183 | "startDate": "2022-03-20T23:00:00", 184 | "endDate": "2022-03-22T23:00:00" 185 | }, 186 | { 187 | "startDate": "2022-03-20T23:00:00", 188 | "endDate": "2022-03-22T23:00:00" 189 | }, 190 | { 191 | "startDate": "2022-03-20T23:00:00", 192 | "endDate": "2022-03-22T23:00:00" 193 | } 194 | ] 195 | } 196 | ], 197 | "baselines": [ 198 | { 199 | "startDate": "2022-03-13T23:00:00", 200 | "endDate": "2022-03-22T23:00:00" 201 | }, 202 | { 203 | "startDate": "2022-03-13T23:00:00", 204 | "endDate": "2022-03-22T23:00:00" 205 | }, 206 | { 207 | "startDate": "2022-03-13T23:00:00", 208 | "endDate": "2022-03-22T23:00:00" 209 | } 210 | ] 211 | }, 212 | { 213 | "id": 2, 214 | "name": "Website Design", 215 | "percentDone": 60, 216 | "startDate": "2022-03-23", 217 | "rollup": true, 218 | "endDate": "2022-04-13", 219 | "expanded": true, 220 | "children": [ 221 | { 222 | "id": 21, 223 | "name": "Contact designers", 224 | "percentDone": 70, 225 | "startDate": "2022-03-23", 226 | "rollup": true, 227 | "duration": 5, 228 | "endDate": "2022-03-30", 229 | "cost": 500, 230 | "baselines": [ 231 | { 232 | "startDate": "2022-03-22T23:00:00", 233 | "endDate": "2022-03-25T23:00:00" 234 | }, 235 | { 236 | "startDate": "2022-03-22T23:00:00", 237 | "endDate": "2022-03-28T23:00:00" 238 | }, 239 | { 240 | "startDate": "2022-03-22T23:00:00", 241 | "endDate": "2022-03-29T23:00:00" 242 | } 243 | ] 244 | }, 245 | { 246 | "id": 22, 247 | "name": "Create shortlist of three designers", 248 | "percentDone": 60, 249 | "startDate": "2022-03-30", 250 | "rollup": true, 251 | "duration": 1, 252 | "endDate": "2022-03-31", 253 | "cost": 1000, 254 | "baselines": [ 255 | { 256 | "startDate": "2022-03-27T23:00:00", 257 | "endDate": "2022-03-28T23:00:00" 258 | }, 259 | { 260 | "startDate": "2022-03-28T23:00:00", 261 | "endDate": "2022-03-29T23:00:00" 262 | }, 263 | { 264 | "startDate": "2022-03-29T23:00:00", 265 | "endDate": "2022-03-30T23:00:00" 266 | } 267 | ] 268 | }, 269 | { 270 | "id": 23, 271 | "name": "Select & review final design", 272 | "percentDone": 50, 273 | "startDate": "2022-03-31", 274 | "rollup": true, 275 | "duration": 2, 276 | "showInTimeline": true, 277 | "endDate": "2022-04-02", 278 | "cost": 1000, 279 | "baselines": [ 280 | { 281 | "startDate": "2022-03-28T23:00:00", 282 | "endDate": "2022-03-30T23:00:00" 283 | }, 284 | { 285 | "startDate": "2022-03-29T23:00:00", 286 | "endDate": "2022-03-31T23:00:00" 287 | }, 288 | { 289 | "startDate": "2022-03-30T23:00:00", 290 | "endDate": "2022-04-01T23:00:00" 291 | } 292 | ] 293 | }, 294 | { 295 | "id": 24, 296 | "name": "Inform management about decision", 297 | "percentDone": 100, 298 | "startDate": "2022-04-04", 299 | "rollup": true, 300 | "duration": 0, 301 | "cost": 500, 302 | "baselines": [ 303 | { 304 | "startDate": "2022-03-30T23:00:00", 305 | "endDate": "2022-03-30T23:00:00" 306 | }, 307 | { 308 | "startDate": "2022-03-31T23:00:00", 309 | "endDate": "2022-03-31T23:00:00" 310 | }, 311 | { 312 | "startDate": "2022-04-01T23:00:00", 313 | "endDate": "2022-04-01T23:00:00" 314 | } 315 | ] 316 | }, 317 | { 318 | "id": 25, 319 | "name": "Apply design to web site", 320 | "percentDone": 0, 321 | "startDate": "2022-04-04", 322 | "rollup": true, 323 | "duration": 7, 324 | "endDate": "2022-04-13", 325 | "cost": 11000, 326 | "baselines": [ 327 | { 328 | "startDate": "2022-03-30T23:00:00", 329 | "endDate": "2022-04-08T23:00:00" 330 | }, 331 | { 332 | "startDate": "2022-03-31T23:00:00", 333 | "endDate": "2022-04-11T23:00:00" 334 | }, 335 | { 336 | "startDate": "2022-04-03T23:00:00", 337 | "endDate": "2022-04-12T23:00:00" 338 | } 339 | ] 340 | } 341 | ], 342 | "baselines": [ 343 | { 344 | "startDate": "2022-03-22T23:00:00", 345 | "endDate": "2022-04-08T23:00:00" 346 | }, 347 | { 348 | "startDate": "2022-03-22T23:00:00", 349 | "endDate": "2022-04-11T23:00:00" 350 | }, 351 | { 352 | "startDate": "2022-03-22T23:00:00", 353 | "endDate": "2022-04-12T23:00:00" 354 | } 355 | ] 356 | }, 357 | { 358 | "id": 3, 359 | "name": "Setup Test Strategy", 360 | "percentDone": 20, 361 | "startDate": "2022-03-14", 362 | "expanded": true, 363 | "children": [ 364 | { 365 | "id": 31, 366 | "name": "Hire QA staff", 367 | "percentDone": 40, 368 | "startDate": "2022-03-14", 369 | "duration": 5, 370 | "endDate": "2022-03-19", 371 | "cost": 6000, 372 | "baselines": [ 373 | { 374 | "startDate": "2022-03-13T23:00:00", 375 | "endDate": "2022-03-18T23:00:00" 376 | }, 377 | { 378 | "startDate": "2022-03-13T23:00:00", 379 | "endDate": "2022-03-18T23:00:00" 380 | }, 381 | { 382 | "startDate": "2022-03-13T23:00:00", 383 | "endDate": "2022-03-18T23:00:00" 384 | } 385 | ] 386 | }, 387 | { 388 | "id": 33, 389 | "name": "Write test specs", 390 | "percentDone": 9, 391 | "duration": 5, 392 | "startDate": "2022-03-21", 393 | "expanded": true, 394 | "children": [ 395 | { 396 | "id": 331, 397 | "name": "Unit tests", 398 | "percentDone": 20, 399 | "startDate": "2022-03-21", 400 | "duration": 10, 401 | "endDate": "2022-04-02", 402 | "showInTimeline": true, 403 | "cost": 7000, 404 | "baselines": [ 405 | { 406 | "startDate": "2022-03-20T23:00:00", 407 | "endDate": "2022-04-01T23:00:00" 408 | }, 409 | { 410 | "startDate": "2022-03-20T23:00:00", 411 | "endDate": "2022-04-01T23:00:00" 412 | }, 413 | { 414 | "startDate": "2022-03-20T23:00:00", 415 | "endDate": "2022-04-01T23:00:00" 416 | } 417 | ] 418 | }, 419 | { 420 | "id": 332, 421 | "name": "UI unit tests / individual screens", 422 | "percentDone": 10, 423 | "startDate": "2022-03-21", 424 | "duration": 5, 425 | "endDate": "2022-03-26", 426 | "showInTimeline": true, 427 | "cost": 5000, 428 | "baselines": [ 429 | { 430 | "startDate": "2022-03-20T23:00:00", 431 | "endDate": "2022-03-25T23:00:00" 432 | }, 433 | { 434 | "startDate": "2022-03-20T23:00:00", 435 | "endDate": "2022-03-25T23:00:00" 436 | }, 437 | { 438 | "startDate": "2022-03-20T23:00:00", 439 | "endDate": "2022-03-25T23:00:00" 440 | } 441 | ] 442 | }, 443 | { 444 | "id": 333, 445 | "name": "Application tests", 446 | "percentDone": 0, 447 | "startDate": "2022-03-21", 448 | "duration": 10, 449 | "endDate": "2022-04-02", 450 | "cost": 2500, 451 | "baselines": [ 452 | { 453 | "startDate": "2022-03-20T23:00:00", 454 | "endDate": "2022-04-01T23:00:00" 455 | }, 456 | { 457 | "startDate": "2022-03-20T23:00:00", 458 | "endDate": "2022-04-01T23:00:00" 459 | }, 460 | { 461 | "startDate": "2022-03-20T23:00:00", 462 | "endDate": "2022-04-01T23:00:00" 463 | } 464 | ] 465 | }, 466 | { 467 | "id": 334, 468 | "name": "Monkey tests", 469 | "percentDone": 0, 470 | "startDate": "2022-03-21", 471 | "duration": 1, 472 | "endDate": "2022-03-22", 473 | "cost": 250, 474 | "baselines": [ 475 | { 476 | "startDate": "2022-03-20T23:00:00", 477 | "endDate": "2022-03-21T23:00:00" 478 | }, 479 | { 480 | "startDate": "2022-03-20T23:00:00", 481 | "endDate": "2022-03-21T23:00:00" 482 | }, 483 | { 484 | "startDate": "2022-03-20T23:00:00", 485 | "endDate": "2022-03-21T23:00:00" 486 | } 487 | ] 488 | } 489 | ], 490 | "endDate": "2022-04-02", 491 | "baselines": [ 492 | { 493 | "startDate": "2022-03-20T23:00:00", 494 | "endDate": "2022-04-01T23:00:00" 495 | }, 496 | { 497 | "startDate": "2022-03-20T23:00:00", 498 | "endDate": "2022-04-01T23:00:00" 499 | }, 500 | { 501 | "startDate": "2022-03-20T23:00:00", 502 | "endDate": "2022-04-01T23:00:00" 503 | } 504 | ] 505 | } 506 | ], 507 | "endDate": "2022-04-02", 508 | "baselines": [ 509 | { 510 | "startDate": "2022-03-13T23:00:00", 511 | "endDate": "2022-04-01T23:00:00" 512 | }, 513 | { 514 | "startDate": "2022-03-13T23:00:00", 515 | "endDate": "2022-04-01T23:00:00" 516 | }, 517 | { 518 | "startDate": "2022-03-13T23:00:00", 519 | "endDate": "2022-04-01T23:00:00" 520 | } 521 | ] 522 | }, 523 | { 524 | "id": 4, 525 | "name": "Application Implementation", 526 | "percentDone": 60, 527 | "startDate": "2022-04-04", 528 | "expanded": true, 529 | "children": [ 530 | { 531 | "id": 400, 532 | "name": "Phase #1", 533 | "expanded": true, 534 | "children": [ 535 | { 536 | "id": 41, 537 | "name": "Authentication module", 538 | "percentDone": 100, 539 | "duration": 5, 540 | "startDate": "2022-04-04", 541 | "endDate": "2022-04-09", 542 | "cost": 8000, 543 | "baselines": [ 544 | { 545 | "startDate": "2022-04-03T23:00:00", 546 | "endDate": "2022-04-08T23:00:00" 547 | }, 548 | { 549 | "startDate": "2022-04-03T23:00:00", 550 | "endDate": "2022-04-08T23:00:00" 551 | }, 552 | { 553 | "startDate": "2022-04-03T23:00:00", 554 | "endDate": "2022-04-08T23:00:00" 555 | } 556 | ] 557 | }, 558 | { 559 | "id": 42, 560 | "name": "Single sign on", 561 | "percentDone": 100, 562 | "duration": 3, 563 | "startDate": "2022-04-04", 564 | "endDate": "2022-04-07", 565 | "cost": 4700, 566 | "baselines": [ 567 | { 568 | "startDate": "2022-04-03T23:00:00", 569 | "endDate": "2022-04-06T23:00:00" 570 | }, 571 | { 572 | "startDate": "2022-04-03T23:00:00", 573 | "endDate": "2022-04-06T23:00:00" 574 | }, 575 | { 576 | "startDate": "2022-04-03T23:00:00", 577 | "endDate": "2022-04-06T23:00:00" 578 | } 579 | ] 580 | }, 581 | { 582 | "id": 43, 583 | "name": "Implement role based access", 584 | "percentDone": 0, 585 | "duration": 4, 586 | "startDate": "2022-04-04", 587 | "endDate": "2022-04-08", 588 | "cost": 5800, 589 | "baselines": [ 590 | { 591 | "startDate": "2022-04-03T23:00:00", 592 | "endDate": "2022-04-07T23:00:00" 593 | }, 594 | { 595 | "startDate": "2022-04-03T23:00:00", 596 | "endDate": "2022-04-07T23:00:00" 597 | }, 598 | { 599 | "startDate": "2022-04-03T23:00:00", 600 | "endDate": "2022-04-07T23:00:00" 601 | } 602 | ] 603 | }, 604 | { 605 | "id": 44, 606 | "name": "Basic test coverage", 607 | "showInTimeline": true, 608 | "percentDone": 0, 609 | "duration": 3, 610 | "startDate": "2022-04-04", 611 | "endDate": "2022-04-07", 612 | "cost": 7000, 613 | "baselines": [ 614 | { 615 | "startDate": "2022-04-03T23:00:00", 616 | "endDate": "2022-04-06T23:00:00" 617 | }, 618 | { 619 | "startDate": "2022-04-03T23:00:00", 620 | "endDate": "2022-04-06T23:00:00" 621 | }, 622 | { 623 | "startDate": "2022-04-03T23:00:00", 624 | "endDate": "2022-04-06T23:00:00" 625 | } 626 | ] 627 | }, 628 | { 629 | "id": 45, 630 | "name": "Verify high test coverage", 631 | "percentDone": 0, 632 | "duration": 2, 633 | "startDate": "2022-04-11", 634 | "endDate": "2022-04-13", 635 | "cost": 16000, 636 | "baselines": [ 637 | { 638 | "startDate": "2022-04-11", 639 | "endDate": "2022-04-13" 640 | }, 641 | { 642 | "startDate": "2022-04-11", 643 | "endDate": "2022-04-13" 644 | }, 645 | { 646 | "startDate": "2022-04-11", 647 | "endDate": "2022-04-13" 648 | } 649 | ] 650 | }, 651 | { 652 | "id": 46, 653 | "name": "Make backup", 654 | "percentDone": 0, 655 | "duration": 0, 656 | "startDate": "2022-04-13", 657 | "endDate": "2022-04-13", 658 | "showInTimeline": true, 659 | "rollup": true, 660 | "cost": 500, 661 | "baselines": [ 662 | { 663 | "startDate": "2022-04-11", 664 | "endDate": "2022-04-11" 665 | }, 666 | { 667 | "startDate": "2022-04-12", 668 | "endDate": "2022-04-12" 669 | }, 670 | { 671 | "startDate": "2022-04-13", 672 | "endDate": "2022-04-13" 673 | } 674 | ] 675 | } 676 | ], 677 | "startDate": "2022-04-04", 678 | "endDate": "2022-04-09", 679 | "baselines": [ 680 | { 681 | "startDate": "2022-04-03T23:00:00", 682 | "endDate": "2022-04-08T23:00:00" 683 | }, 684 | { 685 | "startDate": "2022-04-03T23:00:00", 686 | "endDate": "2022-04-08T23:00:00" 687 | }, 688 | { 689 | "startDate": "2022-04-03T23:00:00", 690 | "endDate": "2022-04-08T23:00:00" 691 | } 692 | ] 693 | }, 694 | { 695 | "id": 401, 696 | "name": "Phase #2", 697 | "expanded": true, 698 | "children": [ 699 | { 700 | "id": 4011, 701 | "name": "Authentication module", 702 | "percentDone": 70, 703 | "duration": 15, 704 | "startDate": "2022-04-11", 705 | "endDate": "2022-05-02", 706 | "cost": 1200, 707 | "baselines": [ 708 | { 709 | "startDate": "2022-04-10T23:00:00", 710 | "endDate": "2022-05-01T23:00:00" 711 | }, 712 | { 713 | "startDate": "2022-04-10T23:00:00", 714 | "endDate": "2022-05-01T23:00:00" 715 | }, 716 | { 717 | "startDate": "2022-04-10T23:00:00", 718 | "endDate": "2022-05-01T23:00:00" 719 | } 720 | ] 721 | }, 722 | { 723 | "id": 4012, 724 | "name": "Single sign on", 725 | "percentDone": 60, 726 | "duration": 5, 727 | "startDate": "2022-04-11", 728 | "endDate": "2022-04-16", 729 | "cost": 2500, 730 | "baselines": [ 731 | { 732 | "startDate": "2022-04-10T23:00:00", 733 | "endDate": "2022-04-15T23:00:00" 734 | }, 735 | { 736 | "startDate": "2022-04-10T23:00:00", 737 | "endDate": "2022-04-15T23:00:00" 738 | }, 739 | { 740 | "startDate": "2022-04-10T23:00:00", 741 | "endDate": "2022-04-15T23:00:00" 742 | } 743 | ] 744 | }, 745 | { 746 | "id": 4013, 747 | "name": "Implement role based access", 748 | "percentDone": 50, 749 | "duration": 21, 750 | "startDate": "2022-04-11", 751 | "endDate": "2022-05-12", 752 | "cost": 4100, 753 | "baselines": [ 754 | { 755 | "startDate": "2022-04-10T23:00:00", 756 | "endDate": "2022-05-11T23:00:00" 757 | }, 758 | { 759 | "startDate": "2022-04-10T23:00:00", 760 | "endDate": "2022-05-11T23:00:00" 761 | }, 762 | { 763 | "startDate": "2022-04-10T23:00:00", 764 | "endDate": "2022-05-11T23:00:00" 765 | } 766 | ] 767 | }, 768 | { 769 | "id": 4014, 770 | "name": "Basic test coverage", 771 | "percentDone": 0, 772 | "duration": 20, 773 | "startDate": "2022-04-11", 774 | "endDate": "2022-05-09", 775 | "cost": 1100, 776 | "baselines": [ 777 | { 778 | "startDate": "2022-04-10T23:00:00", 779 | "endDate": "2022-05-08T23:00:00" 780 | }, 781 | { 782 | "startDate": "2022-04-10T23:00:00", 783 | "endDate": "2022-05-08T23:00:00" 784 | }, 785 | { 786 | "startDate": "2022-04-10T23:00:00", 787 | "endDate": "2022-05-08T23:00:00" 788 | } 789 | ] 790 | }, 791 | { 792 | "id": 4015, 793 | "name": "Verify high test coverage", 794 | "percentDone": 0, 795 | "duration": 4, 796 | "startDate": "2022-04-11", 797 | "endDate": "2022-04-15", 798 | "cost": 3000, 799 | "baselines": [ 800 | { 801 | "startDate": "2022-04-10T23:00:00", 802 | "endDate": "2022-04-14T23:00:00" 803 | }, 804 | { 805 | "startDate": "2022-04-10T23:00:00", 806 | "endDate": "2022-04-14T23:00:00" 807 | }, 808 | { 809 | "startDate": "2022-04-10T23:00:00", 810 | "endDate": "2022-04-14T23:00:00" 811 | } 812 | ] 813 | } 814 | ], 815 | "startDate": "2022-04-11", 816 | "endDate": "2022-05-12", 817 | "baselines": [ 818 | { 819 | "startDate": "2022-04-10T23:00:00", 820 | "endDate": "2022-05-11T23:00:00" 821 | }, 822 | { 823 | "startDate": "2022-04-10T23:00:00", 824 | "endDate": "2022-05-11T23:00:00" 825 | }, 826 | { 827 | "startDate": "2022-04-10T23:00:00", 828 | "endDate": "2022-05-11T23:00:00" 829 | } 830 | ] 831 | }, 832 | { 833 | "id": 402, 834 | "name": "Acceptance phase", 835 | "expanded": true, 836 | "children": [ 837 | { 838 | "id": 4031, 839 | "name": "Company bug bash", 840 | "percentDone": 70, 841 | "duration": 3, 842 | "startDate": "2022-05-12", 843 | "endDate": "2022-05-15", 844 | "cost": 10000, 845 | "baselines": [ 846 | { 847 | "startDate": "2022-05-11T23:00:00", 848 | "endDate": "2022-05-14T23:00:00" 849 | }, 850 | { 851 | "startDate": "2022-05-11T23:00:00", 852 | "endDate": "2022-05-14T23:00:00" 853 | }, 854 | { 855 | "startDate": "2022-05-11T23:00:00", 856 | "endDate": "2022-05-14T23:00:00" 857 | } 858 | ] 859 | }, 860 | { 861 | "id": 4032, 862 | "name": "Test all web pages", 863 | "percentDone": 60, 864 | "duration": 2, 865 | "startDate": "2022-05-12", 866 | "endDate": "2022-05-14", 867 | "cost": 5000, 868 | "baselines": [ 869 | { 870 | "startDate": "2022-05-11T23:00:00", 871 | "endDate": "2022-05-13T23:00:00" 872 | }, 873 | { 874 | "startDate": "2022-05-11T23:00:00", 875 | "endDate": "2022-05-13T23:00:00" 876 | }, 877 | { 878 | "startDate": "2022-05-11T23:00:00", 879 | "endDate": "2022-05-13T23:00:00" 880 | } 881 | ] 882 | }, 883 | { 884 | "id": 4033, 885 | "name": "Verify no broken links", 886 | "percentDone": 50, 887 | "duration": 4, 888 | "startDate": "2022-05-12", 889 | "endDate": "2022-05-16", 890 | "cost": 1000, 891 | "baselines": [ 892 | { 893 | "startDate": "2022-05-11T23:00:00", 894 | "endDate": "2022-05-15T23:00:00" 895 | }, 896 | { 897 | "startDate": "2022-05-11T23:00:00", 898 | "endDate": "2022-05-15T23:00:00" 899 | }, 900 | { 901 | "startDate": "2022-05-11T23:00:00", 902 | "endDate": "2022-05-15T23:00:00" 903 | } 904 | ] 905 | }, 906 | { 907 | "id": 4034, 908 | "name": "Make test release", 909 | "percentDone": 0, 910 | "duration": 3, 911 | "startDate": "2022-05-12", 912 | "endDate": "2022-05-15", 913 | "cost": 1200, 914 | "baselines": [ 915 | { 916 | "startDate": "2022-05-11T23:00:00", 917 | "endDate": "2022-05-14T23:00:00" 918 | }, 919 | { 920 | "startDate": "2022-05-11T23:00:00", 921 | "endDate": "2022-05-14T23:00:00" 922 | }, 923 | { 924 | "startDate": "2022-05-11T23:00:00", 925 | "endDate": "2022-05-14T23:00:00" 926 | } 927 | ] 928 | }, 929 | { 930 | "id": 4035, 931 | "name": "Send invitation email", 932 | "percentDone": 0, 933 | "duration": 0, 934 | "startDate": "2022-05-15", 935 | "endDate": "2022-05-16", 936 | "cost": 250, 937 | "baselines": [ 938 | { 939 | "startDate": "2022-05-14T23:00:00", 940 | "endDate": "2022-05-14T23:00:00" 941 | }, 942 | { 943 | "startDate": "2022-05-13T00:00:00", 944 | "endDate": "2022-05-13T00:00:00" 945 | }, 946 | { 947 | "startDate": "2022-05-12T00:00:00", 948 | "endDate": "2022-05-12T00:00:00" 949 | } 950 | ] 951 | }, 952 | { 953 | "id": 4036, 954 | "name": "Celebrate launch", 955 | "iconCls": "b-fa b-fa-glass-cheers", 956 | "percentDone": 0, 957 | "duration": 1, 958 | "startDate": "2022-05-12", 959 | "endDate": "2022-05-13", 960 | "cost": 2500, 961 | "baselines": [ 962 | { 963 | "startDate": "2022-05-11T23:00:00", 964 | "endDate": "2022-05-12T23:00:00" 965 | }, 966 | { 967 | "startDate": "2022-05-11T23:00:00", 968 | "endDate": "2022-05-12T23:00:00" 969 | }, 970 | { 971 | "startDate": "2022-05-11T23:00:00", 972 | "endDate": "2022-05-12T23:00:00" 973 | } 974 | ] 975 | } 976 | ], 977 | "startDate": "2022-05-12", 978 | "endDate": "2022-05-16", 979 | "baselines": [ 980 | { 981 | "startDate": "2022-05-11T23:00:00", 982 | "endDate": "2022-05-15T23:00:00" 983 | }, 984 | { 985 | "startDate": "2022-05-11T23:00:00", 986 | "endDate": "2022-05-15T23:00:00" 987 | }, 988 | { 989 | "startDate": "2022-05-11T23:00:00", 990 | "endDate": "2022-05-15T23:00:00" 991 | } 992 | ] 993 | } 994 | ], 995 | "endDate": "2022-05-16", 996 | "baselines": [ 997 | { 998 | "startDate": "2022-04-03T23:00:00", 999 | "endDate": "2022-05-15T23:00:00" 1000 | }, 1001 | { 1002 | "startDate": "2022-04-03T23:00:00", 1003 | "endDate": "2022-05-15T23:00:00" 1004 | }, 1005 | { 1006 | "startDate": "2022-04-03T23:00:00", 1007 | "endDate": "2022-05-15T23:00:00" 1008 | } 1009 | ] 1010 | } 1011 | ], 1012 | "endDate": "2022-05-16", 1013 | "baselines": [ 1014 | { 1015 | "startDate": "2022-03-13T23:00:00", 1016 | "endDate": "2022-05-15T23:00:00" 1017 | }, 1018 | { 1019 | "startDate": "2022-03-13T23:00:00", 1020 | "endDate": "2022-05-15T23:00:00" 1021 | }, 1022 | { 1023 | "startDate": "2022-03-13T23:00:00", 1024 | "endDate": "2022-05-15T23:00:00" 1025 | } 1026 | ] 1027 | } 1028 | ] 1029 | }, 1030 | "dependencies": { 1031 | "rows": [ 1032 | { 1033 | "id": 1, 1034 | "fromTask": 11, 1035 | "toTask": 15, 1036 | "lag": 2 1037 | }, 1038 | { 1039 | "id": 2, 1040 | "fromTask": 12, 1041 | "toTask": 15 1042 | }, 1043 | { 1044 | "id": 3, 1045 | "fromTask": 13, 1046 | "toTask": 15 1047 | }, 1048 | { 1049 | "id": 4, 1050 | "fromTask": 14, 1051 | "toTask": 15 1052 | }, 1053 | { 1054 | "id": 5, 1055 | "fromTask": 15, 1056 | "toTask": 21 1057 | }, 1058 | { 1059 | "id": 7, 1060 | "fromTask": 21, 1061 | "toTask": 22 1062 | }, 1063 | { 1064 | "id": 8, 1065 | "fromTask": 22, 1066 | "toTask": 23 1067 | }, 1068 | { 1069 | "id": 9, 1070 | "fromTask": 23, 1071 | "toTask": 24 1072 | }, 1073 | { 1074 | "id": 10, 1075 | "fromTask": 24, 1076 | "toTask": 25 1077 | }, 1078 | { 1079 | "id": 11, 1080 | "fromTask": 31, 1081 | "toTask": 331 1082 | }, 1083 | { 1084 | "id": 111, 1085 | "fromTask": 31, 1086 | "toTask": 332 1087 | }, 1088 | { 1089 | "id": 112, 1090 | "fromTask": 31, 1091 | "toTask": 333 1092 | }, 1093 | { 1094 | "id": 113, 1095 | "fromTask": 31, 1096 | "toTask": 334 1097 | }, 1098 | { 1099 | "id": 12, 1100 | "fromTask": 400, 1101 | "toTask": 401 1102 | }, 1103 | { 1104 | "id": 13, 1105 | "fromTask": 401, 1106 | "toTask": 402 1107 | }, 1108 | { 1109 | "id": 15, 1110 | "fromTask": 3, 1111 | "toTask": 4 1112 | }, 1113 | { 1114 | "id": 16, 1115 | "fromTask": 41, 1116 | "toTask": 45 1117 | }, 1118 | { 1119 | "id": 17, 1120 | "fromTask": 42, 1121 | "toTask": 45 1122 | }, 1123 | { 1124 | "id": 18, 1125 | "fromTask": 43, 1126 | "toTask": 45 1127 | }, 1128 | { 1129 | "id": 19, 1130 | "fromTask": 44, 1131 | "toTask": 45 1132 | }, 1133 | { 1134 | "id": 20, 1135 | "fromTask": 4034, 1136 | "toTask": 4035 1137 | } 1138 | ] 1139 | }, 1140 | "resources": { 1141 | "rows": [ 1142 | { 1143 | "id": 1, 1144 | "name": "Celia", 1145 | "city": "Barcelona", 1146 | "calendar": null, 1147 | "image": "celia.jpg" 1148 | }, 1149 | { 1150 | "id": 2, 1151 | "name": "Lee", 1152 | "city": "London", 1153 | "calendar": null, 1154 | "image": "lee.jpg" 1155 | }, 1156 | { 1157 | "id": 3, 1158 | "name": "Macy", 1159 | "city": "New York", 1160 | "calendar": null, 1161 | "image": "macy.jpg" 1162 | }, 1163 | { 1164 | "id": 4, 1165 | "name": "Madison", 1166 | "city": "Barcelona", 1167 | "calendar": null, 1168 | "image": "madison.jpg" 1169 | }, 1170 | { 1171 | "id": 5, 1172 | "name": "Rob", 1173 | "city": "Rome", 1174 | "calendar": "business", 1175 | "image": "rob.jpg" 1176 | }, 1177 | { 1178 | "id": 6, 1179 | "name": "Dave", 1180 | "city": "Barcelona", 1181 | "calendar": "night", 1182 | "image": "dave.jpg" 1183 | }, 1184 | { 1185 | "id": 7, 1186 | "name": "Dan", 1187 | "city": "London", 1188 | "calendar": "night", 1189 | "image": "dan.jpg" 1190 | }, 1191 | { 1192 | "id": 8, 1193 | "name": "George", 1194 | "city": "New York", 1195 | "calendar": null, 1196 | "image": "george.jpg" 1197 | }, 1198 | { 1199 | "id": 9, 1200 | "name": "Gloria", 1201 | "city": "Rome", 1202 | "calendar": null, 1203 | "image": "gloria.jpg" 1204 | }, 1205 | { 1206 | "id": 10, 1207 | "name": "Henrik", 1208 | "city": "London", 1209 | "calendar": null, 1210 | "image": "henrik.jpg" 1211 | } 1212 | ] 1213 | }, 1214 | "assignments": { 1215 | "rows": [ 1216 | { 1217 | "id": 1, 1218 | "event": 11, 1219 | "resource": 1 1220 | }, 1221 | { 1222 | "id": 2, 1223 | "event": 4033, 1224 | "resource": 1 1225 | }, 1226 | { 1227 | "id": 3, 1228 | "event": 12, 1229 | "resource": 9 1230 | }, 1231 | { 1232 | "id": 4, 1233 | "event": 13, 1234 | "resource": 2 1235 | }, 1236 | { 1237 | "id": 5, 1238 | "event": 13, 1239 | "resource": 3 1240 | }, 1241 | { 1242 | "id": 6, 1243 | "event": 13, 1244 | "resource": 6 1245 | }, 1246 | { 1247 | "id": 7, 1248 | "event": 13, 1249 | "resource": 7 1250 | }, 1251 | { 1252 | "id": 8, 1253 | "event": 13, 1254 | "resource": 8 1255 | }, 1256 | { 1257 | "id": 9, 1258 | "event": 21, 1259 | "resource": 5 1260 | }, 1261 | { 1262 | "id": 10, 1263 | "event": 21, 1264 | "resource": 9 1265 | }, 1266 | { 1267 | "id": 11, 1268 | "event": 22, 1269 | "resource": 8 1270 | }, 1271 | { 1272 | "id": 12, 1273 | "event": 25, 1274 | "resource": 3 1275 | } 1276 | ] 1277 | }, 1278 | "timeRanges": { 1279 | "rows": [ 1280 | { 1281 | "id": 1, 1282 | "name": "Important date", 1283 | "startDate": "2022-03-30", 1284 | "duration": 0, 1285 | "durationUnit": "d", 1286 | "cls": "b-fa b-fa-diamond" 1287 | } 1288 | ] 1289 | } 1290 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bryntum/gantt-chart-nextjs-starter/1144d7b2a52027369c9e9b592810899d61fa9f44/public/favicon.ico -------------------------------------------------------------------------------- /styles/global.js: -------------------------------------------------------------------------------- 1 | import css from 'styled-jsx/css'; 2 | 3 | export default css.global` 4 | * { 5 | box-sizing: border-box; 6 | margin: 0; 7 | } 8 | 9 | html, 10 | body { 11 | font-family: 'Montserrat', sans-serif; 12 | color: #000; 13 | } 14 | 15 | input, 16 | select { 17 | font-family: 'Montserrat', sans-serif; 18 | font-size: 13px; 19 | padding: 5px 7px; 20 | margin: 1rem 0; 21 | display: inline-block; 22 | border: 1px solid #ccc; 23 | border-radius: 4px; 24 | } 25 | 26 | button { 27 | cursor: pointer; 28 | font-family: 'Montserrat', sans-serif; 29 | font-size: 13px; 30 | } 31 | 32 | h2 { 33 | font-size: 1.5rem; 34 | } 35 | 36 | form { 37 | padding: 1rem; 38 | } 39 | 40 | form > * { 41 | display: flex; 42 | align-items: center; 43 | } 44 | `; 45 | -------------------------------------------------------------------------------- /utils/fetchWrapper.js: -------------------------------------------------------------------------------- 1 | // from https://kentcdodds.com/blog/replace-axios-with-a-simple-custom-fetch-wrapper 2 | export function client(endpoint, { body, ...customConfig } = {}) { 3 | const headers = { 'Content-Type': 'application/json' }; 4 | const config = { 5 | method: body ? 'POST' : 'GET', 6 | ...customConfig, 7 | headers: { 8 | ...headers, 9 | ...customConfig.headers, 10 | }, 11 | }; 12 | if (body) { 13 | config.body = JSON.stringify(body); 14 | } 15 | 16 | return window.fetch(`${endpoint}`, config).then(async (response) => { 17 | if (response.ok) { 18 | return await response.json(); 19 | } else { 20 | const errorMessage = await response.text(); 21 | return Promise.reject(new Error(errorMessage)); 22 | } 23 | }); 24 | } 25 | --------------------------------------------------------------------------------