gantt-task-react/src/components/Gantt/gantt-content.tsx

441 lines
12 KiB
TypeScript
Raw Normal View History

2020-08-05 08:14:22 +03:00
import React, { useState, useEffect } from "react";
import { Task, EventOption } from "../../types/public-types";
import { Bar } from "../Bar/bar";
import { BarTask } from "../../types/bar-task";
import { Arrow } from "../Other/arrow";
2020-07-22 20:50:43 +03:00
import {
convertToBarTasks,
progressByX,
startByX,
endByX,
moveByX,
dateByX,
2020-08-05 08:14:22 +03:00
} from "../../helpers/bar-helper";
import { Tooltip } from "../Other/tooltip";
2020-07-22 20:50:43 +03:00
export interface GanttTask extends Task {
x1: number;
x2: number;
y: number;
width: number;
height: number;
}
export type GanttContentProps = {
tasks: Task[];
dates: Date[];
rowHeight: number;
barCornerRadius: number;
columnWidth: number;
barFill: number;
2020-07-30 00:01:51 +03:00
barProgressColor: string;
barProgressSelectedColor: string;
barBackgroundColor: string;
barBackgroundSelectedColor: string;
2020-07-22 20:50:43 +03:00
headerHeight: number;
handleWidth: number;
svg: React.MutableRefObject<SVGSVGElement | null>;
timeStep: number;
arrowColor: string;
arrowIndent: number;
fontSize: string;
fontFamily: string;
getTooltipContent?: (
task: Task,
fontSize: string,
fontFamily: string
) => JSX.Element;
} & EventOption;
2020-07-30 22:58:23 +03:00
export type BarAction =
2020-08-05 08:14:22 +03:00
| "progress"
| "end"
| "start"
| "move"
| "mouseenter"
| "mouseleave"
| "";
2020-07-30 22:58:23 +03:00
type BarEvent = {
action: BarAction;
selectedTask: BarTask | null;
};
2020-07-22 20:50:43 +03:00
export const GanttContent: React.FC<GanttContentProps> = ({
tasks,
rowHeight,
barCornerRadius,
columnWidth,
dates,
barFill,
2020-07-30 00:01:51 +03:00
barProgressColor,
barProgressSelectedColor,
barBackgroundColor,
barBackgroundSelectedColor,
2020-07-22 20:50:43 +03:00
headerHeight,
handleWidth,
arrowColor,
svg,
timeStep,
fontFamily,
fontSize,
arrowIndent,
onDateChange,
onProgressChange,
onDoubleClick,
onTaskDelete,
getTooltipContent,
}) => {
2020-07-30 22:58:23 +03:00
const [barEvent, setBarEvent] = useState<BarEvent>({
2020-08-05 08:14:22 +03:00
action: "",
2020-07-30 22:58:23 +03:00
selectedTask: null,
});
const [isSVGListen, setIsSVGListen] = useState(false);
2020-07-22 20:50:43 +03:00
const [barTasks, setBarTasks] = useState<BarTask[]>([]);
const [xStep, setXStep] = useState(0);
const [initEventX1Delta, setInitEventX1Delta] = useState(0);
useEffect(() => {
const dateDelta =
dates[1].getTime() -
dates[0].getTime() -
dates[1].getTimezoneOffset() * 60 * 1000 +
dates[0].getTimezoneOffset() * 60 * 1000;
const newXStep = (timeStep * columnWidth) / dateDelta;
if (newXStep !== xStep) {
setXStep(newXStep);
}
}, [tasks, rowHeight, barCornerRadius, columnWidth, dates, timeStep, xStep]);
useEffect(() => {
const dateDelta =
dates[1].getTime() -
dates[0].getTime() -
dates[1].getTimezoneOffset() * 60 * 1000 +
dates[0].getTimezoneOffset() * 60 * 1000;
const taskHeight = (rowHeight * barFill) / 100;
setBarTasks(
convertToBarTasks(
tasks,
dates,
dateDelta,
columnWidth,
rowHeight,
taskHeight,
headerHeight,
barCornerRadius,
2020-07-30 00:01:51 +03:00
handleWidth,
barProgressColor,
barProgressSelectedColor,
barBackgroundColor,
barBackgroundSelectedColor
2020-07-22 20:50:43 +03:00
)
);
}, [
tasks,
rowHeight,
barCornerRadius,
columnWidth,
dates,
timeStep,
barFill,
handleWidth,
headerHeight,
]);
useEffect(() => {
/**
* Method handles event in real time(mousemove) and on finish(mouseup)
*/
const handleMouseSVGChangeEventsSubscribe = async (event: MouseEvent) => {
2020-08-05 08:14:22 +03:00
if (!barEvent.selectedTask || !barEvent.action || !svg || !svg.current)
return;
2020-07-30 22:58:23 +03:00
const selectedTask = barEvent.selectedTask;
2020-07-22 20:50:43 +03:00
const changedTask = { ...selectedTask } as BarTask;
switch (event.type) {
2020-08-05 08:14:22 +03:00
// On Event changing
case "mousemove": {
2020-07-30 22:58:23 +03:00
switch (barEvent.action) {
2020-08-05 08:14:22 +03:00
case "progress":
2020-07-22 20:50:43 +03:00
changedTask.progress = progressByX(event.offsetX, selectedTask);
break;
2020-08-05 08:14:22 +03:00
case "start": {
const newX1 = startByX(event.offsetX, xStep, selectedTask);
2020-07-22 20:50:43 +03:00
changedTask.x1 = newX1;
changedTask.start = dateByX(
newX1,
selectedTask.x1,
selectedTask.start,
xStep,
timeStep
);
break;
2020-08-05 08:14:22 +03:00
}
case "end": {
const newX2 = endByX(event.offsetX, xStep, selectedTask);
2020-07-22 20:50:43 +03:00
changedTask.x2 = newX2;
changedTask.end = dateByX(
newX2,
selectedTask.x2,
selectedTask.end,
xStep,
timeStep
);
break;
2020-08-05 08:14:22 +03:00
}
case "move": {
2020-07-22 20:50:43 +03:00
const [newMoveX1, newMoveX2] = moveByX(
event.offsetX - initEventX1Delta,
xStep,
selectedTask
);
changedTask.start = dateByX(
newMoveX1,
selectedTask.x1,
selectedTask.start,
xStep,
timeStep
);
changedTask.end = dateByX(
newMoveX2,
selectedTask.x2,
selectedTask.end,
xStep,
timeStep
);
changedTask.x1 = newMoveX1;
changedTask.x2 = newMoveX2;
break;
2020-08-05 08:14:22 +03:00
}
2020-07-22 20:50:43 +03:00
}
2020-08-05 08:14:22 +03:00
// Update internal state
2020-07-22 20:50:43 +03:00
setBarTasks(
barTasks.map(t => (t.id === changedTask.id ? changedTask : t))
);
2020-07-30 22:58:23 +03:00
setBarEvent({ ...barEvent, selectedTask: changedTask });
2020-07-22 20:50:43 +03:00
break;
}
2020-07-30 22:58:23 +03:00
2020-08-05 08:14:22 +03:00
// On finish Event
case "mouseup": {
2020-07-22 20:50:43 +03:00
let eventForExecution: (
task: Task
) => void | Promise<void> = () => {};
2020-07-30 22:58:23 +03:00
switch (barEvent.action) {
2020-08-05 08:14:22 +03:00
case "progress":
2020-07-22 20:50:43 +03:00
changedTask.progress = progressByX(event.offsetX, selectedTask);
if (onProgressChange) {
eventForExecution = onProgressChange;
}
break;
2020-08-05 08:14:22 +03:00
case "start": {
2020-07-22 20:50:43 +03:00
const newX1 = startByX(event.offsetX, xStep, selectedTask);
changedTask.start = dateByX(
newX1,
selectedTask.x1,
selectedTask.start,
xStep,
timeStep
);
if (onDateChange && newX1 !== selectedTask.x1) {
eventForExecution = onDateChange;
}
break;
2020-08-05 08:14:22 +03:00
}
case "end": {
2020-07-22 20:50:43 +03:00
const newX2 = endByX(event.offsetX, xStep, selectedTask);
changedTask.end = dateByX(
newX2,
selectedTask.x2,
selectedTask.end,
xStep,
timeStep
);
if (onDateChange && newX2 !== selectedTask.x2) {
eventForExecution = onDateChange;
}
break;
2020-08-05 08:14:22 +03:00
}
case "move": {
2020-07-22 20:50:43 +03:00
const [newMoveX1, newMoveX2] = moveByX(
event.offsetX - initEventX1Delta,
xStep,
selectedTask
);
changedTask.start = dateByX(
newMoveX1,
selectedTask.x1,
selectedTask.start,
xStep,
timeStep
);
changedTask.end = dateByX(
newMoveX2,
selectedTask.x2,
selectedTask.end,
xStep,
timeStep
);
if (
onDateChange &&
newMoveX1 !== selectedTask.x1 &&
newMoveX2 !== selectedTask.x2
) {
eventForExecution = onDateChange;
}
break;
2020-08-05 08:14:22 +03:00
}
2020-07-22 20:50:43 +03:00
}
2020-08-05 08:14:22 +03:00
setBarEvent({ action: "", selectedTask: null });
2020-07-22 20:50:43 +03:00
setIsSVGListen(false);
2020-08-05 08:14:22 +03:00
svg.current.removeEventListener(
"mousemove",
2020-07-22 20:50:43 +03:00
handleMouseSVGChangeEventsSubscribe
);
2020-08-05 08:14:22 +03:00
svg.current.removeEventListener(
"mouseup",
2020-07-22 20:50:43 +03:00
handleMouseSVGChangeEventsSubscribe
);
2020-08-05 08:14:22 +03:00
// If update successful - update Gantt state, otherwise we shell back old Bar state
2020-07-22 20:50:43 +03:00
await eventForExecution(changedTask);
break;
}
}
};
2020-08-05 08:14:22 +03:00
if (
barEvent.selectedTask &&
barEvent.action &&
!isSVGListen &&
svg &&
svg.current
) {
2020-07-30 22:58:23 +03:00
setIsSVGListen(true);
2020-08-05 08:14:22 +03:00
svg.current.addEventListener(
"mousemove",
2020-07-22 20:50:43 +03:00
handleMouseSVGChangeEventsSubscribe
);
2020-08-05 08:14:22 +03:00
svg.current.addEventListener(
"mouseup",
2020-07-22 20:50:43 +03:00
handleMouseSVGChangeEventsSubscribe
);
}
}, [
barEvent,
2020-07-30 22:58:23 +03:00
isSVGListen,
2020-07-22 20:50:43 +03:00
xStep,
svg,
initEventX1Delta,
barTasks,
onProgressChange,
timeStep,
onDateChange,
]);
/**
* Method is Start point of task change
* @param event init mouse event
* @param eventType
* @param task events task
*/
const handleMouseEvents = (
event:
| React.MouseEvent<SVGPolygonElement, MouseEvent>
| React.MouseEvent<SVGRectElement, MouseEvent>
| React.MouseEvent<SVGGElement, MouseEvent>,
2020-07-30 22:58:23 +03:00
eventType: BarAction,
2020-07-22 20:50:43 +03:00
task: BarTask
) => {
switch (event.type) {
2020-08-05 08:14:22 +03:00
case "mousedown":
2020-07-30 22:58:23 +03:00
setBarEvent({ ...barEvent, selectedTask: task, action: eventType });
2020-07-22 20:50:43 +03:00
setInitEventX1Delta(event.nativeEvent.offsetX - task.x1);
event.stopPropagation();
break;
2020-08-05 08:14:22 +03:00
case "mouseleave":
2020-07-30 22:58:23 +03:00
if (!barEvent.action)
2020-08-05 08:14:22 +03:00
setBarEvent({ ...barEvent, selectedTask: null, action: "" });
2020-07-22 20:50:43 +03:00
break;
2020-08-05 08:14:22 +03:00
case "mouseenter":
2020-07-30 22:58:23 +03:00
if (!barEvent.selectedTask) {
2020-08-05 08:14:22 +03:00
setBarEvent({ ...barEvent, selectedTask: task, action: "" });
2020-07-22 20:50:43 +03:00
}
break;
}
};
/**
* Method handles Bar keyboard events
* @param event
* @param task
*/
const handleButtonSVGEvents = async (
event: React.KeyboardEvent<SVGGElement>,
task: BarTask
) => {
if (task.isDisabled) return;
switch (event.key) {
2020-08-05 08:14:22 +03:00
case "Delete": {
2020-07-22 20:50:43 +03:00
if (onTaskDelete) {
onTaskDelete(task);
}
break;
}
}
};
return (
2020-08-05 08:14:22 +03:00
<g className="content">
<g className="arrows" fill={arrowColor} stroke={arrowColor}>
2020-07-22 20:50:43 +03:00
{barTasks.map(task => {
return task.barChildren.map(child => {
return (
<Arrow
key={`Arrow from ${task.id} to ${barTasks[child].id}`}
taskFrom={task}
taskTo={barTasks[child]}
rowHeight={rowHeight}
arrowIndent={arrowIndent}
/>
);
});
})}
</g>
<g className="bar" fontFamily={fontFamily} fontSize={fontSize}>
{barTasks.map(task => {
return (
<Bar
task={task}
arrowIndent={arrowIndent}
isProgressChangeable={!!onProgressChange && !task.isDisabled}
onDoubleClick={onDoubleClick}
isDateChangeable={!!onDateChange && !task.isDisabled}
handleMouseEvents={handleMouseEvents}
handleButtonSVGEvents={handleButtonSVGEvents}
key={task.id}
/>
);
})}
</g>
<g className="toolTip">
2020-07-30 22:58:23 +03:00
{barEvent.selectedTask &&
2020-08-05 08:14:22 +03:00
barEvent.action !== "end" &&
barEvent.action !== "start" && (
2020-07-30 22:58:23 +03:00
<Tooltip
x={barEvent.selectedTask.x2 + columnWidth + arrowIndent}
y={barEvent.selectedTask.y + rowHeight}
task={barEvent.selectedTask}
fontFamily={fontFamily}
fontSize={fontSize}
getTooltipContent={getTooltipContent}
/>
)}
2020-07-22 20:50:43 +03:00
</g>
2020-08-05 08:14:22 +03:00
</g>
2020-07-22 20:50:43 +03:00
);
};