Merge pull request #40 from MaTeMaTuK/dev

Dev
This commit is contained in:
MaTeMaTuK 2022-02-13 20:36:08 +01:00 committed by GitHub
commit 26c16ce08f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 178 additions and 37 deletions

1
.gitignore vendored
View File

@ -20,3 +20,4 @@ dist
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
package-lock.json

View File

@ -76,7 +76,7 @@ npm start
| onDateChange\* | (task: Task, children: Task[]) => void/boolean/Promise<void>/Promise<boolean> | Specifies the function to be executed when drag taskbar event on timeline has finished. | | onDateChange\* | (task: Task, children: Task[]) => void/boolean/Promise<void>/Promise<boolean> | Specifies the function to be executed when drag taskbar event on timeline has finished. |
| onProgressChange\* | (task: Task, children: Task[]) => void/boolean/Promise<void>/Promise<boolean> | Specifies the function to be executed when drag taskbar progress event has finished. | | onProgressChange\* | (task: Task, children: Task[]) => void/boolean/Promise<void>/Promise<boolean> | Specifies the function to be executed when drag taskbar progress event has finished. |
| onExpanderClick\* | onExpanderClick: (task: Task) => void; | Specifies the function to be executed on the table expander click | | onExpanderClick\* | onExpanderClick: (task: Task) => void; | Specifies the function to be executed on the table expander click |
| timeStep | (task: Task) => number | A time step value for onDateChange. Specify in milliseconds. | | timeStep | number | A time step value for onDateChange. Specify in milliseconds. |
\* Chart undoes operation if method return false or error. Parameter children returns one level deep records. \* Chart undoes operation if method return false or error. Parameter children returns one level deep records.

View File

@ -9,7 +9,7 @@ const App = () => {
const [view, setView] = React.useState<ViewMode>(ViewMode.Day); const [view, setView] = React.useState<ViewMode>(ViewMode.Day);
const [tasks, setTasks] = React.useState<Task[]>(initTasks()); const [tasks, setTasks] = React.useState<Task[]>(initTasks());
const [isChecked, setIsChecked] = React.useState(true); const [isChecked, setIsChecked] = React.useState(true);
let columnWidth = 60; let columnWidth = 65;
if (view === ViewMode.Month) { if (view === ViewMode.Month) {
columnWidth = 300; columnWidth = 300;
} else if (view === ViewMode.Week) { } else if (view === ViewMode.Week) {

View File

@ -13,6 +13,12 @@ export const ViewSwitcher: React.SFC<ViewSwitcherProps> = ({
}) => { }) => {
return ( return (
<div className="ViewContainer"> <div className="ViewContainer">
<button
className="Button"
onClick={() => onViewModeChange(ViewMode.Hour)}
>
Hour
</button>
<button <button
className="Button" className="Button"
onClick={() => onViewModeChange(ViewMode.QuarterDay)} onClick={() => onViewModeChange(ViewMode.QuarterDay)}

View File

@ -10,8 +10,8 @@ export function initTasks() {
id: "ProjectSample", id: "ProjectSample",
progress: 25, progress: 25,
type: "project", type: "project",
hideChildren: false, hideChildren: false,
displayOrder: 1,
}, },
{ {
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 1), start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 1),
@ -27,6 +27,7 @@ export function initTasks() {
progress: 45, progress: 45,
type: "task", type: "task",
project: "ProjectSample", project: "ProjectSample",
displayOrder: 2,
}, },
{ {
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 2), start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 2),
@ -37,6 +38,7 @@ export function initTasks() {
dependencies: ["Task 0"], dependencies: ["Task 0"],
type: "task", type: "task",
project: "ProjectSample", project: "ProjectSample",
displayOrder: 3,
}, },
{ {
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 4), start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 4),
@ -47,6 +49,7 @@ export function initTasks() {
dependencies: ["Task 1"], dependencies: ["Task 1"],
type: "task", type: "task",
project: "ProjectSample", project: "ProjectSample",
displayOrder: 4,
}, },
{ {
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 8), start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 8),
@ -57,6 +60,7 @@ export function initTasks() {
dependencies: ["Task 2"], dependencies: ["Task 2"],
type: "task", type: "task",
project: "ProjectSample", project: "ProjectSample",
displayOrder: 5,
}, },
{ {
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 8), start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 8),
@ -67,6 +71,7 @@ export function initTasks() {
progress: 70, progress: 70,
dependencies: ["Task 2"], dependencies: ["Task 2"],
project: "ProjectSample", project: "ProjectSample",
displayOrder: 6,
}, },
{ {
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 15), start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 15),
@ -77,6 +82,7 @@ export function initTasks() {
type: "milestone", type: "milestone",
dependencies: ["Task 4"], dependencies: ["Task 4"],
project: "ProjectSample", project: "ProjectSample",
displayOrder: 7,
}, },
{ {
start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 18), start: new Date(currentDate.getFullYear(), currentDate.getMonth(), 18),

View File

@ -2,4 +2,4 @@
// allows you to do things like: // allows you to do things like:
// expect(element).toHaveTextContent(/react/i) // expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom // learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect'; import "@testing-library/jest-dom/extend-expect";

View File

@ -1,6 +1,6 @@
{ {
"name": "gantt-task-react", "name": "gantt-task-react",
"version": "0.3.7", "version": "0.3.8",
"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",

View File

@ -4,6 +4,7 @@ import { TopPartOfCalendar } from "./top-part-of-calendar";
import { import {
getCachedDateTimeFormat, getCachedDateTimeFormat,
getDaysInMonth, getDaysInMonth,
getLocalDayOfWeek,
getLocaleMonth, getLocaleMonth,
getWeekNumberISO8601, getWeekNumberISO8601,
} from "../../helpers/date-helper"; } from "../../helpers/date-helper";
@ -131,7 +132,9 @@ export const Calendar: React.FC<CalendarProps> = ({
const dates = dateSetup.dates; 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 = `${getLocalDayOfWeek(date, locale, "short")}, ${date
.getDate()
.toString()}`;
bottomValues.push( bottomValues.push(
<text <text
@ -170,7 +173,7 @@ export const Calendar: React.FC<CalendarProps> = ({
return [topValues, bottomValues]; return [topValues, bottomValues];
}; };
const getCalendarValuesForOther = () => { const getCalendarValuesForPartOfDay = () => {
const topValues: ReactChild[] = []; const topValues: ReactChild[] = [];
const bottomValues: ReactChild[] = []; const bottomValues: ReactChild[] = [];
const ticks = viewMode === ViewMode.HalfDay ? 2 : 4; const ticks = viewMode === ViewMode.HalfDay ? 2 : 4;
@ -194,7 +197,11 @@ export const Calendar: React.FC<CalendarProps> = ({
</text> </text>
); );
if (i === 0 || date.getDate() !== dates[i - 1].getDate()) { if (i === 0 || date.getDate() !== dates[i - 1].getDate()) {
const topValue = `${date.getDate()} ${getLocaleMonth(date, locale)}`; const topValue = `${getLocalDayOfWeek(
date,
locale,
"short"
)}, ${date.getDate()} ${getLocaleMonth(date, locale)}`;
topValues.push( topValues.push(
<TopPartOfCalendar <TopPartOfCalendar
key={topValue + date.getFullYear()} key={topValue + date.getFullYear()}
@ -212,6 +219,52 @@ export const Calendar: React.FC<CalendarProps> = ({
return [topValues, bottomValues]; return [topValues, bottomValues];
}; };
const getCalendarValuesForHour = () => {
const topValues: ReactChild[] = [];
const bottomValues: ReactChild[] = [];
const topDefaultHeight = headerHeight * 0.5;
const dates = dateSetup.dates;
for (let i = 0; i < dates.length; i++) {
const date = dates[i];
const bottomValue = getCachedDateTimeFormat(locale, {
hour: "numeric",
}).format(date);
bottomValues.push(
<text
key={date.getTime()}
y={headerHeight * 0.8}
x={columnWidth * (i + +rtl)}
className={styles.calendarBottomText}
fontFamily={fontFamily}
>
{bottomValue}
</text>
);
if (i === 0 || date.getDate() !== dates[i - 1].getDate()) {
const topValue = `${getLocalDayOfWeek(
date,
locale,
"long"
)}, ${date.getDate()} ${getLocaleMonth(date, locale)}`;
const topPosition = (date.getHours() - 24) / 2;
topValues.push(
<TopPartOfCalendar
key={topValue + date.getFullYear()}
value={topValue}
x1Line={columnWidth * i}
y1Line={0}
y2Line={topDefaultHeight}
xText={columnWidth * (i + topPosition)}
yText={topDefaultHeight * 0.9}
/>
);
}
}
return [topValues, bottomValues];
};
let topValues: ReactChild[] = []; let topValues: ReactChild[] = [];
let bottomValues: ReactChild[] = []; let bottomValues: ReactChild[] = [];
switch (dateSetup.viewMode) { switch (dateSetup.viewMode) {
@ -224,9 +277,12 @@ export const Calendar: React.FC<CalendarProps> = ({
case ViewMode.Day: case ViewMode.Day:
[topValues, bottomValues] = getCalendarValuesForDay(); [topValues, bottomValues] = getCalendarValuesForDay();
break; break;
default: case ViewMode.QuarterDay:
[topValues, bottomValues] = getCalendarValuesForOther(); case ViewMode.HalfDay:
[topValues, bottomValues] = getCalendarValuesForPartOfDay();
break; break;
case ViewMode.Hour:
[topValues, bottomValues] = getCalendarValuesForHour();
} }
return ( return (
<g className="calendar" fontSize={fontSize} fontFamily={fontFamily}> <g className="calendar" fontSize={fontSize} fontFamily={fontFamily}>

View File

@ -1,4 +1,10 @@
import React, { useState, SyntheticEvent, useRef, useEffect } from "react"; import React, {
useState,
SyntheticEvent,
useRef,
useEffect,
useMemo,
} from "react";
import { ViewMode, GanttProps, Task } from "../../types/public-types"; import { ViewMode, GanttProps, Task } 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";
@ -16,7 +22,7 @@ import { GanttEvent } from "../../types/gantt-task-actions";
import { DateSetup } from "../../types/date-setup"; import { DateSetup } from "../../types/date-setup";
import styles from "./gantt.module.css"; import styles from "./gantt.module.css";
import { HorizontalScroll } from "../other/horizontal-scroll"; import { HorizontalScroll } from "../other/horizontal-scroll";
import { removeHiddenTasks } from "../../helpers/other-helper"; import { removeHiddenTasks, sortTasks } from "../../helpers/other-helper";
export const Gantt: React.FunctionComponent<GanttProps> = ({ export const Gantt: React.FunctionComponent<GanttProps> = ({
tasks, tasks,
@ -47,6 +53,7 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({
fontSize = "14px", fontSize = "14px",
arrowIndent = 20, arrowIndent = 20,
todayColor = "rgba(252, 248, 227, 0.5)", todayColor = "rgba(252, 248, 227, 0.5)",
viewDate,
TooltipContent = StandardTooltipContent, TooltipContent = StandardTooltipContent,
TaskListHeader = TaskListHeaderDefault, TaskListHeader = TaskListHeaderDefault,
TaskListTable = TaskListTableDefault, TaskListTable = TaskListTableDefault,
@ -63,8 +70,10 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({
const [startDate, endDate] = ganttDateRange(tasks, viewMode); const [startDate, endDate] = ganttDateRange(tasks, viewMode);
return { viewMode, dates: seedDates(startDate, endDate, viewMode) }; return { viewMode, dates: seedDates(startDate, endDate, viewMode) };
}); });
const [currentViewDate, setCurrentViewDate] = useState<Date | undefined>(
undefined
);
const [taskHeight, setTaskHeight] = useState((rowHeight * barFill) / 100);
const [taskListWidth, setTaskListWidth] = useState(0); const [taskListWidth, setTaskListWidth] = useState(0);
const [svgContainerWidth, setSvgContainerWidth] = useState(0); const [svgContainerWidth, setSvgContainerWidth] = useState(0);
const [svgContainerHeight, setSvgContainerHeight] = useState(ganttHeight); const [svgContainerHeight, setSvgContainerHeight] = useState(ganttHeight);
@ -72,6 +81,10 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({
const [ganttEvent, setGanttEvent] = useState<GanttEvent>({ const [ganttEvent, setGanttEvent] = useState<GanttEvent>({
action: "", action: "",
}); });
const taskHeight = useMemo(
() => (rowHeight * barFill) / 100,
[rowHeight, barFill]
);
const [selectedTask, setSelectedTask] = useState<BarTask>(); const [selectedTask, setSelectedTask] = useState<BarTask>();
const [failedTask, setFailedTask] = useState<BarTask | null>(null); const [failedTask, setFailedTask] = useState<BarTask | null>(null);
@ -91,6 +104,7 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({
} else { } else {
filteredTasks = tasks; filteredTasks = tasks;
} }
filteredTasks = filteredTasks.sort(sortTasks);
const [startDate, endDate] = ganttDateRange(filteredTasks, viewMode); const [startDate, endDate] = ganttDateRange(filteredTasks, viewMode);
let newDates = seedDates(startDate, endDate, viewMode); let newDates = seedDates(startDate, endDate, viewMode);
if (rtl) { if (rtl) {
@ -145,6 +159,34 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({
onExpanderClick, onExpanderClick,
]); ]);
useEffect(() => {
if (
viewMode === dateSetup.viewMode &&
((viewDate && !currentViewDate) ||
(viewDate && currentViewDate?.valueOf() !== viewDate.valueOf()))
) {
const dates = dateSetup.dates;
const index = dates.findIndex(
(d, i) =>
viewDate.valueOf() >= d.valueOf() &&
i + 1 !== dates.length &&
viewDate.valueOf() < dates[i + 1].valueOf()
);
if (index === -1) {
return;
}
setCurrentViewDate(viewDate);
setScrollX(columnWidth * index);
}
}, [
viewDate,
columnWidth,
dateSetup.dates,
viewMode,
currentViewDate,
setCurrentViewDate,
]);
useEffect(() => { useEffect(() => {
const { changedTask, action } = ganttEvent; const { changedTask, action } = ganttEvent;
if (changedTask) { if (changedTask) {
@ -181,13 +223,6 @@ export const Gantt: React.FunctionComponent<GanttProps> = ({
} }
}, [failedTask, barTasks]); }, [failedTask, barTasks]);
useEffect(() => {
const newTaskHeight = (rowHeight * barFill) / 100;
if (newTaskHeight !== taskHeight) {
setTaskHeight(newTaskHeight);
}
}, [rowHeight, barFill, taskHeight]);
useEffect(() => { useEffect(() => {
if (!listCellWidth) { if (!listCellWidth) {
setTaskListWidth(0); setTaskListWidth(0);

View File

@ -3,18 +3,17 @@ import styles from "./task-list-table.module.css";
import { Task } from "../../types/public-types"; import { Task } from "../../types/public-types";
const localeDateStringCache = {}; const localeDateStringCache = {};
const toLocaleDateStringFactory = (locale: string) => ( const toLocaleDateStringFactory =
date: Date, (locale: string) =>
dateTimeOptions: Intl.DateTimeFormatOptions (date: Date, dateTimeOptions: Intl.DateTimeFormatOptions) => {
) => { const key = date.toString();
const key = date.toString(); let lds = localeDateStringCache[key];
let lds = localeDateStringCache[key]; if (!lds) {
if (!lds) { lds = date.toLocaleDateString(locale, dateTimeOptions);
lds = date.toLocaleDateString(locale, dateTimeOptions); localeDateStringCache[key] = lds;
localeDateStringCache[key] = lds; }
} return lds;
return lds; };
};
const dateTimeOptions: Intl.DateTimeFormatOptions = { const dateTimeOptions: Intl.DateTimeFormatOptions = {
weekday: "short", weekday: "short",
year: "numeric", year: "numeric",
@ -41,9 +40,10 @@ export const TaskListTableDefault: React.FC<{
locale, locale,
onExpanderClick, onExpanderClick,
}) => { }) => {
const toLocaleDateString = useMemo(() => toLocaleDateStringFactory(locale), [ const toLocaleDateString = useMemo(
locale, () => toLocaleDateStringFactory(locale),
]); [locale]
);
return ( return (
<div <div

View File

@ -25,7 +25,6 @@ export const getCachedDateTimeFormat = (
return dtf; return dtf;
}; };
export const addToDate = ( export const addToDate = (
date: Date, date: Date,
quantity: number, quantity: number,
@ -112,6 +111,12 @@ export const ganttDateRange = (tasks: Task[], viewMode: ViewMode) => {
newStartDate = addToDate(newStartDate, -1, "day"); newStartDate = addToDate(newStartDate, -1, "day");
newEndDate = addToDate(newEndDate, 108, "hour"); // 24(1 day)*5 - 12 newEndDate = addToDate(newEndDate, 108, "hour"); // 24(1 day)*5 - 12
break; break;
case ViewMode.Hour:
newStartDate = startOfDate(newStartDate, "hour");
newEndDate = startOfDate(newEndDate, "day");
newStartDate = addToDate(newStartDate, -1, "hour");
newEndDate = addToDate(newEndDate, 1, "day");
break;
} }
return [newStartDate, newEndDate]; return [newStartDate, newEndDate];
}; };
@ -140,6 +145,9 @@ export const seedDates = (
case ViewMode.QuarterDay: case ViewMode.QuarterDay:
currentDate = addToDate(currentDate, 6, "hour"); currentDate = addToDate(currentDate, 6, "hour");
break; break;
case ViewMode.Hour:
currentDate = addToDate(currentDate, 1, "hour");
break;
} }
dates.push(currentDate); dates.push(currentDate);
} }
@ -157,6 +165,21 @@ export const getLocaleMonth = (date: Date, locale: string) => {
return bottomValue; return bottomValue;
}; };
export const getLocalDayOfWeek = (
date: Date,
locale: string,
format?: "long" | "short" | "narrow" | undefined
) => {
let bottomValue = getCachedDateTimeFormat(locale, {
weekday: format,
}).format(date);
bottomValue = bottomValue.replace(
bottomValue[0],
bottomValue[0].toLocaleUpperCase()
);
return bottomValue;
};
/** /**
* Returns monday of current week * Returns monday of current week
* @param date date for modify * @param date date for modify
@ -190,4 +213,3 @@ export const getWeekNumberISO8601 = (date: Date) => {
export const getDaysInMonth = (month: number, year: number) => { export const getDaysInMonth = (month: number, year: number) => {
return new Date(year, month + 1, 0).getDate(); return new Date(year, month + 1, 0).getDate();
}; };

View File

@ -48,3 +48,15 @@ function getChildren(taskList: Task[], task: Task) {
tasks = tasks.concat(tasks, taskChildren); tasks = tasks.concat(tasks, taskChildren);
return tasks; return tasks;
} }
export const sortTasks = (taskA: Task, taskB: Task) => {
const orderA = taskA.displayOrder || Number.MAX_VALUE;
const orderB = taskB.displayOrder || Number.MAX_VALUE;
if (orderA > orderB) {
return 1;
} else if (orderA < orderB) {
return -1;
} else {
return 0;
}
};

View File

@ -1,4 +1,5 @@
export enum ViewMode { export enum ViewMode {
Hour = "Hour",
QuarterDay = "Quarter Day", QuarterDay = "Quarter Day",
HalfDay = "Half Day", HalfDay = "Half Day",
Day = "Day", Day = "Day",
@ -27,6 +28,7 @@ export interface Task {
project?: string; project?: string;
dependencies?: string[]; dependencies?: string[];
hideChildren?: boolean; hideChildren?: boolean;
displayOrder?: number;
} }
export interface EventOption { export interface EventOption {
@ -68,6 +70,7 @@ export interface EventOption {
export interface DisplayOption { export interface DisplayOption {
viewMode?: ViewMode; viewMode?: ViewMode;
viewDate?: Date;
/** /**
* Specifies the month name language. Able formats: ISO 639-2, Java Locale * Specifies the month name language. Able formats: ISO 639-2, Java Locale
*/ */