commit
7ab4e7784e
@ -32,6 +32,7 @@
|
|||||||
"react/no-unused-prop-types": 0,
|
"react/no-unused-prop-types": 0,
|
||||||
"import/export": 0,
|
"import/export": 0,
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
|
"no-use-before-define": "off",
|
||||||
"@typescript-eslint/no-unused-vars": "error"
|
"@typescript-eslint/no-unused-vars": "error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,7 @@ let tasks: Task[] = [
|
|||||||
end: new Date(2020, 1, 2),
|
end: new Date(2020, 1, 2),
|
||||||
name: 'Idea',
|
name: 'Idea',
|
||||||
id: 'Task 0',
|
id: 'Task 0',
|
||||||
|
type:'task'
|
||||||
progress: 45,
|
progress: 45,
|
||||||
isDisabled: true,
|
isDisabled: true,
|
||||||
styles: { progressColor: '#ffbb54', progressSelectedColor: '#ff9e0d' },
|
styles: { progressColor: '#ffbb54', progressSelectedColor: '#ff9e0d' },
|
||||||
@ -71,7 +72,7 @@ npm start
|
|||||||
| :----------------- | :---------------------------------------------------------- | :-------------------------------------------------------------------------------------- |
|
| :----------------- | :---------------------------------------------------------- | :-------------------------------------------------------------------------------------- |
|
||||||
| onSelect | (task: Task, isSelected: boolean) => void | Specifies the function to be executed on the taskbar select or unselect event. |
|
| onSelect | (task: Task, isSelected: boolean) => void | Specifies the function to be executed on the taskbar select or unselect event. |
|
||||||
| onDoubleClick | (task: Task) => void | Specifies the function to be executed on the taskbar onDoubleClick event. |
|
| onDoubleClick | (task: Task) => void | Specifies the function to be executed on the taskbar onDoubleClick event. |
|
||||||
| onTaskDelete\* | (task: Task) => void/boolean/Promise<void>/Promise<boolean> | Specifies the function to be executed on the taskbar on Delete button press event. |
|
| onDelete\* | (task: Task) => void/boolean/Promise<void>/Promise<boolean> | Specifies the function to be executed on the taskbar on Delete button press event. |
|
||||||
| onDateChange\* | (task: Task) => void/boolean/Promise<void>/Promise<boolean> | Specifies the function to be executed when drag taskbar event on timeline has finished. |
|
| onDateChange\* | (task: Task) => void/boolean/Promise<void>/Promise<boolean> | Specifies the function to be executed when drag taskbar event on timeline has finished. |
|
||||||
| onProgressChange\* | (task: Task) => void/boolean/Promise<void>/Promise<boolean> | Specifies the function to be executed when drag taskbar progress event has finished. |
|
| onProgressChange\* | (task: Task) => void/boolean/Promise<void>/Promise<boolean> | Specifies the function to be executed when drag taskbar progress event has finished. |
|
||||||
| timeStep | (task: Task) => number | A time step value for onDateChange. Specify in milliseconds. |
|
| timeStep | (task: Task) => number | A time step value for onDateChange. Specify in milliseconds. |
|
||||||
@ -120,6 +121,7 @@ npm start
|
|||||||
| :------------- | :------- | :---------------------------------------------------------------------------------------------------- |
|
| :------------- | :------- | :---------------------------------------------------------------------------------------------------- |
|
||||||
| id\* | string | Task id. |
|
| id\* | string | Task id. |
|
||||||
| name\* | string | Task display name. |
|
| name\* | string | Task display name. |
|
||||||
|
| type\* | string | Task display type: **task**, **milestone**, **project** |
|
||||||
| start\* | Date | Task start date. |
|
| start\* | Date | Task start date. |
|
||||||
| end\* | Date | Task end date. |
|
| end\* | Date | Task end date. |
|
||||||
| progress\* | number | Task progress. Sets in percent from 0 to 100. |
|
| progress\* | number | Task progress. Sets in percent from 0 to 100. |
|
||||||
@ -131,6 +133,7 @@ npm start
|
|||||||
| | | - **progressSelectedColor**: String. Specifies the taskbar progress fill color globally on select. |
|
| | | - **progressSelectedColor**: String. Specifies the taskbar progress fill color globally on select. |
|
||||||
| isDisabled | bool | Disables all action for current task. |
|
| isDisabled | bool | Disables all action for current task. |
|
||||||
| fontSize | string | Specifies the taskbar font size locally. |
|
| fontSize | string | Specifies the taskbar font size locally. |
|
||||||
|
| project | string | Task project name |
|
||||||
|
|
||||||
\*Required
|
\*Required
|
||||||
|
|
||||||
|
|||||||
24018
example/package-lock.json
generated
24018
example/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,13 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import "gantt-task-react/dist/index.css";
|
|
||||||
import { Task, ViewMode, Gantt } from "gantt-task-react";
|
import { Task, ViewMode, Gantt } from "gantt-task-react";
|
||||||
import { ViewSwitcher } from "./components/view-switcher";
|
import { ViewSwitcher } from "./components/view-switcher";
|
||||||
|
import { getStartEndDateForProject, initTasks } from "./helper";
|
||||||
|
import "gantt-task-react/dist/index.css";
|
||||||
|
|
||||||
//Init
|
//Init
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const currentDate = new Date();
|
|
||||||
const [view, setView] = React.useState<ViewMode>(ViewMode.Day);
|
const [view, setView] = React.useState<ViewMode>(ViewMode.Day);
|
||||||
|
const [tasks, setTasks] = React.useState<Task[]>(initTasks());
|
||||||
const [isChecked, setIsChecked] = React.useState(true);
|
const [isChecked, setIsChecked] = React.useState(true);
|
||||||
let columnWidth = 60;
|
let columnWidth = 60;
|
||||||
if (view === ViewMode.Month) {
|
if (view === ViewMode.Month) {
|
||||||
@ -14,93 +15,44 @@ const App = () => {
|
|||||||
} else if (view === ViewMode.Week) {
|
} else if (view === ViewMode.Week) {
|
||||||
columnWidth = 250;
|
columnWidth = 250;
|
||||||
}
|
}
|
||||||
let tasks: Task[] = [
|
|
||||||
{
|
|
||||||
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 1),
|
|
||||||
end: new Date(
|
|
||||||
currentDate.getFullYear(),
|
|
||||||
currentDate.getMonth(),
|
|
||||||
2,
|
|
||||||
12,
|
|
||||||
28
|
|
||||||
),
|
|
||||||
name: "Idea",
|
|
||||||
id: "Task 0",
|
|
||||||
progress: 45,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 2),
|
|
||||||
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 4, 0, 0),
|
|
||||||
name: "Research",
|
|
||||||
id: "Task 1",
|
|
||||||
progress: 25,
|
|
||||||
dependencies: ["Task 0"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 4),
|
|
||||||
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 8, 0, 0),
|
|
||||||
name: "Discussion with team",
|
|
||||||
id: "Task 2",
|
|
||||||
progress: 10,
|
|
||||||
dependencies: ["Task 1"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 8),
|
|
||||||
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 9, 0, 0),
|
|
||||||
name: "Developing",
|
|
||||||
id: "Task 3",
|
|
||||||
progress: 2,
|
|
||||||
dependencies: ["Task 2"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 8),
|
|
||||||
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 10),
|
|
||||||
name: "Review",
|
|
||||||
id: "Task 4",
|
|
||||||
progress: 70,
|
|
||||||
dependencies: ["Task 2"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 15),
|
|
||||||
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 16),
|
|
||||||
name: "Release & Eat Pizza",
|
|
||||||
id: "Task 6",
|
|
||||||
progress: currentDate.getMonth(),
|
|
||||||
dependencies: ["Task 4"],
|
|
||||||
styles: { progressColor: "#ffbb54", progressSelectedColor: "#ff9e0d" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 24),
|
|
||||||
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 25),
|
|
||||||
name: "Closing",
|
|
||||||
id: "Task 9",
|
|
||||||
progress: 0,
|
|
||||||
isDisabled: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const sleep = (milliseconds: number) => {
|
const onTaskChange = (task: Task) => {
|
||||||
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
|
||||||
};
|
|
||||||
let onTaskChange = (task: Task) => {
|
|
||||||
console.log("On date change Id:" + task.id);
|
console.log("On date change Id:" + task.id);
|
||||||
|
let newTasks = tasks.map(t => (t.id === task.id ? task : t));
|
||||||
|
if (task.project) {
|
||||||
|
const [start, end] = getStartEndDateForProject(newTasks, task.project);
|
||||||
|
const project = newTasks[newTasks.findIndex(t => t.id === task.project)];
|
||||||
|
if (
|
||||||
|
project.start.getTime() !== start.getTime() ||
|
||||||
|
project.end.getTime() !== end.getTime()
|
||||||
|
) {
|
||||||
|
const changedProject = { ...project, start, end };
|
||||||
|
newTasks = newTasks.map(t =>
|
||||||
|
t.id === task.project ? changedProject : t
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTasks(newTasks);
|
||||||
};
|
};
|
||||||
|
|
||||||
let onTaskDelete = (task: Task) => {
|
const onTaskDelete = (task: Task) => {
|
||||||
const conf = window.confirm("Are you sure about " + task.name + " ?");
|
const conf = window.confirm("Are you sure about " + task.name + " ?");
|
||||||
|
if (conf) {
|
||||||
|
setTasks(tasks.filter(t => t.id !== task.id));
|
||||||
|
}
|
||||||
return conf;
|
return conf;
|
||||||
};
|
};
|
||||||
|
|
||||||
let onProgressChange = async (task: Task) => {
|
const onProgressChange = async (task: Task) => {
|
||||||
await sleep(5000);
|
setTasks(tasks.map(t => (t.id === task.id ? task : t)));
|
||||||
console.log("On progress change Id:" + task.id);
|
console.log("On progress change Id:" + task.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
let onDblClick = (task: Task) => {
|
const onDblClick = (task: Task) => {
|
||||||
alert("On Double Click event Id:" + task.id);
|
alert("On Double Click event Id:" + task.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
let onSelect = (task: Task, isSelected: boolean) => {
|
const onSelect = (task: Task, isSelected: boolean) => {
|
||||||
console.log(task.name + " has " + (isSelected ? "selected" : "unselected"));
|
console.log(task.name + " has " + (isSelected ? "selected" : "unselected"));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -116,19 +68,22 @@ const App = () => {
|
|||||||
tasks={tasks}
|
tasks={tasks}
|
||||||
viewMode={view}
|
viewMode={view}
|
||||||
onDateChange={onTaskChange}
|
onDateChange={onTaskChange}
|
||||||
onTaskDelete={onTaskDelete}
|
onDelete={onTaskDelete}
|
||||||
onProgressChange={onProgressChange}
|
onProgressChange={onProgressChange}
|
||||||
onDoubleClick={onDblClick}
|
onDoubleClick={onDblClick}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
listCellWidth={isChecked ? "155px" : ""}
|
listCellWidth={isChecked ? "155px" : ""}
|
||||||
columnWidth={columnWidth}
|
columnWidth={columnWidth}
|
||||||
/>
|
/>
|
||||||
|
<h3 style={{ color: "#e56b6f" }}>
|
||||||
|
Milestones and projects are not available
|
||||||
|
</h3>
|
||||||
<h3>Gantt With Limited Height</h3>
|
<h3>Gantt With Limited Height</h3>
|
||||||
<Gantt
|
<Gantt
|
||||||
tasks={tasks}
|
tasks={tasks}
|
||||||
viewMode={view}
|
viewMode={view}
|
||||||
onDateChange={onTaskChange}
|
onDateChange={onTaskChange}
|
||||||
onTaskDelete={onTaskDelete}
|
onDelete={onTaskDelete}
|
||||||
onProgressChange={onProgressChange}
|
onProgressChange={onProgressChange}
|
||||||
onDoubleClick={onDblClick}
|
onDoubleClick={onDblClick}
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
|
|||||||
107
example/src/helper.tsx
Normal file
107
example/src/helper.tsx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { Task } from "../../dist/types/public-types";
|
||||||
|
|
||||||
|
export function initTasks() {
|
||||||
|
const currentDate = new Date();
|
||||||
|
const tasks: Task[] = [
|
||||||
|
{
|
||||||
|
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 1),
|
||||||
|
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 15),
|
||||||
|
name: "Some Project",
|
||||||
|
id: "ProjectSample",
|
||||||
|
progress: 25,
|
||||||
|
type: "project",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 1),
|
||||||
|
end: new Date(
|
||||||
|
currentDate.getFullYear(),
|
||||||
|
currentDate.getMonth(),
|
||||||
|
2,
|
||||||
|
12,
|
||||||
|
28
|
||||||
|
),
|
||||||
|
name: "Idea",
|
||||||
|
id: "Task 0",
|
||||||
|
progress: 45,
|
||||||
|
type: "task",
|
||||||
|
project: "ProjectSample",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 2),
|
||||||
|
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 4, 0, 0),
|
||||||
|
name: "Research",
|
||||||
|
id: "Task 1",
|
||||||
|
progress: 25,
|
||||||
|
dependencies: ["Task 0"],
|
||||||
|
type: "task",
|
||||||
|
project: "ProjectSample",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 4),
|
||||||
|
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 8, 0, 0),
|
||||||
|
name: "Discussion with team",
|
||||||
|
id: "Task 2",
|
||||||
|
progress: 10,
|
||||||
|
dependencies: ["Task 1"],
|
||||||
|
type: "task",
|
||||||
|
project: "ProjectSample",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 8),
|
||||||
|
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 9, 0, 0),
|
||||||
|
name: "Developing",
|
||||||
|
id: "Task 3",
|
||||||
|
progress: 2,
|
||||||
|
dependencies: ["Task 2"],
|
||||||
|
type: "task",
|
||||||
|
project: "ProjectSample",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 8),
|
||||||
|
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 10),
|
||||||
|
name: "Review",
|
||||||
|
id: "Task 4",
|
||||||
|
type: "task",
|
||||||
|
progress: 70,
|
||||||
|
dependencies: ["Task 2"],
|
||||||
|
project: "ProjectSample",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 15),
|
||||||
|
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 15),
|
||||||
|
name: "Release",
|
||||||
|
id: "Task 6",
|
||||||
|
progress: currentDate.getMonth(),
|
||||||
|
type: "milestone",
|
||||||
|
dependencies: ["Task 4"],
|
||||||
|
project: "ProjectSample",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 18),
|
||||||
|
end: new Date(currentDate.getFullYear(), currentDate.getMonth(), 19),
|
||||||
|
name: "Party Time",
|
||||||
|
id: "Task 9",
|
||||||
|
progress: 0,
|
||||||
|
isDisabled: true,
|
||||||
|
type: "task",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return tasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStartEndDateForProject(tasks: Task[], projectId: string) {
|
||||||
|
const projectTasks = tasks.filter(t => t.project === projectId);
|
||||||
|
let start = projectTasks[0].start;
|
||||||
|
let end = projectTasks[0].end;
|
||||||
|
|
||||||
|
for (let i = 0; i < projectTasks.length; i++) {
|
||||||
|
const task = projectTasks[i];
|
||||||
|
if (start.getTime() > task.start.getTime()) {
|
||||||
|
start = task.start;
|
||||||
|
}
|
||||||
|
if (end.getTime() < task.end.getTime()) {
|
||||||
|
end = task.end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [start, end];
|
||||||
|
}
|
||||||
@ -26,7 +26,8 @@
|
|||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true
|
"noEmit": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src"
|
"src"
|
||||||
|
|||||||
13855
package-lock.json
generated
13855
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
36
package.json
36
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gantt-task-react",
|
"name": "gantt-task-react",
|
||||||
"version": "0.2.2",
|
"version": "0.3.0",
|
||||||
"description": "Interactive Gantt Chart for React with TypeScript.",
|
"description": "Interactive Gantt Chart for React with TypeScript.",
|
||||||
"author": "MaTeMaTuK <maksym.vikarii@gmail.com>",
|
"author": "MaTeMaTuK <maksym.vikarii@gmail.com>",
|
||||||
"homepage": "https://github.com/MaTeMaTuK/gantt-task-react",
|
"homepage": "https://github.com/MaTeMaTuK/gantt-task-react",
|
||||||
@ -43,30 +43,30 @@
|
|||||||
"@testing-library/react": "^9.5.0",
|
"@testing-library/react": "^9.5.0",
|
||||||
"@testing-library/user-event": "^7.2.1",
|
"@testing-library/user-event": "^7.2.1",
|
||||||
"@types/jest": "^25.1.4",
|
"@types/jest": "^25.1.4",
|
||||||
"@types/node": "^12.12.38",
|
"@types/node": "^12.20.4",
|
||||||
"@types/react": "^16.9.27",
|
"@types/react": "^16.14.4",
|
||||||
"@types/react-dom": "^16.9.7",
|
"@types/react-dom": "^16.9.11",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
"@typescript-eslint/eslint-plugin": "^4.7.0",
|
||||||
"@typescript-eslint/parser": "^2.26.0",
|
"@typescript-eslint/parser": "^4.7.0",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
"cross-env": "^7.0.2",
|
"cross-env": "^7.0.3",
|
||||||
"eslint-config-prettier": "^6.7.0",
|
"eslint-config-prettier": "^6.15.0",
|
||||||
"eslint-config-standard": "^14.1.0",
|
"eslint-config-standard": "^14.1.0",
|
||||||
"eslint-config-standard-react": "^9.2.0",
|
"eslint-config-standard-react": "^9.2.0",
|
||||||
"eslint-plugin-import": "^2.18.2",
|
"eslint-plugin-import": "^2.22.1",
|
||||||
"eslint-plugin-node": "^11.0.0",
|
"eslint-plugin-node": "^11.0.0",
|
||||||
"eslint-plugin-prettier": "^3.1.1",
|
"eslint-plugin-prettier": "^3.3.1",
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^4.3.1",
|
||||||
"eslint-plugin-react": "^7.17.0",
|
"eslint-plugin-react": "^7.22.0",
|
||||||
"eslint-plugin-standard": "^4.0.1",
|
"eslint-plugin-standard": "^4.1.0",
|
||||||
"gh-pages": "^2.2.0",
|
"gh-pages": "^2.2.0",
|
||||||
"microbundle-crl": "^0.13.11",
|
"microbundle-crl": "^0.13.11",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"prettier": "^2.0.4",
|
"prettier": "^2.2.1",
|
||||||
"react": "^16.13.1",
|
"react": "^16.14.0",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.14.0",
|
||||||
"react-scripts": "^3.4.3",
|
"react-scripts": "^4.0.3",
|
||||||
"typescript": "^3.8.3"
|
"typescript": "^3.9.9"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@ -5,10 +5,11 @@ import {
|
|||||||
getLocaleMonth,
|
getLocaleMonth,
|
||||||
getWeekNumberISO8601,
|
getWeekNumberISO8601,
|
||||||
} from "../../helpers/date-helper";
|
} from "../../helpers/date-helper";
|
||||||
|
import { DateSetup } from "../../types/date-setup";
|
||||||
import styles from "./calendar.module.css";
|
import styles from "./calendar.module.css";
|
||||||
|
|
||||||
export type CalendarProps = {
|
export type CalendarProps = {
|
||||||
dates: Date[];
|
dateSetup: DateSetup;
|
||||||
locale: string;
|
locale: string;
|
||||||
viewMode: ViewMode;
|
viewMode: ViewMode;
|
||||||
headerHeight: number;
|
headerHeight: number;
|
||||||
@ -18,7 +19,7 @@ export type CalendarProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Calendar: React.FC<CalendarProps> = ({
|
export const Calendar: React.FC<CalendarProps> = ({
|
||||||
dates,
|
dateSetup,
|
||||||
locale,
|
locale,
|
||||||
viewMode,
|
viewMode,
|
||||||
headerHeight,
|
headerHeight,
|
||||||
@ -31,8 +32,8 @@ export const Calendar: React.FC<CalendarProps> = ({
|
|||||||
const bottomValues: ReactChild[] = [];
|
const bottomValues: ReactChild[] = [];
|
||||||
const topDefaultWidth = columnWidth * 6;
|
const topDefaultWidth = columnWidth * 6;
|
||||||
const topDefaultHeight = headerHeight * 0.5;
|
const topDefaultHeight = headerHeight * 0.5;
|
||||||
for (let i = 0; i < dates.length; i++) {
|
for (let i = 0; i < dateSetup.dates.length; i++) {
|
||||||
const date = dates[i];
|
const date = dateSetup.dates[i];
|
||||||
const bottomValue = getLocaleMonth(date, locale);
|
const bottomValue = getLocaleMonth(date, locale);
|
||||||
bottomValues.push(
|
bottomValues.push(
|
||||||
<text
|
<text
|
||||||
@ -44,7 +45,10 @@ export const Calendar: React.FC<CalendarProps> = ({
|
|||||||
{bottomValue}
|
{bottomValue}
|
||||||
</text>
|
</text>
|
||||||
);
|
);
|
||||||
if (i === 0 || date.getFullYear() !== dates[i - 1].getFullYear()) {
|
if (
|
||||||
|
i === 0 ||
|
||||||
|
date.getFullYear() !== dateSetup.dates[i - 1].getFullYear()
|
||||||
|
) {
|
||||||
const topValue = date.getFullYear().toString();
|
const topValue = date.getFullYear().toString();
|
||||||
topValues.push(
|
topValues.push(
|
||||||
<TopPartOfCalendar
|
<TopPartOfCalendar
|
||||||
@ -69,6 +73,7 @@ export const Calendar: React.FC<CalendarProps> = ({
|
|||||||
const bottomValues: ReactChild[] = [];
|
const bottomValues: ReactChild[] = [];
|
||||||
let weeksCount: number = 1;
|
let weeksCount: number = 1;
|
||||||
const topDefaultHeight = headerHeight * 0.5;
|
const topDefaultHeight = headerHeight * 0.5;
|
||||||
|
const dates = dateSetup.dates;
|
||||||
for (let i = dates.length - 1; i >= 0; i--) {
|
for (let i = dates.length - 1; i >= 0; i--) {
|
||||||
const date = dates[i];
|
const date = dates[i];
|
||||||
let topValue = "";
|
let topValue = "";
|
||||||
@ -116,6 +121,7 @@ export const Calendar: React.FC<CalendarProps> = ({
|
|||||||
const topValues: ReactChild[] = [];
|
const topValues: ReactChild[] = [];
|
||||||
const bottomValues: ReactChild[] = [];
|
const bottomValues: ReactChild[] = [];
|
||||||
const topDefaultHeight = headerHeight * 0.5;
|
const topDefaultHeight = headerHeight * 0.5;
|
||||||
|
const dates = dateSetup.dates;
|
||||||
for (let i = 0; i < dates.length; i++) {
|
for (let i = 0; i < dates.length; i++) {
|
||||||
const date = dates[i];
|
const date = dates[i];
|
||||||
const bottomValue = date.getDate().toString();
|
const bottomValue = date.getDate().toString();
|
||||||
@ -157,7 +163,7 @@ export const Calendar: React.FC<CalendarProps> = ({
|
|||||||
const bottomValues: ReactChild[] = [];
|
const bottomValues: ReactChild[] = [];
|
||||||
const ticks = viewMode === ViewMode.HalfDay ? 2 : 4;
|
const ticks = viewMode === ViewMode.HalfDay ? 2 : 4;
|
||||||
const topDefaultHeight = headerHeight * 0.5;
|
const topDefaultHeight = headerHeight * 0.5;
|
||||||
|
const dates = dateSetup.dates;
|
||||||
for (let i = 0; i < dates.length; i++) {
|
for (let i = 0; i < dates.length; i++) {
|
||||||
const date = dates[i];
|
const date = dates[i];
|
||||||
const bottomValue = Intl.DateTimeFormat(locale, {
|
const bottomValue = Intl.DateTimeFormat(locale, {
|
||||||
@ -194,7 +200,7 @@ export const Calendar: React.FC<CalendarProps> = ({
|
|||||||
};
|
};
|
||||||
let topValues: ReactChild[] = [];
|
let topValues: ReactChild[] = [];
|
||||||
let bottomValues: ReactChild[] = [];
|
let bottomValues: ReactChild[] = [];
|
||||||
switch (viewMode) {
|
switch (dateSetup.viewMode) {
|
||||||
case ViewMode.Month:
|
case ViewMode.Month:
|
||||||
[topValues, bottomValues] = getCalendarValuesForMonth();
|
[topValues, bottomValues] = getCalendarValuesForMonth();
|
||||||
break;
|
break;
|
||||||
@ -213,7 +219,7 @@ export const Calendar: React.FC<CalendarProps> = ({
|
|||||||
<rect
|
<rect
|
||||||
x={0}
|
x={0}
|
||||||
y={0}
|
y={0}
|
||||||
width={columnWidth * dates.length}
|
width={columnWidth * dateSetup.dates.length}
|
||||||
height={headerHeight}
|
height={headerHeight}
|
||||||
className={styles.calendarHeader}
|
className={styles.calendarHeader}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { useState, SyntheticEvent, useRef, useEffect } from "react";
|
import React, { useState, SyntheticEvent, useRef, useEffect } from "react";
|
||||||
import { ViewMode, GanttProps, Task } from "../../types/public-types";
|
import { ViewMode, GanttProps } from "../../types/public-types";
|
||||||
import { GridProps } from "../grid/grid";
|
import { GridProps } from "../grid/grid";
|
||||||
import { ganttDateRange, seedDates } from "../../helpers/date-helper";
|
import { ganttDateRange, seedDates } from "../../helpers/date-helper";
|
||||||
import { CalendarProps } from "../calendar/calendar";
|
import { CalendarProps } from "../calendar/calendar";
|
||||||
@ -9,10 +9,14 @@ import { TaskListTableDefault } from "../task-list/task-list-table";
|
|||||||
import { StandardTooltipContent } from "../other/tooltip";
|
import { StandardTooltipContent } from "../other/tooltip";
|
||||||
import { Scroll } from "../other/scroll";
|
import { Scroll } from "../other/scroll";
|
||||||
import { TaskListProps, TaskList } from "../task-list/task-list";
|
import { TaskListProps, TaskList } from "../task-list/task-list";
|
||||||
import styles from "./gantt.module.css";
|
|
||||||
import { TaskGantt } from "./task-gantt";
|
import { TaskGantt } from "./task-gantt";
|
||||||
|
import { BarTask } from "../../types/bar-task";
|
||||||
|
import { convertToBarTasks } from "../../helpers/bar-helper";
|
||||||
|
import { GanttEvent } from "../../types/gantt-task-actions";
|
||||||
|
import { DateSetup } from "../../types/date-setup";
|
||||||
|
import styles from "./gantt.module.css";
|
||||||
|
|
||||||
export const Gantt: React.SFC<GanttProps> = ({
|
export const Gantt: React.FunctionComponent<GanttProps> = ({
|
||||||
tasks,
|
tasks,
|
||||||
headerHeight = 50,
|
headerHeight = 50,
|
||||||
columnWidth = 60,
|
columnWidth = 60,
|
||||||
@ -27,6 +31,12 @@ export const Gantt: React.SFC<GanttProps> = ({
|
|||||||
barProgressSelectedColor = "#8282f5",
|
barProgressSelectedColor = "#8282f5",
|
||||||
barBackgroundColor = "#b8c2cc",
|
barBackgroundColor = "#b8c2cc",
|
||||||
barBackgroundSelectedColor = "#aeb8c2",
|
barBackgroundSelectedColor = "#aeb8c2",
|
||||||
|
projectProgressColor = "#7db59a",
|
||||||
|
projectProgressSelectedColor = "#59a985",
|
||||||
|
projectBackgroundColor = "#fac465",
|
||||||
|
projectBackgroundSelectedColor = "#f7bb53",
|
||||||
|
milestoneBackgroundColor = "#f1c453",
|
||||||
|
milestoneBackgroundSelectedColor = "#f29e4c",
|
||||||
handleWidth = 8,
|
handleWidth = 8,
|
||||||
timeStep = 300000,
|
timeStep = 300000,
|
||||||
arrowColor = "grey",
|
arrowColor = "grey",
|
||||||
@ -40,25 +50,120 @@ export const Gantt: React.SFC<GanttProps> = ({
|
|||||||
onDateChange,
|
onDateChange,
|
||||||
onProgressChange,
|
onProgressChange,
|
||||||
onDoubleClick,
|
onDoubleClick,
|
||||||
onTaskDelete,
|
onDelete,
|
||||||
onSelect,
|
onSelect,
|
||||||
}) => {
|
}) => {
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||||
const [ganttTasks, setGanttTasks] = useState<Task[]>(tasks);
|
const [dateSetup, setDateSetup] = useState<DateSetup>(() => {
|
||||||
const [selectedTask, setSelectedTask] = useState<string>("");
|
const [startDate, endDate] = ganttDateRange(tasks, viewMode);
|
||||||
|
return { viewMode, dates: seedDates(startDate, endDate, viewMode) };
|
||||||
|
});
|
||||||
|
|
||||||
|
const [taskHeight, setTaskHeight] = useState((rowHeight * barFill) / 100);
|
||||||
|
const [barTasks, setBarTasks] = useState<BarTask[]>([]);
|
||||||
|
const [ganttEvent, setGanttEvent] = useState<GanttEvent>({
|
||||||
|
action: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const [selectedTask, setSelectedTask] = useState<BarTask>();
|
||||||
|
const [failedTask, setFailedTask] = useState<BarTask | null>(null);
|
||||||
|
|
||||||
const [scrollY, setScrollY] = useState(0);
|
const [scrollY, setScrollY] = useState(0);
|
||||||
const [scrollX, setScrollX] = useState(0);
|
const [scrollX, setScrollX] = useState(0);
|
||||||
const [ignoreScrollEvent, setIgnoreScrollEvent] = useState(false);
|
const [ignoreScrollEvent, setIgnoreScrollEvent] = useState(false);
|
||||||
const [startDate, endDate] = ganttDateRange(ganttTasks, viewMode);
|
|
||||||
const dates = seedDates(startDate, endDate, viewMode);
|
|
||||||
|
|
||||||
const svgHeight = rowHeight * ganttTasks.length;
|
const svgHeight = rowHeight * barTasks.length;
|
||||||
const gridWidth = dates.length * columnWidth;
|
const svgWidth = dateSetup.dates.length * columnWidth;
|
||||||
const ganttFullHeight = ganttTasks.length * rowHeight;
|
const ganttFullHeight = barTasks.length * rowHeight;
|
||||||
|
|
||||||
|
// task change events
|
||||||
|
useEffect(() => {
|
||||||
|
const [startDate, endDate] = ganttDateRange(tasks, viewMode);
|
||||||
|
const newDates = seedDates(startDate, endDate, viewMode);
|
||||||
|
setDateSetup({ dates: newDates, viewMode });
|
||||||
|
setBarTasks(
|
||||||
|
convertToBarTasks(
|
||||||
|
tasks,
|
||||||
|
newDates,
|
||||||
|
columnWidth,
|
||||||
|
rowHeight,
|
||||||
|
taskHeight,
|
||||||
|
barCornerRadius,
|
||||||
|
handleWidth,
|
||||||
|
barProgressColor,
|
||||||
|
barProgressSelectedColor,
|
||||||
|
barBackgroundColor,
|
||||||
|
barBackgroundSelectedColor,
|
||||||
|
projectProgressColor,
|
||||||
|
projectProgressSelectedColor,
|
||||||
|
projectBackgroundColor,
|
||||||
|
projectBackgroundSelectedColor,
|
||||||
|
milestoneBackgroundColor,
|
||||||
|
milestoneBackgroundSelectedColor
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, [
|
||||||
|
tasks,
|
||||||
|
viewMode,
|
||||||
|
rowHeight,
|
||||||
|
barCornerRadius,
|
||||||
|
columnWidth,
|
||||||
|
taskHeight,
|
||||||
|
handleWidth,
|
||||||
|
barProgressColor,
|
||||||
|
barProgressSelectedColor,
|
||||||
|
barBackgroundColor,
|
||||||
|
barBackgroundSelectedColor,
|
||||||
|
projectProgressColor,
|
||||||
|
projectProgressSelectedColor,
|
||||||
|
projectBackgroundColor,
|
||||||
|
projectBackgroundSelectedColor,
|
||||||
|
milestoneBackgroundColor,
|
||||||
|
milestoneBackgroundSelectedColor,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setGanttTasks(tasks);
|
const { changedTask, action } = ganttEvent;
|
||||||
}, [tasks]);
|
if (changedTask) {
|
||||||
|
if (action === "delete") {
|
||||||
|
setGanttEvent({ action: "" });
|
||||||
|
setBarTasks(barTasks.filter(t => t.id !== changedTask.id));
|
||||||
|
} else if (
|
||||||
|
action === "move" ||
|
||||||
|
action === "end" ||
|
||||||
|
action === "start" ||
|
||||||
|
action === "progress"
|
||||||
|
) {
|
||||||
|
const prevStateTask = barTasks.find(t => t.id === changedTask.id);
|
||||||
|
if (
|
||||||
|
prevStateTask &&
|
||||||
|
(prevStateTask.start.getTime() !== changedTask.start.getTime() ||
|
||||||
|
prevStateTask.end.getTime() !== changedTask.end.getTime() ||
|
||||||
|
prevStateTask.progress !== changedTask.progress)
|
||||||
|
) {
|
||||||
|
// actions for change
|
||||||
|
const newTaskList = barTasks.map(t =>
|
||||||
|
t.id === changedTask.id ? changedTask : t
|
||||||
|
);
|
||||||
|
setBarTasks(newTaskList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [ganttEvent, barTasks]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (failedTask) {
|
||||||
|
setBarTasks(barTasks.map(t => (t.id !== failedTask.id ? t : failedTask)));
|
||||||
|
setFailedTask(null);
|
||||||
|
}
|
||||||
|
}, [failedTask, barTasks]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const newTaskHeight = (rowHeight * barFill) / 100;
|
||||||
|
if (newTaskHeight !== taskHeight) {
|
||||||
|
setTaskHeight(newTaskHeight);
|
||||||
|
}
|
||||||
|
}, [rowHeight, barFill, taskHeight]);
|
||||||
|
|
||||||
// scroll events
|
// scroll events
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -79,7 +184,7 @@ export const Gantt: React.SFC<GanttProps> = ({
|
|||||||
if (
|
if (
|
||||||
wrapperRef.current &&
|
wrapperRef.current &&
|
||||||
ganttHeight &&
|
ganttHeight &&
|
||||||
ganttHeight < ganttTasks.length * rowHeight
|
ganttHeight < barTasks.length * rowHeight
|
||||||
) {
|
) {
|
||||||
wrapperRef.current.addEventListener("wheel", handleWheel, {
|
wrapperRef.current.addEventListener("wheel", handleWheel, {
|
||||||
passive: false,
|
passive: false,
|
||||||
@ -90,7 +195,7 @@ export const Gantt: React.SFC<GanttProps> = ({
|
|||||||
wrapperRef.current.removeEventListener("wheel", handleWheel);
|
wrapperRef.current.removeEventListener("wheel", handleWheel);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [wrapperRef.current, scrollY, ganttHeight, ganttTasks, rowHeight]);
|
}, [wrapperRef.current, scrollY, ganttHeight, barTasks, rowHeight]);
|
||||||
|
|
||||||
const handleScrollY = (event: SyntheticEvent<HTMLDivElement>) => {
|
const handleScrollY = (event: SyntheticEvent<HTMLDivElement>) => {
|
||||||
if (scrollY !== event.currentTarget.scrollTop && !ignoreScrollEvent) {
|
if (scrollY !== event.currentTarget.scrollTop && !ignoreScrollEvent) {
|
||||||
@ -136,64 +241,51 @@ export const Gantt: React.SFC<GanttProps> = ({
|
|||||||
}
|
}
|
||||||
if (isX) {
|
if (isX) {
|
||||||
if (newScrollX < 0) {
|
if (newScrollX < 0) {
|
||||||
setScrollX(0);
|
newScrollX = 0;
|
||||||
} else if (newScrollX > gridWidth) {
|
} else if (newScrollX > svgWidth) {
|
||||||
setScrollX(gridWidth);
|
newScrollX = svgWidth;
|
||||||
} else {
|
|
||||||
setScrollX(newScrollX);
|
|
||||||
}
|
}
|
||||||
|
setScrollX(newScrollX);
|
||||||
} else {
|
} else {
|
||||||
if (newScrollY < 0) {
|
if (newScrollY < 0) {
|
||||||
setScrollY(0);
|
newScrollY = 0;
|
||||||
} else if (newScrollY > ganttFullHeight - ganttHeight) {
|
} else if (newScrollY > ganttFullHeight - ganttHeight) {
|
||||||
setScrollY(ganttFullHeight - ganttHeight);
|
newScrollY = ganttFullHeight - ganttHeight;
|
||||||
} else {
|
|
||||||
setScrollY(newScrollY);
|
|
||||||
}
|
}
|
||||||
|
setScrollY(newScrollY);
|
||||||
}
|
}
|
||||||
setIgnoreScrollEvent(true);
|
setIgnoreScrollEvent(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
// task change event
|
|
||||||
const handleTasksChange = (tasks: Task[]) => {
|
|
||||||
setGanttTasks(tasks);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Task select event
|
* Task select event
|
||||||
*/
|
*/
|
||||||
const handleSelectedTask = (taskId: string) => {
|
const handleSelectedTask = (taskId: string) => {
|
||||||
const newSelectedTask = ganttTasks.find(t => t.id === taskId);
|
const newSelectedTask = barTasks.find(t => t.id === taskId);
|
||||||
if (newSelectedTask) {
|
const oldSelectedTask = barTasks.find(
|
||||||
if (onSelect) {
|
t => !!selectedTask && t.id === selectedTask.id
|
||||||
const oldSelectedTask = ganttTasks.find(t => t.id === selectedTask);
|
);
|
||||||
if (oldSelectedTask) {
|
if (onSelect) {
|
||||||
onSelect(oldSelectedTask, false);
|
if (oldSelectedTask) {
|
||||||
}
|
onSelect(oldSelectedTask, false);
|
||||||
|
}
|
||||||
|
if (newSelectedTask) {
|
||||||
onSelect(newSelectedTask, true);
|
onSelect(newSelectedTask, true);
|
||||||
}
|
}
|
||||||
setSelectedTask(newSelectedTask.id);
|
|
||||||
} else {
|
|
||||||
if (onSelect) {
|
|
||||||
const oldSelectedTask = ganttTasks.find(t => t.id === selectedTask);
|
|
||||||
if (oldSelectedTask) {
|
|
||||||
onSelect(oldSelectedTask, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setSelectedTask("");
|
|
||||||
}
|
}
|
||||||
|
setSelectedTask(newSelectedTask);
|
||||||
};
|
};
|
||||||
|
|
||||||
const gridProps: GridProps = {
|
const gridProps: GridProps = {
|
||||||
columnWidth,
|
columnWidth,
|
||||||
gridWidth,
|
svgWidth,
|
||||||
tasks: ganttTasks,
|
tasks: tasks,
|
||||||
rowHeight,
|
rowHeight,
|
||||||
dates,
|
dates: dateSetup.dates,
|
||||||
todayColor,
|
todayColor,
|
||||||
};
|
};
|
||||||
const calendarProps: CalendarProps = {
|
const calendarProps: CalendarProps = {
|
||||||
dates,
|
dateSetup,
|
||||||
locale,
|
locale,
|
||||||
viewMode,
|
viewMode,
|
||||||
headerHeight,
|
headerHeight,
|
||||||
@ -202,30 +294,27 @@ export const Gantt: React.SFC<GanttProps> = ({
|
|||||||
fontSize,
|
fontSize,
|
||||||
};
|
};
|
||||||
const barProps: TaskGanttContentProps = {
|
const barProps: TaskGanttContentProps = {
|
||||||
tasks: ganttTasks,
|
tasks: barTasks,
|
||||||
|
dates: dateSetup.dates,
|
||||||
|
ganttEvent,
|
||||||
selectedTask,
|
selectedTask,
|
||||||
setSelectedTask: handleSelectedTask,
|
|
||||||
rowHeight,
|
rowHeight,
|
||||||
barCornerRadius,
|
taskHeight,
|
||||||
columnWidth,
|
columnWidth,
|
||||||
dates,
|
|
||||||
barFill,
|
|
||||||
barProgressColor,
|
|
||||||
barProgressSelectedColor,
|
|
||||||
barBackgroundColor,
|
|
||||||
barBackgroundSelectedColor,
|
|
||||||
handleWidth,
|
|
||||||
arrowColor,
|
arrowColor,
|
||||||
timeStep,
|
timeStep,
|
||||||
fontFamily,
|
fontFamily,
|
||||||
fontSize,
|
fontSize,
|
||||||
arrowIndent,
|
arrowIndent,
|
||||||
svgHeight,
|
svgHeight,
|
||||||
onTasksChange: handleTasksChange,
|
svgWidth,
|
||||||
|
setGanttEvent,
|
||||||
|
setFailedTask,
|
||||||
|
setSelectedTask: handleSelectedTask,
|
||||||
onDateChange,
|
onDateChange,
|
||||||
onProgressChange,
|
onProgressChange,
|
||||||
onDoubleClick,
|
onDoubleClick,
|
||||||
onTaskDelete,
|
onDelete,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -234,18 +323,17 @@ export const Gantt: React.SFC<GanttProps> = ({
|
|||||||
rowWidth: listCellWidth,
|
rowWidth: listCellWidth,
|
||||||
fontFamily,
|
fontFamily,
|
||||||
fontSize,
|
fontSize,
|
||||||
tasks: ganttTasks,
|
tasks: barTasks,
|
||||||
locale,
|
locale,
|
||||||
headerHeight,
|
headerHeight,
|
||||||
scrollY,
|
scrollY,
|
||||||
ganttHeight,
|
ganttHeight,
|
||||||
horizontalContainerClass: styles.horizontalContainer,
|
horizontalContainerClass: styles.horizontalContainer,
|
||||||
selectedTaskId: selectedTask,
|
selectedTask,
|
||||||
setSelectedTask: handleSelectedTask,
|
setSelectedTask: handleSelectedTask,
|
||||||
TaskListHeader,
|
TaskListHeader,
|
||||||
TaskListTable,
|
TaskListTable,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.wrapper}
|
className={styles.wrapper}
|
||||||
|
|||||||
@ -1,91 +1,73 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Task, EventOption } from "../../types/public-types";
|
import { Task, EventOption } from "../../types/public-types";
|
||||||
import { Bar } from "../bar/bar";
|
|
||||||
import { BarTask } from "../../types/bar-task";
|
import { BarTask } from "../../types/bar-task";
|
||||||
import { Arrow } from "../other/arrow";
|
import { Arrow } from "../other/arrow";
|
||||||
import {
|
import { handleTaskBySVGMouseEvent } from "../../helpers/bar-helper";
|
||||||
convertToBarTasks,
|
|
||||||
handleTaskBySVGMouseEvent,
|
|
||||||
BarMoveAction,
|
|
||||||
} from "../../helpers/bar-helper";
|
|
||||||
import { Tooltip } from "../other/tooltip";
|
import { Tooltip } from "../other/tooltip";
|
||||||
import { isKeyboardEvent } from "../../helpers/other-helper";
|
import { isKeyboardEvent } from "../../helpers/other-helper";
|
||||||
|
import { TaskItem } from "../task-item/task-item";
|
||||||
|
import {
|
||||||
|
BarMoveAction,
|
||||||
|
GanttContentMoveAction,
|
||||||
|
GanttEvent,
|
||||||
|
} from "../../types/gantt-task-actions";
|
||||||
|
|
||||||
export type GanttContentMoveAction =
|
|
||||||
| "mouseenter"
|
|
||||||
| "mouseleave"
|
|
||||||
| "delete"
|
|
||||||
| "dblclick"
|
|
||||||
| "select"
|
|
||||||
| BarMoveAction;
|
|
||||||
export type BarEvent = {
|
|
||||||
changedTask?: BarTask;
|
|
||||||
originalTask?: BarTask;
|
|
||||||
action: GanttContentMoveAction;
|
|
||||||
};
|
|
||||||
export type TaskGanttContentProps = {
|
export type TaskGanttContentProps = {
|
||||||
tasks: Task[];
|
tasks: BarTask[];
|
||||||
dates: Date[];
|
dates: Date[];
|
||||||
selectedTask: string;
|
ganttEvent: GanttEvent;
|
||||||
|
selectedTask: BarTask | undefined;
|
||||||
rowHeight: number;
|
rowHeight: number;
|
||||||
barCornerRadius: number;
|
|
||||||
columnWidth: number;
|
columnWidth: number;
|
||||||
barFill: number;
|
|
||||||
barProgressColor: string;
|
|
||||||
barProgressSelectedColor: string;
|
|
||||||
barBackgroundColor: string;
|
|
||||||
barBackgroundSelectedColor: string;
|
|
||||||
handleWidth: number;
|
|
||||||
timeStep: number;
|
timeStep: number;
|
||||||
svg?: React.RefObject<SVGSVGElement>;
|
svg?: React.RefObject<SVGSVGElement>;
|
||||||
svgHeight: number;
|
svgHeight: number;
|
||||||
|
svgWidth: number;
|
||||||
|
displayXStartEndpoint?: {
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
};
|
||||||
|
taskHeight: number;
|
||||||
arrowColor: string;
|
arrowColor: string;
|
||||||
arrowIndent: number;
|
arrowIndent: number;
|
||||||
fontSize: string;
|
fontSize: string;
|
||||||
fontFamily: string;
|
fontFamily: string;
|
||||||
|
setGanttEvent: (value: GanttEvent) => void;
|
||||||
|
setFailedTask: (value: BarTask | null) => void;
|
||||||
setSelectedTask: (taskId: string) => void;
|
setSelectedTask: (taskId: string) => void;
|
||||||
TooltipContent: React.FC<{
|
TooltipContent: React.FC<{
|
||||||
task: Task;
|
task: Task;
|
||||||
fontSize: string;
|
fontSize: string;
|
||||||
fontFamily: string;
|
fontFamily: string;
|
||||||
}>;
|
}>;
|
||||||
onTasksChange: (tasks: Task[]) => void;
|
|
||||||
} & EventOption;
|
} & EventOption;
|
||||||
|
|
||||||
export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||||
tasks,
|
tasks,
|
||||||
dates,
|
dates,
|
||||||
|
ganttEvent,
|
||||||
selectedTask,
|
selectedTask,
|
||||||
rowHeight,
|
rowHeight,
|
||||||
barCornerRadius,
|
|
||||||
columnWidth,
|
columnWidth,
|
||||||
barFill,
|
|
||||||
barProgressColor,
|
|
||||||
barProgressSelectedColor,
|
|
||||||
barBackgroundColor,
|
|
||||||
barBackgroundSelectedColor,
|
|
||||||
handleWidth,
|
|
||||||
timeStep,
|
timeStep,
|
||||||
svg,
|
svg,
|
||||||
svgHeight,
|
svgHeight,
|
||||||
|
displayXStartEndpoint,
|
||||||
|
taskHeight,
|
||||||
arrowColor,
|
arrowColor,
|
||||||
arrowIndent,
|
arrowIndent,
|
||||||
fontFamily,
|
fontFamily,
|
||||||
fontSize,
|
fontSize,
|
||||||
|
setGanttEvent,
|
||||||
|
setFailedTask,
|
||||||
setSelectedTask,
|
setSelectedTask,
|
||||||
onTasksChange,
|
|
||||||
onDateChange,
|
onDateChange,
|
||||||
onProgressChange,
|
onProgressChange,
|
||||||
onDoubleClick,
|
onDoubleClick,
|
||||||
onTaskDelete,
|
onDelete,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
}) => {
|
}) => {
|
||||||
const point = svg?.current?.createSVGPoint();
|
const point = svg?.current?.createSVGPoint();
|
||||||
const [barEvent, setBarEvent] = useState<BarEvent>({
|
|
||||||
action: "",
|
|
||||||
});
|
|
||||||
const [barTasks, setBarTasks] = useState<BarTask[]>([]);
|
|
||||||
const [failedTask, setFailedTask] = useState<BarTask | null>(null);
|
|
||||||
const [xStep, setXStep] = useState(0);
|
const [xStep, setXStep] = useState(0);
|
||||||
const [initEventX1Delta, setInitEventX1Delta] = useState(0);
|
const [initEventX1Delta, setInitEventX1Delta] = useState(0);
|
||||||
const [isMoving, setIsMoving] = useState(false);
|
const [isMoving, setIsMoving] = useState(false);
|
||||||
@ -101,51 +83,9 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
|||||||
setXStep(newXStep);
|
setXStep(newXStep);
|
||||||
}, [columnWidth, dates, timeStep]);
|
}, [columnWidth, dates, timeStep]);
|
||||||
|
|
||||||
// generate tasks
|
|
||||||
useEffect(() => {
|
|
||||||
setBarTasks(
|
|
||||||
convertToBarTasks(
|
|
||||||
tasks,
|
|
||||||
dates,
|
|
||||||
columnWidth,
|
|
||||||
rowHeight,
|
|
||||||
barFill,
|
|
||||||
barCornerRadius,
|
|
||||||
handleWidth,
|
|
||||||
barProgressColor,
|
|
||||||
barProgressSelectedColor,
|
|
||||||
barBackgroundColor,
|
|
||||||
barBackgroundSelectedColor
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}, [
|
|
||||||
tasks,
|
|
||||||
rowHeight,
|
|
||||||
barCornerRadius,
|
|
||||||
columnWidth,
|
|
||||||
dates,
|
|
||||||
barFill,
|
|
||||||
handleWidth,
|
|
||||||
barProgressColor,
|
|
||||||
barProgressSelectedColor,
|
|
||||||
barBackgroundColor,
|
|
||||||
barBackgroundSelectedColor,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// on failed task update
|
|
||||||
useEffect(() => {
|
|
||||||
if (failedTask) {
|
|
||||||
const newTasks = barTasks.map(t =>
|
|
||||||
t.id === failedTask.id ? failedTask : t
|
|
||||||
);
|
|
||||||
onTasksChange(newTasks);
|
|
||||||
setFailedTask(null);
|
|
||||||
}
|
|
||||||
}, [failedTask, barTasks]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleMouseMove = async (event: MouseEvent) => {
|
const handleMouseMove = async (event: MouseEvent) => {
|
||||||
if (!barEvent.changedTask || !point || !svg?.current) return;
|
if (!ganttEvent.changedTask || !point || !svg?.current) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
point.x = event.clientX;
|
point.x = event.clientX;
|
||||||
@ -155,54 +95,46 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
|||||||
|
|
||||||
const { isChanged, changedTask } = handleTaskBySVGMouseEvent(
|
const { isChanged, changedTask } = handleTaskBySVGMouseEvent(
|
||||||
cursor.x,
|
cursor.x,
|
||||||
barEvent.action as BarMoveAction,
|
ganttEvent.action as BarMoveAction,
|
||||||
barEvent.changedTask,
|
ganttEvent.changedTask,
|
||||||
xStep,
|
xStep,
|
||||||
timeStep,
|
timeStep,
|
||||||
initEventX1Delta
|
initEventX1Delta
|
||||||
);
|
);
|
||||||
if (isChanged) {
|
if (isChanged) {
|
||||||
setBarTasks(
|
setGanttEvent({ action: ganttEvent.action, changedTask });
|
||||||
barTasks.map(t => (t.id === changedTask.id ? changedTask : t))
|
|
||||||
);
|
|
||||||
setBarEvent({ ...barEvent, changedTask: changedTask });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseUp = async (event: MouseEvent) => {
|
const handleMouseUp = async (event: MouseEvent) => {
|
||||||
const { changedTask: selectedTask, action, originalTask } = barEvent;
|
const { action, originalSelectedTask, changedTask } = ganttEvent;
|
||||||
|
if (!changedTask || !point || !svg?.current || !originalSelectedTask)
|
||||||
if (!selectedTask || !point || !svg?.current || !originalTask) return;
|
return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
point.x = event.clientX;
|
point.x = event.clientX;
|
||||||
const cursor = point.matrixTransform(
|
const cursor = point.matrixTransform(
|
||||||
svg?.current.getScreenCTM()?.inverse()
|
svg?.current.getScreenCTM()?.inverse()
|
||||||
);
|
);
|
||||||
|
const { changedTask: newChangedTask } = handleTaskBySVGMouseEvent(
|
||||||
const { changedTask } = handleTaskBySVGMouseEvent(
|
|
||||||
cursor.x,
|
cursor.x,
|
||||||
action as BarMoveAction,
|
action as BarMoveAction,
|
||||||
selectedTask,
|
changedTask,
|
||||||
xStep,
|
xStep,
|
||||||
timeStep,
|
timeStep,
|
||||||
initEventX1Delta
|
initEventX1Delta
|
||||||
);
|
);
|
||||||
|
|
||||||
const isNotLikeOriginal =
|
const isNotLikeOriginal =
|
||||||
originalTask.start !== changedTask.start ||
|
originalSelectedTask.start !== newChangedTask.start ||
|
||||||
originalTask.end !== changedTask.end ||
|
originalSelectedTask.end !== newChangedTask.end ||
|
||||||
originalTask.progress !== changedTask.progress;
|
originalSelectedTask.progress !== newChangedTask.progress;
|
||||||
|
|
||||||
// remove listeners
|
// remove listeners
|
||||||
svg.current.removeEventListener("mousemove", handleMouseMove);
|
svg.current.removeEventListener("mousemove", handleMouseMove);
|
||||||
svg.current.removeEventListener("mouseup", handleMouseUp);
|
svg.current.removeEventListener("mouseup", handleMouseUp);
|
||||||
setBarEvent({ action: "" });
|
setGanttEvent({ action: "" });
|
||||||
setIsMoving(false);
|
setIsMoving(false);
|
||||||
const newTasks = barTasks.map(t =>
|
|
||||||
t.id === changedTask.id ? changedTask : t
|
|
||||||
);
|
|
||||||
onTasksChange(newTasks);
|
|
||||||
|
|
||||||
// custom operation start
|
// custom operation start
|
||||||
let operationSuccess = true;
|
let operationSuccess = true;
|
||||||
@ -212,7 +144,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
|||||||
isNotLikeOriginal
|
isNotLikeOriginal
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const result = await onDateChange(changedTask);
|
const result = await onDateChange(newChangedTask);
|
||||||
if (result !== undefined) {
|
if (result !== undefined) {
|
||||||
operationSuccess = result;
|
operationSuccess = result;
|
||||||
}
|
}
|
||||||
@ -221,7 +153,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
|||||||
}
|
}
|
||||||
} else if (onProgressChange && isNotLikeOriginal) {
|
} else if (onProgressChange && isNotLikeOriginal) {
|
||||||
try {
|
try {
|
||||||
const result = await onProgressChange(changedTask);
|
const result = await onProgressChange(newChangedTask);
|
||||||
if (result !== undefined) {
|
if (result !== undefined) {
|
||||||
operationSuccess = result;
|
operationSuccess = result;
|
||||||
}
|
}
|
||||||
@ -232,16 +164,16 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
|||||||
|
|
||||||
// If operation is failed - return old state
|
// If operation is failed - return old state
|
||||||
if (!operationSuccess) {
|
if (!operationSuccess) {
|
||||||
setFailedTask(originalTask);
|
setFailedTask(originalSelectedTask);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isMoving &&
|
!isMoving &&
|
||||||
(barEvent.action === "move" ||
|
(ganttEvent.action === "move" ||
|
||||||
barEvent.action === "end" ||
|
ganttEvent.action === "end" ||
|
||||||
barEvent.action === "start" ||
|
ganttEvent.action === "start" ||
|
||||||
barEvent.action === "progress") &&
|
ganttEvent.action === "progress") &&
|
||||||
svg?.current
|
svg?.current
|
||||||
) {
|
) {
|
||||||
svg.current.addEventListener("mousemove", handleMouseMove);
|
svg.current.addEventListener("mousemove", handleMouseMove);
|
||||||
@ -249,8 +181,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
|||||||
setIsMoving(true);
|
setIsMoving(true);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
barTasks,
|
ganttEvent,
|
||||||
barEvent,
|
|
||||||
xStep,
|
xStep,
|
||||||
initEventX1Delta,
|
initEventX1Delta,
|
||||||
onProgressChange,
|
onProgressChange,
|
||||||
@ -276,13 +207,11 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
|||||||
// Keyboard events
|
// Keyboard events
|
||||||
else if (isKeyboardEvent(event)) {
|
else if (isKeyboardEvent(event)) {
|
||||||
if (action === "delete") {
|
if (action === "delete") {
|
||||||
if (onTaskDelete) {
|
if (onDelete) {
|
||||||
try {
|
try {
|
||||||
const result = await onTaskDelete(task);
|
const result = await onDelete(task);
|
||||||
if (result !== undefined && result) {
|
if (result !== undefined && result) {
|
||||||
const newTasks = barTasks.filter(t => t.id !== task.id);
|
setGanttEvent({ action, changedTask: task });
|
||||||
onTasksChange(newTasks);
|
|
||||||
setSelectedTask("");
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error on Delete. " + error);
|
console.error("Error on Delete. " + error);
|
||||||
@ -292,16 +221,16 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
|||||||
}
|
}
|
||||||
// Mouse Events
|
// Mouse Events
|
||||||
else if (action === "mouseenter") {
|
else if (action === "mouseenter") {
|
||||||
if (!barEvent.action) {
|
if (!ganttEvent.action) {
|
||||||
setBarEvent({
|
setGanttEvent({
|
||||||
action,
|
action,
|
||||||
changedTask: task,
|
changedTask: task,
|
||||||
originalTask: task,
|
originalSelectedTask: task,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (action === "mouseleave") {
|
} else if (action === "mouseleave") {
|
||||||
if (barEvent.action === "mouseenter") {
|
if (ganttEvent.action === "mouseenter") {
|
||||||
setBarEvent({ action: "" });
|
setGanttEvent({ action: "" });
|
||||||
}
|
}
|
||||||
} else if (action === "dblclick") {
|
} else if (action === "dblclick") {
|
||||||
!!onDoubleClick && onDoubleClick(task);
|
!!onDoubleClick && onDoubleClick(task);
|
||||||
@ -314,16 +243,16 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
|||||||
svg.current.getScreenCTM()?.inverse()
|
svg.current.getScreenCTM()?.inverse()
|
||||||
);
|
);
|
||||||
setInitEventX1Delta(cursor.x - task.x1);
|
setInitEventX1Delta(cursor.x - task.x1);
|
||||||
setBarEvent({
|
setGanttEvent({
|
||||||
action,
|
action,
|
||||||
changedTask: task,
|
changedTask: task,
|
||||||
originalTask: task,
|
originalSelectedTask: task,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setBarEvent({
|
setGanttEvent({
|
||||||
action,
|
action,
|
||||||
changedTask: task,
|
changedTask: task,
|
||||||
originalTask: task,
|
originalSelectedTask: task,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -331,14 +260,15 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
|||||||
return (
|
return (
|
||||||
<g className="content">
|
<g className="content">
|
||||||
<g className="arrows" fill={arrowColor} stroke={arrowColor}>
|
<g className="arrows" fill={arrowColor} stroke={arrowColor}>
|
||||||
{barTasks.map(task => {
|
{tasks.map(task => {
|
||||||
return task.barChildren.map(child => {
|
return task.barChildren.map(child => {
|
||||||
return (
|
return (
|
||||||
<Arrow
|
<Arrow
|
||||||
key={`Arrow from ${task.id} to ${barTasks[child].id}`}
|
key={`Arrow from ${task.id} to ${tasks[child].id}`}
|
||||||
taskFrom={task}
|
taskFrom={task}
|
||||||
taskTo={barTasks[child]}
|
taskTo={tasks[child]}
|
||||||
rowHeight={rowHeight}
|
rowHeight={rowHeight}
|
||||||
|
taskHeight={taskHeight}
|
||||||
arrowIndent={arrowIndent}
|
arrowIndent={arrowIndent}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -346,28 +276,30 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
|||||||
})}
|
})}
|
||||||
</g>
|
</g>
|
||||||
<g className="bar" fontFamily={fontFamily} fontSize={fontSize}>
|
<g className="bar" fontFamily={fontFamily} fontSize={fontSize}>
|
||||||
{barTasks.map(task => {
|
{tasks.map(task => {
|
||||||
return (
|
return (
|
||||||
<Bar
|
<TaskItem
|
||||||
task={task}
|
task={task}
|
||||||
arrowIndent={arrowIndent}
|
arrowIndent={arrowIndent}
|
||||||
|
taskHeight={taskHeight}
|
||||||
isProgressChangeable={!!onProgressChange && !task.isDisabled}
|
isProgressChangeable={!!onProgressChange && !task.isDisabled}
|
||||||
isDateChangeable={!!onDateChange && !task.isDisabled}
|
isDateChangeable={!!onDateChange && !task.isDisabled}
|
||||||
isDelete={!task.isDisabled}
|
isDelete={!task.isDisabled}
|
||||||
onEventStart={handleBarEventStart}
|
onEventStart={handleBarEventStart}
|
||||||
key={task.id}
|
key={task.id}
|
||||||
isSelected={task.id === selectedTask}
|
isSelected={!!selectedTask && task.id === selectedTask.id}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</g>
|
</g>
|
||||||
<g className="toolTip">
|
<g className="toolTip">
|
||||||
{barEvent.changedTask && (
|
{ganttEvent.changedTask && displayXStartEndpoint && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
x={barEvent.changedTask.x2 + arrowIndent + arrowIndent * 0.5}
|
arrowIndent={arrowIndent}
|
||||||
rowHeight={rowHeight}
|
rowHeight={rowHeight}
|
||||||
svgHeight={svgHeight}
|
svgHeight={svgHeight}
|
||||||
task={barEvent.changedTask}
|
displayXStartEndpoint={displayXStartEndpoint}
|
||||||
|
task={ganttEvent.changedTask}
|
||||||
fontFamily={fontFamily}
|
fontFamily={fontFamily}
|
||||||
fontSize={fontSize}
|
fontSize={fontSize}
|
||||||
TooltipContent={TooltipContent}
|
TooltipContent={TooltipContent}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useRef, useEffect, SyntheticEvent } from "react";
|
import React, { useRef, useEffect, SyntheticEvent, useState } from "react";
|
||||||
import { GridProps, Grid } from "../grid/grid";
|
import { GridProps, Grid } from "../grid/grid";
|
||||||
import { CalendarProps, Calendar } from "../calendar/calendar";
|
import { CalendarProps, Calendar } from "../calendar/calendar";
|
||||||
import { TaskGanttContentProps, TaskGanttContent } from "./task-gantt-content";
|
import { TaskGanttContentProps, TaskGanttContent } from "./task-gantt-content";
|
||||||
@ -25,7 +25,11 @@ export const TaskGantt: React.FC<TaskGanttProps> = ({
|
|||||||
const ganttSVGRef = useRef<SVGSVGElement>(null);
|
const ganttSVGRef = useRef<SVGSVGElement>(null);
|
||||||
const horizontalContainerRef = useRef<HTMLDivElement>(null);
|
const horizontalContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const verticalContainerRef = useRef<HTMLDivElement>(null);
|
const verticalContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const newBarProps = { ...barProps, svg: ganttSVGRef };
|
const [displayXStartEndpoint, setDisplayXStartEndpoint] = useState({
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
});
|
||||||
|
const newBarProps = { ...barProps, svg: ganttSVGRef, displayXStartEndpoint };
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (horizontalContainerRef.current) {
|
if (horizontalContainerRef.current) {
|
||||||
@ -36,8 +40,13 @@ export const TaskGantt: React.FC<TaskGanttProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (verticalContainerRef.current) {
|
if (verticalContainerRef.current) {
|
||||||
verticalContainerRef.current.scrollLeft = scrollX;
|
verticalContainerRef.current.scrollLeft = scrollX;
|
||||||
|
setDisplayXStartEndpoint({
|
||||||
|
start: scrollX,
|
||||||
|
end: verticalContainerRef.current.clientWidth + scrollX,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [scrollX]);
|
// verticalContainerRef.current?.clientWidth need for resize window tracking
|
||||||
|
}, [scrollX, verticalContainerRef.current?.clientWidth]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -47,7 +56,7 @@ export const TaskGantt: React.FC<TaskGanttProps> = ({
|
|||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width={gridProps.gridWidth}
|
width={gridProps.svgWidth}
|
||||||
height={calendarProps.headerHeight}
|
height={calendarProps.headerHeight}
|
||||||
fontFamily={barProps.fontFamily}
|
fontFamily={barProps.fontFamily}
|
||||||
>
|
>
|
||||||
@ -58,13 +67,13 @@ export const TaskGantt: React.FC<TaskGanttProps> = ({
|
|||||||
className={styles.horizontalContainer}
|
className={styles.horizontalContainer}
|
||||||
style={
|
style={
|
||||||
ganttHeight
|
ganttHeight
|
||||||
? { height: ganttHeight, width: gridProps.gridWidth }
|
? { height: ganttHeight, width: gridProps.svgWidth }
|
||||||
: { width: gridProps.gridWidth }
|
: { width: gridProps.svgWidth }
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width={gridProps.gridWidth}
|
width={gridProps.svgWidth}
|
||||||
height={barProps.rowHeight * barProps.tasks.length}
|
height={barProps.rowHeight * barProps.tasks.length}
|
||||||
fontFamily={barProps.fontFamily}
|
fontFamily={barProps.fontFamily}
|
||||||
ref={ganttSVGRef}
|
ref={ganttSVGRef}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import styles from "./grid.module.css";
|
|||||||
export type GridBodyProps = {
|
export type GridBodyProps = {
|
||||||
tasks: Task[];
|
tasks: Task[];
|
||||||
dates: Date[];
|
dates: Date[];
|
||||||
gridWidth: number;
|
svgWidth: number;
|
||||||
rowHeight: number;
|
rowHeight: number;
|
||||||
columnWidth: number;
|
columnWidth: number;
|
||||||
todayColor: string;
|
todayColor: string;
|
||||||
@ -15,7 +15,7 @@ export const GridBody: React.FC<GridBodyProps> = ({
|
|||||||
tasks,
|
tasks,
|
||||||
dates,
|
dates,
|
||||||
rowHeight,
|
rowHeight,
|
||||||
gridWidth,
|
svgWidth,
|
||||||
columnWidth,
|
columnWidth,
|
||||||
todayColor,
|
todayColor,
|
||||||
}) => {
|
}) => {
|
||||||
@ -26,7 +26,7 @@ export const GridBody: React.FC<GridBodyProps> = ({
|
|||||||
key="RowLineFirst"
|
key="RowLineFirst"
|
||||||
x="0"
|
x="0"
|
||||||
y1={0}
|
y1={0}
|
||||||
x2={gridWidth}
|
x2={svgWidth}
|
||||||
y2={0}
|
y2={0}
|
||||||
className={styles.gridRowLine}
|
className={styles.gridRowLine}
|
||||||
/>,
|
/>,
|
||||||
@ -37,7 +37,7 @@ export const GridBody: React.FC<GridBodyProps> = ({
|
|||||||
key={"Row" + task.id}
|
key={"Row" + task.id}
|
||||||
x="0"
|
x="0"
|
||||||
y={y}
|
y={y}
|
||||||
width={gridWidth}
|
width={svgWidth}
|
||||||
height={rowHeight}
|
height={rowHeight}
|
||||||
className={styles.gridRow}
|
className={styles.gridRow}
|
||||||
/>
|
/>
|
||||||
@ -47,7 +47,7 @@ export const GridBody: React.FC<GridBodyProps> = ({
|
|||||||
key={"RowLine" + task.id}
|
key={"RowLine" + task.id}
|
||||||
x="0"
|
x="0"
|
||||||
y1={y + rowHeight}
|
y1={y + rowHeight}
|
||||||
x2={gridWidth}
|
x2={svgWidth}
|
||||||
y2={y + rowHeight}
|
y2={y + rowHeight}
|
||||||
className={styles.gridRowLine}
|
className={styles.gridRowLine}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -5,18 +5,20 @@ type ArrowProps = {
|
|||||||
taskFrom: BarTask;
|
taskFrom: BarTask;
|
||||||
taskTo: BarTask;
|
taskTo: BarTask;
|
||||||
rowHeight: number;
|
rowHeight: number;
|
||||||
|
taskHeight: number;
|
||||||
arrowIndent: number;
|
arrowIndent: number;
|
||||||
};
|
};
|
||||||
export const Arrow: React.FC<ArrowProps> = ({
|
export const Arrow: React.FC<ArrowProps> = ({
|
||||||
taskFrom,
|
taskFrom,
|
||||||
taskTo,
|
taskTo,
|
||||||
rowHeight,
|
rowHeight,
|
||||||
|
taskHeight,
|
||||||
arrowIndent,
|
arrowIndent,
|
||||||
}) => {
|
}) => {
|
||||||
const indexCompare = taskFrom.index > taskTo.index ? -1 : 1;
|
const indexCompare = taskFrom.index > taskTo.index ? -1 : 1;
|
||||||
const taskToEndPosition = taskTo.y + taskTo.height / 2;
|
const taskToEndPosition = taskTo.y + taskHeight / 2;
|
||||||
|
|
||||||
const path = `M ${taskFrom.x2} ${taskFrom.y + taskFrom.height / 2}
|
const path = `M ${taskFrom.x2} ${taskFrom.y + taskHeight / 2}
|
||||||
h ${arrowIndent}
|
h ${arrowIndent}
|
||||||
v ${(indexCompare * rowHeight) / 2}
|
v ${(indexCompare * rowHeight) / 2}
|
||||||
H ${taskTo.x1 - arrowIndent}
|
H ${taskTo.x1 - arrowIndent}
|
||||||
|
|||||||
@ -4,10 +4,14 @@ import { BarTask } from "../../types/bar-task";
|
|||||||
import styles from "./tooltip.module.css";
|
import styles from "./tooltip.module.css";
|
||||||
|
|
||||||
export type TooltipProps = {
|
export type TooltipProps = {
|
||||||
x: number;
|
|
||||||
svgHeight: number;
|
|
||||||
rowHeight: number;
|
|
||||||
task: BarTask;
|
task: BarTask;
|
||||||
|
arrowIndent: number;
|
||||||
|
svgHeight: number;
|
||||||
|
displayXStartEndpoint: {
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
};
|
||||||
|
rowHeight: number;
|
||||||
fontSize: string;
|
fontSize: string;
|
||||||
fontFamily: string;
|
fontFamily: string;
|
||||||
TooltipContent: React.FC<{
|
TooltipContent: React.FC<{
|
||||||
@ -17,31 +21,77 @@ export type TooltipProps = {
|
|||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
export const Tooltip: React.FC<TooltipProps> = ({
|
export const Tooltip: React.FC<TooltipProps> = ({
|
||||||
x,
|
task,
|
||||||
rowHeight,
|
rowHeight,
|
||||||
svgHeight,
|
svgHeight,
|
||||||
task,
|
displayXStartEndpoint,
|
||||||
|
arrowIndent,
|
||||||
fontSize,
|
fontSize,
|
||||||
fontFamily,
|
fontFamily,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
}) => {
|
}) => {
|
||||||
const tooltipRef = useRef<HTMLDivElement | null>(null);
|
const tooltipRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [toolWidth, setToolWidth] = useState(1000);
|
const [toolWidth, setToolWidth] = useState(1000);
|
||||||
const [relatedY, setRelatedY] = useState((task.index - 1) * rowHeight);
|
const [toolHeight, setToolHeight] = useState(1000);
|
||||||
|
const [relatedY, setRelatedY] = useState(task.index * rowHeight);
|
||||||
|
const [relatedX, setRelatedX] = useState(displayXStartEndpoint.end);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tooltipRef.current) {
|
if (tooltipRef.current) {
|
||||||
const tooltipHeight = tooltipRef.current.offsetHeight;
|
const tooltipHeight = tooltipRef.current.offsetHeight * 1.1;
|
||||||
const tooltipY = task.index * rowHeight + rowHeight;
|
let tooltipY = task.index * rowHeight;
|
||||||
if (tooltipHeight > tooltipY) {
|
const newWidth = tooltipRef.current.scrollWidth * 1.1;
|
||||||
setRelatedY(tooltipHeight * 0.5);
|
let newRelatedX = task.x2 + arrowIndent + arrowIndent * 0.5;
|
||||||
} else if (tooltipY + tooltipHeight > svgHeight) {
|
if (newWidth + newRelatedX > displayXStartEndpoint.end) {
|
||||||
setRelatedY(svgHeight - tooltipHeight * 1.05);
|
newRelatedX = task.x1 - arrowIndent - arrowIndent * 0.5 - newWidth;
|
||||||
|
}
|
||||||
|
const tooltipLowerPoint = tooltipHeight + tooltipY;
|
||||||
|
|
||||||
|
if (
|
||||||
|
newRelatedX < displayXStartEndpoint.start &&
|
||||||
|
tooltipLowerPoint > svgHeight
|
||||||
|
) {
|
||||||
|
tooltipY -= tooltipHeight;
|
||||||
|
newRelatedX = (task.x1 + task.x2 - newWidth) * 0.5;
|
||||||
|
if (newRelatedX + newWidth > displayXStartEndpoint.end) {
|
||||||
|
newRelatedX = displayXStartEndpoint.end - newWidth;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
newRelatedX + newWidth > displayXStartEndpoint.end ||
|
||||||
|
newRelatedX - newWidth < displayXStartEndpoint.start
|
||||||
|
) {
|
||||||
|
newRelatedX = displayXStartEndpoint.end - newWidth;
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
newRelatedX < displayXStartEndpoint.start &&
|
||||||
|
tooltipLowerPoint < svgHeight
|
||||||
|
) {
|
||||||
|
tooltipY += rowHeight;
|
||||||
|
newRelatedX = (task.x1 + task.x2 - newWidth) * 0.5;
|
||||||
|
if (
|
||||||
|
newRelatedX + newWidth > displayXStartEndpoint.end ||
|
||||||
|
newRelatedX - newWidth < displayXStartEndpoint.start
|
||||||
|
) {
|
||||||
|
newRelatedX = displayXStartEndpoint.end - newWidth;
|
||||||
|
}
|
||||||
|
} else if (tooltipLowerPoint > svgHeight) {
|
||||||
|
tooltipY = svgHeight - tooltipHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRelatedY(tooltipY);
|
||||||
|
setToolWidth(newWidth);
|
||||||
|
setRelatedX(newRelatedX);
|
||||||
|
if (tooltipHeight !== 1000) {
|
||||||
|
setToolHeight(tooltipHeight);
|
||||||
}
|
}
|
||||||
setToolWidth(tooltipRef.current.scrollWidth * 1.1);
|
|
||||||
}
|
}
|
||||||
}, [tooltipRef, task]);
|
}, [tooltipRef, task, arrowIndent, displayXStartEndpoint]);
|
||||||
return (
|
return (
|
||||||
<foreignObject x={x} y={relatedY} width={toolWidth} height={1000}>
|
<foreignObject
|
||||||
|
x={relatedX}
|
||||||
|
y={relatedY}
|
||||||
|
width={toolWidth}
|
||||||
|
height={toolHeight}
|
||||||
|
>
|
||||||
<div ref={tooltipRef} className={styles.tooltipDetailsContainer}>
|
<div ref={tooltipRef} className={styles.tooltipDetailsContainer}>
|
||||||
<TooltipContent
|
<TooltipContent
|
||||||
task={task}
|
task={task}
|
||||||
@ -71,10 +121,13 @@ export const StandardTooltipContent: React.FC<{
|
|||||||
}-${task.start.getFullYear()} - ${task.end.getDate()}-${
|
}-${task.start.getFullYear()} - ${task.end.getDate()}-${
|
||||||
task.end.getMonth() + 1
|
task.end.getMonth() + 1
|
||||||
}-${task.end.getFullYear()}`}</b>
|
}-${task.end.getFullYear()}`}</b>
|
||||||
<p className={styles.tooltipDefaultContainerParagraph}>{`Duration: ${~~(
|
{task.end.getTime() - task.start.getTime() !== 0 && (
|
||||||
(task.end.getTime() - task.start.getTime()) /
|
<p className={styles.tooltipDefaultContainerParagraph}>{`Duration: ${~~(
|
||||||
(1000 * 60 * 60 * 24)
|
(task.end.getTime() - task.start.getTime()) /
|
||||||
)} day(s)`}</p>
|
(1000 * 60 * 60 * 24)
|
||||||
|
)} day(s)`}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
<p className={styles.tooltipDefaultContainerParagraph}>
|
<p className={styles.tooltipDefaultContainerParagraph}>
|
||||||
{!!task.progress && `Progress: ${task.progress} %`}
|
{!!task.progress && `Progress: ${task.progress} %`}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useRef, useState, useEffect } from "react";
|
import React from "react";
|
||||||
import style from "./bar.module.css";
|
import style from "./bar.module.css";
|
||||||
|
|
||||||
type BarDisplayProps = {
|
type BarDisplayProps = {
|
||||||
@ -9,9 +9,6 @@ type BarDisplayProps = {
|
|||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
progressWidth: number;
|
progressWidth: number;
|
||||||
barCornerRadius: number;
|
barCornerRadius: number;
|
||||||
text: string;
|
|
||||||
hasChild: boolean;
|
|
||||||
arrowIndent: number;
|
|
||||||
styles: {
|
styles: {
|
||||||
backgroundColor: string;
|
backgroundColor: string;
|
||||||
backgroundSelectedColor: string;
|
backgroundSelectedColor: string;
|
||||||
@ -28,20 +25,9 @@ export const BarDisplay: React.FC<BarDisplayProps> = ({
|
|||||||
isSelected,
|
isSelected,
|
||||||
progressWidth,
|
progressWidth,
|
||||||
barCornerRadius,
|
barCornerRadius,
|
||||||
text,
|
|
||||||
hasChild,
|
|
||||||
arrowIndent,
|
|
||||||
styles,
|
styles,
|
||||||
onMouseDown,
|
onMouseDown,
|
||||||
}) => {
|
}) => {
|
||||||
const textRef = useRef<SVGTextElement>(null);
|
|
||||||
const [isTextInside, setIsTextInside] = useState(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (textRef.current)
|
|
||||||
setIsTextInside(textRef.current.getBBox().width < width);
|
|
||||||
}, [textRef, width]);
|
|
||||||
|
|
||||||
const getProcessColor = () => {
|
const getProcessColor = () => {
|
||||||
return isSelected ? styles.progressSelectedColor : styles.progressColor;
|
return isSelected ? styles.progressSelectedColor : styles.progressColor;
|
||||||
};
|
};
|
||||||
@ -50,12 +36,6 @@ export const BarDisplay: React.FC<BarDisplayProps> = ({
|
|||||||
return isSelected ? styles.backgroundSelectedColor : styles.backgroundColor;
|
return isSelected ? styles.backgroundSelectedColor : styles.backgroundColor;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getX = () => {
|
|
||||||
return isTextInside
|
|
||||||
? x + width * 0.5
|
|
||||||
: x + width + arrowIndent * +hasChild + arrowIndent * 0.2;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<g onMouseDown={onMouseDown}>
|
<g onMouseDown={onMouseDown}>
|
||||||
<rect
|
<rect
|
||||||
@ -77,18 +57,6 @@ export const BarDisplay: React.FC<BarDisplayProps> = ({
|
|||||||
rx={barCornerRadius}
|
rx={barCornerRadius}
|
||||||
fill={getProcessColor()}
|
fill={getProcessColor()}
|
||||||
/>
|
/>
|
||||||
<text
|
|
||||||
x={getX()}
|
|
||||||
y={y + height * 0.5}
|
|
||||||
className={
|
|
||||||
isTextInside
|
|
||||||
? style.barLabel
|
|
||||||
: style.barLabel && style.barLabelOutside
|
|
||||||
}
|
|
||||||
ref={textRef}
|
|
||||||
>
|
|
||||||
{text}
|
|
||||||
</text>
|
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
51
src/components/task-item/bar/bar-small.tsx
Normal file
51
src/components/task-item/bar/bar-small.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {
|
||||||
|
progressWithByParams,
|
||||||
|
getProgressPoint,
|
||||||
|
} from "../../../helpers/bar-helper";
|
||||||
|
import { BarDisplay } from "./bar-display";
|
||||||
|
import { BarProgressHandle } from "./bar-progress-handle";
|
||||||
|
import { TaskItemProps } from "../task-item";
|
||||||
|
import styles from "./bar.module.css";
|
||||||
|
|
||||||
|
export const BarSmall: React.FC<TaskItemProps> = ({
|
||||||
|
task,
|
||||||
|
isProgressChangeable,
|
||||||
|
isDateChangeable,
|
||||||
|
onEventStart,
|
||||||
|
isSelected,
|
||||||
|
}) => {
|
||||||
|
const progressWidth = progressWithByParams(task.x1, task.x2, task.progress);
|
||||||
|
const progressPoint = getProgressPoint(
|
||||||
|
progressWidth + task.x1,
|
||||||
|
task.y,
|
||||||
|
task.height
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<g className={styles.barWrapper} tabIndex={0}>
|
||||||
|
<BarDisplay
|
||||||
|
x={task.x1}
|
||||||
|
y={task.y}
|
||||||
|
width={task.x2 - task.x1}
|
||||||
|
height={task.height}
|
||||||
|
progressWidth={progressWidth}
|
||||||
|
barCornerRadius={task.barCornerRadius}
|
||||||
|
styles={task.styles}
|
||||||
|
isSelected={isSelected}
|
||||||
|
onMouseDown={e => {
|
||||||
|
isDateChangeable && onEventStart("move", task, e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<g className="handleGroup">
|
||||||
|
{isProgressChangeable && (
|
||||||
|
<BarProgressHandle
|
||||||
|
progressPoint={progressPoint}
|
||||||
|
onMouseDown={e => {
|
||||||
|
onEventStart("progress", task, e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
};
|
||||||
21
src/components/task-item/bar/bar.module.css
Normal file
21
src/components/task-item/bar/bar.module.css
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
.barWrapper {
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barWrapper:hover .barHandle {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barHandle {
|
||||||
|
fill: #ddd;
|
||||||
|
cursor: ew-resize;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.barBackground {
|
||||||
|
user-select: none;
|
||||||
|
stroke-width: 0;
|
||||||
|
}
|
||||||
@ -1,36 +1,19 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { BarTask } from "../../types/bar-task";
|
|
||||||
import {
|
import {
|
||||||
progressWithByParams,
|
progressWithByParams,
|
||||||
getProgressPoint,
|
getProgressPoint,
|
||||||
} from "../../helpers/bar-helper";
|
} from "../../../helpers/bar-helper";
|
||||||
import styles from "./bar.module.css";
|
|
||||||
import { GanttContentMoveAction } from "../gantt/task-gantt-content";
|
|
||||||
import { BarDisplay } from "./bar-display";
|
import { BarDisplay } from "./bar-display";
|
||||||
import { BarDateHandle } from "./bar-date-handle";
|
import { BarDateHandle } from "./bar-date-handle";
|
||||||
import { BarProgressHandle } from "./bar-progress-handle";
|
import { BarProgressHandle } from "./bar-progress-handle";
|
||||||
|
import { TaskItemProps } from "../task-item";
|
||||||
|
import styles from "./bar.module.css";
|
||||||
|
|
||||||
export type BarProps = {
|
export const Bar: React.FC<TaskItemProps> = ({
|
||||||
task: BarTask;
|
|
||||||
arrowIndent: number;
|
|
||||||
isProgressChangeable: boolean;
|
|
||||||
isDateChangeable: boolean;
|
|
||||||
isDelete: boolean;
|
|
||||||
isSelected: boolean;
|
|
||||||
onEventStart: (
|
|
||||||
action: GanttContentMoveAction,
|
|
||||||
selectedTask: BarTask,
|
|
||||||
event?: React.MouseEvent | React.KeyboardEvent
|
|
||||||
) => any;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Bar: React.FC<BarProps> = ({
|
|
||||||
task,
|
task,
|
||||||
arrowIndent,
|
|
||||||
isProgressChangeable,
|
isProgressChangeable,
|
||||||
isDateChangeable,
|
isDateChangeable,
|
||||||
onEventStart,
|
onEventStart,
|
||||||
isDelete,
|
|
||||||
isSelected,
|
isSelected,
|
||||||
}) => {
|
}) => {
|
||||||
const progressWidth = progressWithByParams(task.x1, task.x2, task.progress);
|
const progressWidth = progressWithByParams(task.x1, task.x2, task.progress);
|
||||||
@ -39,33 +22,9 @@ export const Bar: React.FC<BarProps> = ({
|
|||||||
task.y,
|
task.y,
|
||||||
task.height
|
task.height
|
||||||
);
|
);
|
||||||
|
const handleHeight = task.height - 2;
|
||||||
return (
|
return (
|
||||||
<g
|
<g className={styles.barWrapper} tabIndex={0}>
|
||||||
className={styles.barWrapper}
|
|
||||||
tabIndex={0}
|
|
||||||
onKeyDown={e => {
|
|
||||||
switch (e.key) {
|
|
||||||
case "Delete": {
|
|
||||||
if (isDelete) onEventStart("delete", task, e);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
onMouseEnter={e => {
|
|
||||||
onEventStart("mouseenter", task, e);
|
|
||||||
}}
|
|
||||||
onMouseLeave={e => {
|
|
||||||
onEventStart("mouseleave", task, e);
|
|
||||||
}}
|
|
||||||
onDoubleClick={e => {
|
|
||||||
onEventStart("dblclick", task, e);
|
|
||||||
}}
|
|
||||||
onFocus={() => {
|
|
||||||
onEventStart("select", task);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<BarDisplay
|
<BarDisplay
|
||||||
x={task.x1}
|
x={task.x1}
|
||||||
y={task.y}
|
y={task.y}
|
||||||
@ -73,9 +32,6 @@ export const Bar: React.FC<BarProps> = ({
|
|||||||
height={task.height}
|
height={task.height}
|
||||||
progressWidth={progressWidth}
|
progressWidth={progressWidth}
|
||||||
barCornerRadius={task.barCornerRadius}
|
barCornerRadius={task.barCornerRadius}
|
||||||
text={task.name}
|
|
||||||
hasChild={task.barChildren.length > 0}
|
|
||||||
arrowIndent={arrowIndent}
|
|
||||||
styles={task.styles}
|
styles={task.styles}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
onMouseDown={e => {
|
onMouseDown={e => {
|
||||||
@ -90,7 +46,7 @@ export const Bar: React.FC<BarProps> = ({
|
|||||||
x={task.x1 + 1}
|
x={task.x1 + 1}
|
||||||
y={task.y + 1}
|
y={task.y + 1}
|
||||||
width={task.handleWidth}
|
width={task.handleWidth}
|
||||||
height={task.height - 2}
|
height={handleHeight}
|
||||||
barCornerRadius={task.barCornerRadius}
|
barCornerRadius={task.barCornerRadius}
|
||||||
onMouseDown={e => {
|
onMouseDown={e => {
|
||||||
onEventStart("start", task, e);
|
onEventStart("start", task, e);
|
||||||
@ -101,7 +57,7 @@ export const Bar: React.FC<BarProps> = ({
|
|||||||
x={task.x2 - task.handleWidth - 1}
|
x={task.x2 - task.handleWidth - 1}
|
||||||
y={task.y + 1}
|
y={task.y + 1}
|
||||||
width={task.handleWidth}
|
width={task.handleWidth}
|
||||||
height={task.height - 2}
|
height={handleHeight}
|
||||||
barCornerRadius={task.barCornerRadius}
|
barCornerRadius={task.barCornerRadius}
|
||||||
onMouseDown={e => {
|
onMouseDown={e => {
|
||||||
onEventStart("end", task, e);
|
onEventStart("end", task, e);
|
||||||
8
src/components/task-item/milestone/milestone.module.css
Normal file
8
src/components/task-item/milestone/milestone.module.css
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.milestoneWrapper {
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.milestoneBackground {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
37
src/components/task-item/milestone/milestone.tsx
Normal file
37
src/components/task-item/milestone/milestone.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { TaskItemProps } from "../task-item";
|
||||||
|
import styles from "./milestone.module.css";
|
||||||
|
|
||||||
|
export const Milestone: React.FC<TaskItemProps> = ({
|
||||||
|
task,
|
||||||
|
isDateChangeable,
|
||||||
|
onEventStart,
|
||||||
|
isSelected,
|
||||||
|
}) => {
|
||||||
|
const transform = `rotate(45 ${task.x1 + task.height * 0.356}
|
||||||
|
${task.y + task.height * 0.85})`;
|
||||||
|
const getBarColor = () => {
|
||||||
|
return isSelected
|
||||||
|
? task.styles.backgroundSelectedColor
|
||||||
|
: task.styles.backgroundColor;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<g tabIndex={0} className={styles.milestoneWrapper}>
|
||||||
|
<rect
|
||||||
|
fill={getBarColor()}
|
||||||
|
x={task.x1}
|
||||||
|
width={task.height}
|
||||||
|
y={task.y}
|
||||||
|
height={task.height}
|
||||||
|
rx={task.barCornerRadius}
|
||||||
|
ry={task.barCornerRadius}
|
||||||
|
transform={transform}
|
||||||
|
className={styles.milestoneBackground}
|
||||||
|
onMouseDown={e => {
|
||||||
|
isDateChangeable && onEventStart("move", task, e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
};
|
||||||
13
src/components/task-item/project/project.module.css
Normal file
13
src/components/task-item/project/project.module.css
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
.projectWrapper {
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projectBackground {
|
||||||
|
user-select: none;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.projectTop {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
76
src/components/task-item/project/project.tsx
Normal file
76
src/components/task-item/project/project.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { progressWithByParams } from "../../../helpers/bar-helper";
|
||||||
|
import { TaskItemProps } from "../task-item";
|
||||||
|
import styles from "./project.module.css";
|
||||||
|
|
||||||
|
export const Project: React.FC<TaskItemProps> = ({ task, isSelected }) => {
|
||||||
|
const barColor = isSelected
|
||||||
|
? task.styles.backgroundSelectedColor
|
||||||
|
: task.styles.backgroundColor;
|
||||||
|
const processColor = isSelected
|
||||||
|
? task.styles.progressSelectedColor
|
||||||
|
: task.styles.progressColor;
|
||||||
|
const progressWidth = progressWithByParams(task.x1, task.x2, task.progress);
|
||||||
|
const projectWith = task.x2 - task.x1;
|
||||||
|
|
||||||
|
const projectLeftTriangle = [
|
||||||
|
task.x1,
|
||||||
|
task.y + task.height / 2 - 1,
|
||||||
|
task.x1,
|
||||||
|
task.y + task.height,
|
||||||
|
task.x1 + 15,
|
||||||
|
task.y + task.height / 2 - 1,
|
||||||
|
].join(",");
|
||||||
|
const projectRightTriangle = [
|
||||||
|
task.x2,
|
||||||
|
task.y + task.height / 2 - 1,
|
||||||
|
task.x2,
|
||||||
|
task.y + task.height,
|
||||||
|
task.x2 - 15,
|
||||||
|
task.y + task.height / 2 - 1,
|
||||||
|
].join(",");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<g tabIndex={0} className={styles.projectWrapper}>
|
||||||
|
<rect
|
||||||
|
fill={barColor}
|
||||||
|
x={task.x1}
|
||||||
|
width={projectWith}
|
||||||
|
y={task.y}
|
||||||
|
height={task.height}
|
||||||
|
rx={task.barCornerRadius}
|
||||||
|
ry={task.barCornerRadius}
|
||||||
|
className={styles.projectBackground}
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x={task.x1}
|
||||||
|
width={progressWidth}
|
||||||
|
y={task.y}
|
||||||
|
height={task.height}
|
||||||
|
ry={task.barCornerRadius}
|
||||||
|
rx={task.barCornerRadius}
|
||||||
|
fill={processColor}
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
fill={barColor}
|
||||||
|
x={task.x1}
|
||||||
|
width={projectWith}
|
||||||
|
y={task.y}
|
||||||
|
height={task.height / 2}
|
||||||
|
rx={task.barCornerRadius}
|
||||||
|
ry={task.barCornerRadius}
|
||||||
|
className={styles.projectTop}
|
||||||
|
/>
|
||||||
|
<polygon
|
||||||
|
className={styles.projectTop}
|
||||||
|
points={projectLeftTriangle}
|
||||||
|
fill={barColor}
|
||||||
|
/>
|
||||||
|
<polygon
|
||||||
|
className={styles.projectTop}
|
||||||
|
points={projectRightTriangle}
|
||||||
|
fill={barColor}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
};
|
||||||
110
src/components/task-item/task-item.tsx
Normal file
110
src/components/task-item/task-item.tsx
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { BarTask } from "../../types/bar-task";
|
||||||
|
import { GanttContentMoveAction } from "../../types/gantt-task-actions";
|
||||||
|
import { Bar } from "./bar/bar";
|
||||||
|
import { BarSmall } from "./bar/bar-small";
|
||||||
|
import { Milestone } from "./milestone/milestone";
|
||||||
|
import { Project } from "./project/project";
|
||||||
|
import style from "./task-list.module.css";
|
||||||
|
|
||||||
|
export type TaskItemProps = {
|
||||||
|
task: BarTask;
|
||||||
|
arrowIndent: number;
|
||||||
|
taskHeight: number;
|
||||||
|
isProgressChangeable: boolean;
|
||||||
|
isDateChangeable: boolean;
|
||||||
|
isDelete: boolean;
|
||||||
|
isSelected: boolean;
|
||||||
|
onEventStart: (
|
||||||
|
action: GanttContentMoveAction,
|
||||||
|
selectedTask: BarTask,
|
||||||
|
event?: React.MouseEvent | React.KeyboardEvent
|
||||||
|
) => any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TaskItem: React.FC<TaskItemProps> = props => {
|
||||||
|
const {
|
||||||
|
task,
|
||||||
|
arrowIndent,
|
||||||
|
isDelete,
|
||||||
|
taskHeight,
|
||||||
|
isSelected,
|
||||||
|
onEventStart,
|
||||||
|
} = {
|
||||||
|
...props,
|
||||||
|
};
|
||||||
|
const textRef = useRef<SVGTextElement>(null);
|
||||||
|
const [taskItem, setTaskItem] = useState<JSX.Element>(<div />);
|
||||||
|
const [isTextInside, setIsTextInside] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
switch (task.typeInternal) {
|
||||||
|
case "milestone":
|
||||||
|
setTaskItem(<Milestone {...props} />);
|
||||||
|
break;
|
||||||
|
case "project":
|
||||||
|
setTaskItem(<Project {...props} />);
|
||||||
|
break;
|
||||||
|
case "smalltask":
|
||||||
|
setTaskItem(<BarSmall {...props} />);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
setTaskItem(<Bar {...props} />);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, [task, isSelected]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (textRef.current) {
|
||||||
|
setIsTextInside(textRef.current.getBBox().width < task.x2 - task.x1);
|
||||||
|
}
|
||||||
|
}, [textRef, task]);
|
||||||
|
|
||||||
|
const getX = () => {
|
||||||
|
const width = task.x2 - task.x1;
|
||||||
|
const hasChild = task.barChildren.length > 0;
|
||||||
|
return isTextInside
|
||||||
|
? task.x1 + width * 0.5
|
||||||
|
: task.x1 + width + arrowIndent * +hasChild + arrowIndent * 0.2;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<g
|
||||||
|
onKeyDown={e => {
|
||||||
|
switch (e.key) {
|
||||||
|
case "Delete": {
|
||||||
|
if (isDelete) onEventStart("delete", task, e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
onMouseEnter={e => {
|
||||||
|
onEventStart("mouseenter", task, e);
|
||||||
|
}}
|
||||||
|
onMouseLeave={e => {
|
||||||
|
onEventStart("mouseleave", task, e);
|
||||||
|
}}
|
||||||
|
onDoubleClick={e => {
|
||||||
|
onEventStart("dblclick", task, e);
|
||||||
|
}}
|
||||||
|
onFocus={() => {
|
||||||
|
onEventStart("select", task);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{taskItem}
|
||||||
|
<text
|
||||||
|
x={getX()}
|
||||||
|
y={task.y + taskHeight * 0.5}
|
||||||
|
className={
|
||||||
|
isTextInside
|
||||||
|
? style.barLabel
|
||||||
|
: style.barLabel && style.barLabelOutside
|
||||||
|
}
|
||||||
|
ref={textRef}
|
||||||
|
>
|
||||||
|
{task.name}
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,21 +1,3 @@
|
|||||||
.barWrapper {
|
|
||||||
cursor: pointer;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.barWrapper:hover .barHandle {
|
|
||||||
visibility: visible;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.barHandle {
|
|
||||||
fill: #ddd;
|
|
||||||
cursor: ew-resize;
|
|
||||||
opacity: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.barLabel {
|
.barLabel {
|
||||||
fill: #fff;
|
fill: #fff;
|
||||||
text-anchor: middle;
|
text-anchor: middle;
|
||||||
@ -39,8 +21,3 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.barBackground {
|
|
||||||
user-select: none;
|
|
||||||
stroke-width: 0;
|
|
||||||
}
|
|
||||||
@ -59,7 +59,8 @@ export const TaskListTableDefault: React.FC<{
|
|||||||
maxWidth: rowWidth,
|
maxWidth: rowWidth,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t.end.toLocaleDateString(locale, dateTimeOptions)}
|
|
||||||
|
{t.end.toLocaleDateString(locale, dateTimeOptions)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
|
import { BarTask } from "../../types/bar-task";
|
||||||
import { Task } from "../../types/public-types";
|
import { Task } from "../../types/public-types";
|
||||||
|
|
||||||
export type TaskListProps = {
|
export type TaskListProps = {
|
||||||
@ -12,8 +13,8 @@ export type TaskListProps = {
|
|||||||
locale: string;
|
locale: string;
|
||||||
tasks: Task[];
|
tasks: Task[];
|
||||||
horizontalContainerClass?: string;
|
horizontalContainerClass?: string;
|
||||||
selectedTaskId: string;
|
selectedTask: BarTask | undefined;
|
||||||
setSelectedTask: (taskId: string) => void;
|
setSelectedTask: (task: string) => void;
|
||||||
TaskListHeader: React.FC<{
|
TaskListHeader: React.FC<{
|
||||||
headerHeight: number;
|
headerHeight: number;
|
||||||
rowWidth: string;
|
rowWidth: string;
|
||||||
@ -40,7 +41,7 @@ export const TaskList: React.FC<TaskListProps> = ({
|
|||||||
rowHeight,
|
rowHeight,
|
||||||
scrollY,
|
scrollY,
|
||||||
tasks,
|
tasks,
|
||||||
selectedTaskId,
|
selectedTask,
|
||||||
setSelectedTask,
|
setSelectedTask,
|
||||||
locale,
|
locale,
|
||||||
ganttHeight,
|
ganttHeight,
|
||||||
@ -61,6 +62,7 @@ export const TaskList: React.FC<TaskListProps> = ({
|
|||||||
fontSize,
|
fontSize,
|
||||||
rowWidth,
|
rowWidth,
|
||||||
};
|
};
|
||||||
|
const selectedTaskId = selectedTask ? selectedTask.id : "";
|
||||||
const tableProps = {
|
const tableProps = {
|
||||||
rowHeight,
|
rowHeight,
|
||||||
rowWidth,
|
rowWidth,
|
||||||
@ -68,7 +70,7 @@ export const TaskList: React.FC<TaskListProps> = ({
|
|||||||
fontSize,
|
fontSize,
|
||||||
tasks,
|
tasks,
|
||||||
locale,
|
locale,
|
||||||
selectedTaskId,
|
selectedTaskId: selectedTaskId,
|
||||||
setSelectedTask,
|
setSelectedTask,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,26 +1,31 @@
|
|||||||
import { Task } from "../types/public-types";
|
import { Task } from "../types/public-types";
|
||||||
import { BarTask } from "../types/bar-task";
|
import { BarTask, TaskTypeInternal } from "../types/bar-task";
|
||||||
|
import { BarMoveAction } from "../types/gantt-task-actions";
|
||||||
|
|
||||||
export const convertToBarTasks = (
|
export const convertToBarTasks = (
|
||||||
tasks: Task[],
|
tasks: Task[],
|
||||||
dates: Date[],
|
dates: Date[],
|
||||||
columnWidth: number,
|
columnWidth: number,
|
||||||
rowHeight: number,
|
rowHeight: number,
|
||||||
barFill: number,
|
taskHeight: number,
|
||||||
barCornerRadius: number,
|
barCornerRadius: number,
|
||||||
handleWidth: number,
|
handleWidth: number,
|
||||||
barProgressColor: string,
|
barProgressColor: string,
|
||||||
barProgressSelectedColor: string,
|
barProgressSelectedColor: string,
|
||||||
barBackgroundColor: string,
|
barBackgroundColor: string,
|
||||||
barBackgroundSelectedColor: string
|
barBackgroundSelectedColor: string,
|
||||||
|
projectProgressColor: string,
|
||||||
|
projectProgressSelectedColor: string,
|
||||||
|
projectBackgroundColor: string,
|
||||||
|
projectBackgroundSelectedColor: string,
|
||||||
|
milestoneBackgroundColor: string,
|
||||||
|
milestoneBackgroundSelectedColor: string
|
||||||
) => {
|
) => {
|
||||||
const dateDelta =
|
const dateDelta =
|
||||||
dates[1].getTime() -
|
dates[1].getTime() -
|
||||||
dates[0].getTime() -
|
dates[0].getTime() -
|
||||||
dates[1].getTimezoneOffset() * 60 * 1000 +
|
dates[1].getTimezoneOffset() * 60 * 1000 +
|
||||||
dates[0].getTimezoneOffset() * 60 * 1000;
|
dates[0].getTimezoneOffset() * 60 * 1000;
|
||||||
const taskHeight = (rowHeight * barFill) / 100;
|
|
||||||
|
|
||||||
let barTasks = tasks.map((t, i) => {
|
let barTasks = tasks.map((t, i) => {
|
||||||
return convertToBarTask(
|
return convertToBarTask(
|
||||||
t,
|
t,
|
||||||
@ -35,7 +40,13 @@ export const convertToBarTasks = (
|
|||||||
barProgressColor,
|
barProgressColor,
|
||||||
barProgressSelectedColor,
|
barProgressSelectedColor,
|
||||||
barBackgroundColor,
|
barBackgroundColor,
|
||||||
barBackgroundSelectedColor
|
barBackgroundSelectedColor,
|
||||||
|
projectProgressColor,
|
||||||
|
projectProgressSelectedColor,
|
||||||
|
projectBackgroundColor,
|
||||||
|
projectBackgroundSelectedColor,
|
||||||
|
milestoneBackgroundColor,
|
||||||
|
milestoneBackgroundSelectedColor
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -54,7 +65,83 @@ export const convertToBarTasks = (
|
|||||||
return barTasks;
|
return barTasks;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const convertToBarTask = (
|
const convertToBarTask = (
|
||||||
|
task: Task,
|
||||||
|
index: number,
|
||||||
|
dates: Date[],
|
||||||
|
dateDelta: number,
|
||||||
|
columnWidth: number,
|
||||||
|
rowHeight: number,
|
||||||
|
taskHeight: number,
|
||||||
|
barCornerRadius: number,
|
||||||
|
handleWidth: number,
|
||||||
|
barProgressColor: string,
|
||||||
|
barProgressSelectedColor: string,
|
||||||
|
barBackgroundColor: string,
|
||||||
|
barBackgroundSelectedColor: string,
|
||||||
|
projectProgressColor: string,
|
||||||
|
projectProgressSelectedColor: string,
|
||||||
|
projectBackgroundColor: string,
|
||||||
|
projectBackgroundSelectedColor: string,
|
||||||
|
milestoneBackgroundColor: string,
|
||||||
|
milestoneBackgroundSelectedColor: string
|
||||||
|
): BarTask => {
|
||||||
|
let barTask: BarTask;
|
||||||
|
switch (task.type) {
|
||||||
|
case "milestone":
|
||||||
|
barTask = convertToMilestone(
|
||||||
|
task,
|
||||||
|
index,
|
||||||
|
dates,
|
||||||
|
dateDelta,
|
||||||
|
columnWidth,
|
||||||
|
rowHeight,
|
||||||
|
taskHeight,
|
||||||
|
barCornerRadius,
|
||||||
|
handleWidth,
|
||||||
|
milestoneBackgroundColor,
|
||||||
|
milestoneBackgroundSelectedColor
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "project":
|
||||||
|
barTask = convertToBar(
|
||||||
|
task,
|
||||||
|
index,
|
||||||
|
dates,
|
||||||
|
dateDelta,
|
||||||
|
columnWidth,
|
||||||
|
rowHeight,
|
||||||
|
taskHeight,
|
||||||
|
barCornerRadius,
|
||||||
|
handleWidth,
|
||||||
|
projectProgressColor,
|
||||||
|
projectProgressSelectedColor,
|
||||||
|
projectBackgroundColor,
|
||||||
|
projectBackgroundSelectedColor
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
barTask = convertToBar(
|
||||||
|
task,
|
||||||
|
index,
|
||||||
|
dates,
|
||||||
|
dateDelta,
|
||||||
|
columnWidth,
|
||||||
|
rowHeight,
|
||||||
|
taskHeight,
|
||||||
|
barCornerRadius,
|
||||||
|
handleWidth,
|
||||||
|
barProgressColor,
|
||||||
|
barProgressSelectedColor,
|
||||||
|
barBackgroundColor,
|
||||||
|
barBackgroundSelectedColor
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return barTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
const convertToBar = (
|
||||||
task: Task,
|
task: Task,
|
||||||
index: number,
|
index: number,
|
||||||
dates: Date[],
|
dates: Date[],
|
||||||
@ -69,8 +156,9 @@ export const convertToBarTask = (
|
|||||||
barBackgroundColor: string,
|
barBackgroundColor: string,
|
||||||
barBackgroundSelectedColor: string
|
barBackgroundSelectedColor: string
|
||||||
): BarTask => {
|
): BarTask => {
|
||||||
|
debugger;
|
||||||
const x1 = taskXCoordinate(task.start, dates, dateDelta, columnWidth);
|
const x1 = taskXCoordinate(task.start, dates, dateDelta, columnWidth);
|
||||||
const x2 = taskXCoordinate(task.end, dates, dateDelta, columnWidth);
|
let x2 = taskXCoordinate(task.end, dates, dateDelta, columnWidth);
|
||||||
const y = taskYCoordinate(index, rowHeight, taskHeight);
|
const y = taskYCoordinate(index, rowHeight, taskHeight);
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
@ -80,8 +168,14 @@ export const convertToBarTask = (
|
|||||||
progressSelectedColor: barProgressSelectedColor,
|
progressSelectedColor: barProgressSelectedColor,
|
||||||
...task.styles,
|
...task.styles,
|
||||||
};
|
};
|
||||||
|
let typeInternal: TaskTypeInternal = task.type;
|
||||||
|
if (typeInternal === "task" && x2 - x1 < handleWidth * 2) {
|
||||||
|
typeInternal = "smalltask";
|
||||||
|
x2 = x1 + handleWidth * 2;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...task,
|
...task,
|
||||||
|
typeInternal,
|
||||||
x1,
|
x1,
|
||||||
x2,
|
x2,
|
||||||
y,
|
y,
|
||||||
@ -94,7 +188,51 @@ export const convertToBarTask = (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const taskXCoordinate = (
|
const convertToMilestone = (
|
||||||
|
task: Task,
|
||||||
|
index: number,
|
||||||
|
dates: Date[],
|
||||||
|
dateDelta: number,
|
||||||
|
columnWidth: number,
|
||||||
|
rowHeight: number,
|
||||||
|
taskHeight: number,
|
||||||
|
barCornerRadius: number,
|
||||||
|
handleWidth: number,
|
||||||
|
milestoneBackgroundColor: string,
|
||||||
|
milestoneBackgroundSelectedColor: string
|
||||||
|
) => {
|
||||||
|
const x = taskXCoordinate(task.start, dates, dateDelta, columnWidth);
|
||||||
|
const y = taskYCoordinate(index, rowHeight, taskHeight);
|
||||||
|
|
||||||
|
const x1 = x - taskHeight * 0.5;
|
||||||
|
const x2 = x + taskHeight * 0.5;
|
||||||
|
|
||||||
|
const rotatedHeight = taskHeight / 1.414;
|
||||||
|
const styles = {
|
||||||
|
backgroundColor: milestoneBackgroundColor,
|
||||||
|
backgroundSelectedColor: milestoneBackgroundSelectedColor,
|
||||||
|
progressColor: "",
|
||||||
|
progressSelectedColor: "",
|
||||||
|
...task.styles,
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
...task,
|
||||||
|
end: task.start,
|
||||||
|
x1,
|
||||||
|
x2,
|
||||||
|
y,
|
||||||
|
index,
|
||||||
|
barCornerRadius,
|
||||||
|
handleWidth,
|
||||||
|
typeInternal: task.type,
|
||||||
|
progress: 0,
|
||||||
|
height: rotatedHeight,
|
||||||
|
barChildren: [],
|
||||||
|
styles,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const taskXCoordinate = (
|
||||||
xDate: Date,
|
xDate: Date,
|
||||||
dates: Date[],
|
dates: Date[],
|
||||||
dateDelta: number,
|
dateDelta: number,
|
||||||
@ -119,7 +257,7 @@ export const taskXCoordinate = (
|
|||||||
return x;
|
return x;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const taskYCoordinate = (
|
const taskYCoordinate = (
|
||||||
index: number,
|
index: number,
|
||||||
rowHeight: number,
|
rowHeight: number,
|
||||||
taskHeight: number
|
taskHeight: number
|
||||||
@ -149,7 +287,7 @@ export const progressByProgressWidth = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const progressByX = (x: number, task: BarTask) => {
|
const progressByX = (x: number, task: BarTask) => {
|
||||||
if (x >= task.x2) return 100;
|
if (x >= task.x2) return 100;
|
||||||
else if (x <= task.x1) return 0;
|
else if (x <= task.x1) return 0;
|
||||||
else {
|
else {
|
||||||
@ -175,7 +313,7 @@ export const getProgressPoint = (
|
|||||||
return point.join(",");
|
return point.join(",");
|
||||||
};
|
};
|
||||||
|
|
||||||
export const startByX = (x: number, xStep: number, task: BarTask) => {
|
const startByX = (x: number, xStep: number, task: BarTask) => {
|
||||||
if (x >= task.x2 - task.handleWidth * 2) {
|
if (x >= task.x2 - task.handleWidth * 2) {
|
||||||
x = task.x2 - task.handleWidth * 2;
|
x = task.x2 - task.handleWidth * 2;
|
||||||
}
|
}
|
||||||
@ -185,7 +323,7 @@ export const startByX = (x: number, xStep: number, task: BarTask) => {
|
|||||||
return newX;
|
return newX;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const endByX = (x: number, xStep: number, task: BarTask) => {
|
const endByX = (x: number, xStep: number, task: BarTask) => {
|
||||||
if (x <= task.x1 + task.handleWidth * 2) {
|
if (x <= task.x1 + task.handleWidth * 2) {
|
||||||
x = task.x1 + task.handleWidth * 2;
|
x = task.x1 + task.handleWidth * 2;
|
||||||
}
|
}
|
||||||
@ -195,7 +333,7 @@ export const endByX = (x: number, xStep: number, task: BarTask) => {
|
|||||||
return newX;
|
return newX;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const moveByX = (x: number, xStep: number, task: BarTask) => {
|
const moveByX = (x: number, xStep: number, task: BarTask) => {
|
||||||
const steps = Math.round((x - task.x1) / xStep);
|
const steps = Math.round((x - task.x1) / xStep);
|
||||||
const additionalXValue = steps * xStep;
|
const additionalXValue = steps * xStep;
|
||||||
const newX1 = task.x1 + additionalXValue;
|
const newX1 = task.x1 + additionalXValue;
|
||||||
@ -203,7 +341,7 @@ export const moveByX = (x: number, xStep: number, task: BarTask) => {
|
|||||||
return [newX1, newX2];
|
return [newX1, newX2];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dateByX = (
|
const dateByX = (
|
||||||
x: number,
|
x: number,
|
||||||
taskX: number,
|
taskX: number,
|
||||||
taskDate: Date,
|
taskDate: Date,
|
||||||
@ -218,8 +356,6 @@ export const dateByX = (
|
|||||||
return newDate;
|
return newDate;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BarMoveAction = "progress" | "end" | "start" | "move" | "";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method handles event in real time(mousemove) and on finish(mouseup)
|
* Method handles event in real time(mousemove) and on finish(mouseup)
|
||||||
*/
|
*/
|
||||||
@ -230,7 +366,41 @@ export const handleTaskBySVGMouseEvent = (
|
|||||||
xStep: number,
|
xStep: number,
|
||||||
timeStep: number,
|
timeStep: number,
|
||||||
initEventX1Delta: number
|
initEventX1Delta: number
|
||||||
) => {
|
): { isChanged: boolean; changedTask: BarTask } => {
|
||||||
|
let result: { isChanged: boolean; changedTask: BarTask };
|
||||||
|
switch (selectedTask.type) {
|
||||||
|
case "milestone":
|
||||||
|
result = handleTaskBySVGMouseEventForMilestone(
|
||||||
|
svgX,
|
||||||
|
action,
|
||||||
|
selectedTask,
|
||||||
|
xStep,
|
||||||
|
timeStep,
|
||||||
|
initEventX1Delta
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result = handleTaskBySVGMouseEventForBar(
|
||||||
|
svgX,
|
||||||
|
action,
|
||||||
|
selectedTask,
|
||||||
|
xStep,
|
||||||
|
timeStep,
|
||||||
|
initEventX1Delta
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTaskBySVGMouseEventForBar = (
|
||||||
|
svgX: number,
|
||||||
|
action: BarMoveAction,
|
||||||
|
selectedTask: BarTask,
|
||||||
|
xStep: number,
|
||||||
|
timeStep: number,
|
||||||
|
initEventX1Delta: number
|
||||||
|
): { isChanged: boolean; changedTask: BarTask } => {
|
||||||
const changedTask: BarTask = { ...selectedTask };
|
const changedTask: BarTask = { ...selectedTask };
|
||||||
let isChanged = false;
|
let isChanged = false;
|
||||||
switch (action) {
|
switch (action) {
|
||||||
@ -298,3 +468,39 @@ export const handleTaskBySVGMouseEvent = (
|
|||||||
}
|
}
|
||||||
return { isChanged, changedTask };
|
return { isChanged, changedTask };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTaskBySVGMouseEventForMilestone = (
|
||||||
|
svgX: number,
|
||||||
|
action: BarMoveAction,
|
||||||
|
selectedTask: BarTask,
|
||||||
|
xStep: number,
|
||||||
|
timeStep: number,
|
||||||
|
initEventX1Delta: number
|
||||||
|
): { isChanged: boolean; changedTask: BarTask } => {
|
||||||
|
const changedTask: BarTask = { ...selectedTask };
|
||||||
|
let isChanged = false;
|
||||||
|
switch (action) {
|
||||||
|
case "move": {
|
||||||
|
const [newMoveX1, newMoveX2] = moveByX(
|
||||||
|
svgX - initEventX1Delta,
|
||||||
|
xStep,
|
||||||
|
selectedTask
|
||||||
|
);
|
||||||
|
isChanged = newMoveX1 !== selectedTask.x1;
|
||||||
|
if (isChanged) {
|
||||||
|
changedTask.start = dateByX(
|
||||||
|
newMoveX1,
|
||||||
|
selectedTask.x1,
|
||||||
|
selectedTask.start,
|
||||||
|
xStep,
|
||||||
|
timeStep
|
||||||
|
);
|
||||||
|
changedTask.end = changedTask.start;
|
||||||
|
changedTask.x1 = newMoveX1;
|
||||||
|
changedTask.x2 = newMoveX2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { isChanged, changedTask };
|
||||||
|
};
|
||||||
|
|||||||
@ -55,7 +55,7 @@ export const startOfDate = (date: Date, scale: DateHelperScales) => {
|
|||||||
|
|
||||||
export const ganttDateRange = (tasks: Task[], viewMode: ViewMode) => {
|
export const ganttDateRange = (tasks: Task[], viewMode: ViewMode) => {
|
||||||
let newStartDate: Date = tasks[0].start;
|
let newStartDate: Date = tasks[0].start;
|
||||||
let newEndDate: Date = tasks[0].end;
|
let newEndDate: Date = tasks[0].start;
|
||||||
for (const task of tasks) {
|
for (const task of tasks) {
|
||||||
if (task.start < newStartDate) {
|
if (task.start < newStartDate) {
|
||||||
newStartDate = task.start;
|
newStartDate = task.start;
|
||||||
@ -77,12 +77,18 @@ export const ganttDateRange = (tasks: Task[], viewMode: ViewMode) => {
|
|||||||
newStartDate = addToDate(getMonday(newStartDate), -7, "day");
|
newStartDate = addToDate(getMonday(newStartDate), -7, "day");
|
||||||
newEndDate = addToDate(newEndDate, 1.5, "month");
|
newEndDate = addToDate(newEndDate, 1.5, "month");
|
||||||
break;
|
break;
|
||||||
default:
|
case ViewMode.Day:
|
||||||
newStartDate = startOfDate(newStartDate, "day");
|
newStartDate = startOfDate(newStartDate, "day");
|
||||||
newEndDate = startOfDate(newEndDate, "day");
|
newEndDate = startOfDate(newEndDate, "day");
|
||||||
newStartDate = addToDate(newStartDate, -1, "day");
|
newStartDate = addToDate(newStartDate, -1, "day");
|
||||||
newEndDate = addToDate(newEndDate, 19, "day");
|
newEndDate = addToDate(newEndDate, 19, "day");
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
newStartDate = startOfDate(newStartDate, "day");
|
||||||
|
newEndDate = startOfDate(newEndDate, "day");
|
||||||
|
newStartDate = addToDate(newStartDate, -1, "day");
|
||||||
|
newEndDate = addToDate(newEndDate, 5, "day");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return [newStartDate, newEndDate];
|
return [newStartDate, newEndDate];
|
||||||
};
|
};
|
||||||
|
|||||||
26
src/helpers/reducer.ts
Normal file
26
src/helpers/reducer.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
export function foo() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// import { BarTask } from "../types/bar-task";
|
||||||
|
// export type TaskListAction =
|
||||||
|
// | { type: GanttContentMoveAction; task: BarTask }
|
||||||
|
// | { type: "update"; tasks: BarTask[] };
|
||||||
|
|
||||||
|
// export type TaskListState = {
|
||||||
|
// tasks: BarTask[];
|
||||||
|
// changedTask?: BarTask;
|
||||||
|
// originalTask?: BarTask;
|
||||||
|
// selectedTask?: BarTask;
|
||||||
|
// activeAction: GanttContentMoveAction;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export function taskListReducer(state: TaskListState, action: TaskListAction) {
|
||||||
|
// switch (action.type) {
|
||||||
|
// case "update":
|
||||||
|
// return { ...state, tasks: action.tasks };
|
||||||
|
// case "select":
|
||||||
|
// return { ...state, selectedTask: action.task };
|
||||||
|
// default:
|
||||||
|
// return state;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@ -14,6 +14,7 @@ describe("gantt", () => {
|
|||||||
name: "Redesign website",
|
name: "Redesign website",
|
||||||
id: "Task 0",
|
id: "Task 0",
|
||||||
progress: 45,
|
progress: 45,
|
||||||
|
type: "task",
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>,
|
/>,
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { Task } from "./public-types";
|
import { Task, TaskType } from "./public-types";
|
||||||
|
|
||||||
export interface BarTask extends Task {
|
export interface BarTask extends Task {
|
||||||
index: number;
|
index: number;
|
||||||
|
typeInternal: TaskTypeInternal;
|
||||||
x1: number;
|
x1: number;
|
||||||
x2: number;
|
x2: number;
|
||||||
y: number;
|
y: number;
|
||||||
@ -16,3 +17,5 @@ export interface BarTask extends Task {
|
|||||||
progressSelectedColor: string;
|
progressSelectedColor: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TaskTypeInternal = TaskType | "smalltask";
|
||||||
|
|||||||
6
src/types/date-setup.ts
Normal file
6
src/types/date-setup.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { ViewMode } from "./public-types";
|
||||||
|
|
||||||
|
export interface DateSetup {
|
||||||
|
dates: Date[];
|
||||||
|
viewMode: ViewMode;
|
||||||
|
}
|
||||||
17
src/types/gantt-task-actions.ts
Normal file
17
src/types/gantt-task-actions.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { BarTask } from "./bar-task";
|
||||||
|
|
||||||
|
export type BarMoveAction = "progress" | "end" | "start" | "move";
|
||||||
|
export type GanttContentMoveAction =
|
||||||
|
| "mouseenter"
|
||||||
|
| "mouseleave"
|
||||||
|
| "delete"
|
||||||
|
| "dblclick"
|
||||||
|
| "select"
|
||||||
|
| ""
|
||||||
|
| BarMoveAction;
|
||||||
|
|
||||||
|
export type GanttEvent = {
|
||||||
|
changedTask?: BarTask;
|
||||||
|
originalSelectedTask?: BarTask;
|
||||||
|
action: GanttContentMoveAction;
|
||||||
|
};
|
||||||
@ -6,8 +6,10 @@ export enum ViewMode {
|
|||||||
Week = "Week",
|
Week = "Week",
|
||||||
Month = "Month",
|
Month = "Month",
|
||||||
}
|
}
|
||||||
|
export type TaskType = "task" | "milestone" | "project";
|
||||||
export interface Task {
|
export interface Task {
|
||||||
id: string;
|
id: string;
|
||||||
|
type: TaskType;
|
||||||
name: string;
|
name: string;
|
||||||
start: Date;
|
start: Date;
|
||||||
end: Date;
|
end: Date;
|
||||||
@ -22,6 +24,7 @@ export interface Task {
|
|||||||
progressSelectedColor?: string;
|
progressSelectedColor?: string;
|
||||||
};
|
};
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
|
project?: string;
|
||||||
dependencies?: string[];
|
dependencies?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,9 +56,7 @@ export interface EventOption {
|
|||||||
/**
|
/**
|
||||||
* Invokes on delete selected task. Chart undoes operation if method return false or error.
|
* Invokes on delete selected task. Chart undoes operation if method return false or error.
|
||||||
*/
|
*/
|
||||||
onTaskDelete?: (
|
onDelete?: (task: Task) => void | boolean | Promise<void> | Promise<boolean>;
|
||||||
task: Task
|
|
||||||
) => void | boolean | Promise<void> | Promise<boolean>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DisplayOption {
|
export interface DisplayOption {
|
||||||
@ -85,6 +86,12 @@ export interface StylingOption {
|
|||||||
barProgressSelectedColor?: string;
|
barProgressSelectedColor?: string;
|
||||||
barBackgroundColor?: string;
|
barBackgroundColor?: string;
|
||||||
barBackgroundSelectedColor?: string;
|
barBackgroundSelectedColor?: string;
|
||||||
|
projectProgressColor?: string;
|
||||||
|
projectProgressSelectedColor?: string;
|
||||||
|
projectBackgroundColor?: string;
|
||||||
|
projectBackgroundSelectedColor?: string;
|
||||||
|
milestoneBackgroundColor?: string;
|
||||||
|
milestoneBackgroundSelectedColor?: string;
|
||||||
arrowColor?: string;
|
arrowColor?: string;
|
||||||
arrowIndent?: number;
|
arrowIndent?: number;
|
||||||
todayColor?: string;
|
todayColor?: string;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user