feat: gant expend when table expend

This commit is contained in:
katherinehhh 2023-01-31 15:45:15 +08:00
parent 5a61dd59a9
commit 57448842da
6 changed files with 164 additions and 205 deletions

View File

@ -1,10 +1,107 @@
import { ArrayField } from '@formily/core';
import { useField } from '@formily/react';
import React, { createContext, useContext, useEffect } from 'react';
import React, { createContext, useContext, useEffect, useState } from 'react';
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
export const GanttBlockContext = createContext<any>({});
export function mockTasks() {
const tasks: any[] = [
{
phone: '1767676334',
examiner: 'chao',
personInCharge: 'shery',
end: '2023-09-01T07:29:41.703Z',
start: '2023-01-01T07:33:20.747Z',
title: 'project1',
id: 1,
children: [
{
phone: '1767676334',
examiner: 'katherine',
personInCharge: 'lucy',
end: '2023-01-14T07:33:28.108Z',
start: '2023-01-01T07:33:20.747Z',
title: '需求调研',
id: 11,
},
{
examiner: 'zhou',
personInCharge: 'bery',
end: '2023-04-28T07:26:14.103Z',
start: '2023-02-15T07:26:02.830Z',
phone: '18767646573',
title: '功能设计',
id: 12,
},
{
examiner: 'lin',
personInCharge: 'katy',
phone: '18787834575',
end: '2023-07-31T07:27:18.187Z',
start: '2023-04-29T07:27:07.136Z',
title: '功能开发',
id: 13,
},
{
examiner: 'hong',
personInCharge: 'lily',
phone: '18767676773',
end: '2023-08-29T07:28:29.740Z',
start: '2023-07-31T07:28:04.313Z',
title: '功能测试',
id: 14,
},
{
examiner: 'zhuo',
personInCharge: 'katy',
phone: '176572345',
end: '2023-09-01T07:29:41.703Z',
start: '2023-08-30T07:29:34.178Z',
title: '功能验收',
id: 15,
},
],
},
{
examiner: null,
personInCharge: 'all',
phone: null,
end: '2023-11-09T07:30:40.449Z',
start: '2023-11-01T07:30:30.893Z',
title: '团建',
},
];
return tasks;
}
const formatData = (data = [], fieldNames, tasks = [], projectId = undefined) => {
data.forEach((item) => {
if (item.children && item.children.length) {
tasks.push({
start: new Date(item[fieldNames.start]),
end: new Date(item[fieldNames.end]),
name: item[fieldNames.title],
id: item.id + '',
type: 'project',
progress: item[fieldNames.progress],
hideChildren: true,
});
formatData(item.children, fieldNames, tasks, item.id + '');
} else {
tasks.push({
start: new Date(item[fieldNames.start]),
end: new Date(item[fieldNames.end]),
name: item[fieldNames.title],
id: item.id + '',
type: 'task',
progress: item[fieldNames.progress],
project: projectId,
});
}
});
return tasks;
};
const InternalGanttBlockProvider = (props) => {
const { fieldNames, timeRange } = props;
const field = useField();
@ -41,14 +138,21 @@ export const useGanttBlockContext = () => {
export const useGanttBlockProps = () => {
const ctx = useGanttBlockContext();
const field = useField<ArrayField>();
const [tasks, setTasks] = useState([]);
const onExpanderClick = (task: any) => {
console.log(tasks, task);
setTasks(tasks.map((t) => (t.id === task.id ? task : t)));
};
useEffect(() => {
if (!ctx?.service?.loading) {
field.componentProps.dataSource = ctx?.service?.data?.data;
setTasks(formatData(ctx.service.data?.data, ctx.fieldNames));
}
}, [ctx?.service?.loading]);
return {
fieldNames: ctx.fieldNames,
timeRange: ctx.timeRange,
onExpanderClick,
tasks,
};
};

View File

@ -148,6 +148,8 @@ export const useTableBlockProps = () => {
: globalSort || ctx.service.params?.[0]?.sort;
ctx.service.run({ ...ctx.service.params?.[0], page: current, pageSize, sort });
},
onExpand(expanded, record) {
ctx?.onExpandClick(expanded, record);
},
};
};

View File

@ -1,15 +1,15 @@
import React, { ReactChild } from "react";
import { ViewMode } from "../../types/public-types";
import { TopPartOfCalendar } from "./top-part-of-calendar";
import React, { ReactChild } from 'react';
import { ViewMode } from '../../types/public-types';
import { TopPartOfCalendar } from './top-part-of-calendar';
import {
getCachedDateTimeFormat,
getDaysInMonth,
getLocalDayOfWeek,
getLocaleMonth,
getWeekNumberISO8601,
} from "../../helpers/date-helper";
import { DateSetup } from "../../types/date-setup";
import styles from "./calendar.module.css";
} from '../../helpers/date-helper';
import { DateSetup } from '../../types/date-setup';
import styles from './calendar.module.css';
export type CalendarProps = {
dateSetup: DateSetup;
@ -47,12 +47,9 @@ export const Calendar: React.FC<CalendarProps> = ({
className={styles.calendarBottomText}
>
{bottomValue}
</text>
</text>,
);
if (
i === 0 ||
date.getFullYear() !== dateSetup.dates[i - 1].getFullYear()
) {
if (i === 0 || date.getFullYear() !== dateSetup.dates[i - 1].getFullYear()) {
const topValue = date.getFullYear().toString();
let xText: number;
if (rtl) {
@ -69,7 +66,7 @@ export const Calendar: React.FC<CalendarProps> = ({
y2Line={headerHeight}
xText={xText}
yText={topDefaultHeight * 0.9}
/>
/>,
);
}
}
@ -83,7 +80,7 @@ export const Calendar: React.FC<CalendarProps> = ({
for (let i = 0; i < dateSetup.dates.length; i++) {
const date = dateSetup.dates[i];
// const bottomValue = getLocaleMonth(date, locale);
const quarter = "Q" + Math.floor((date.getMonth() + 3) / 3);
const quarter = 'Q' + Math.floor((date.getMonth() + 3) / 3);
bottomValues.push(
<text
key={date.getTime()}
@ -92,12 +89,9 @@ export const Calendar: React.FC<CalendarProps> = ({
className={styles.calendarBottomText}
>
{quarter}
</text>
</text>,
);
if (
i === 0 ||
date.getFullYear() !== dateSetup.dates[i - 1].getFullYear()
) {
if (i === 0 || date.getFullYear() !== dateSetup.dates[i - 1].getFullYear()) {
const topValue = date.getFullYear().toString();
let xText: number;
if (rtl) {
@ -114,7 +108,7 @@ export const Calendar: React.FC<CalendarProps> = ({
y2Line={topDefaultHeight}
xText={Math.abs(xText)}
yText={topDefaultHeight * 0.9}
/>
/>,
);
}
}
@ -136,12 +130,9 @@ export const Calendar: React.FC<CalendarProps> = ({
className={styles.calendarBottomText}
>
{bottomValue}
</text>
</text>,
);
if (
i === 0 ||
date.getFullYear() !== dateSetup.dates[i - 1].getFullYear()
) {
if (i === 0 || date.getFullYear() !== dateSetup.dates[i - 1].getFullYear()) {
const topValue = date.getFullYear().toString();
let xText: number;
if (rtl) {
@ -158,7 +149,7 @@ export const Calendar: React.FC<CalendarProps> = ({
y2Line={topDefaultHeight}
xText={xText}
yText={topDefaultHeight * 0.9}
/>
/>,
);
}
}
@ -173,7 +164,7 @@ export const Calendar: React.FC<CalendarProps> = ({
const dates = dateSetup.dates;
for (let i = dates.length - 1; i >= 0; i--) {
const date = dates[i];
let topValue = "";
let topValue = '';
if (i === 0 || date.getMonth() !== dates[i - 1].getMonth()) {
// top
topValue = `${getLocaleMonth(date, locale)}, ${date.getFullYear()}`;
@ -189,7 +180,7 @@ export const Calendar: React.FC<CalendarProps> = ({
className={styles.calendarBottomText}
>
{bottomValue}
</text>
</text>,
);
if (topValue) {
@ -204,7 +195,7 @@ export const Calendar: React.FC<CalendarProps> = ({
y2Line={topDefaultHeight}
xText={columnWidth * i + columnWidth * weeksCount * 0.5}
yText={topDefaultHeight * 0.9}
/>
/>,
);
}
weeksCount = 0;
@ -221,9 +212,7 @@ export const Calendar: React.FC<CalendarProps> = ({
const dates = dateSetup.dates;
for (let i = 0; i < dates.length; i++) {
const date = dates[i];
const bottomValue = `${getLocalDayOfWeek(date, locale, "short")}, ${date
.getDate()
.toString()}`;
const bottomValue = `${getLocalDayOfWeek(date, locale, 'short')}, ${date.getDate().toString()}`;
bottomValues.push(
<text
@ -233,12 +222,9 @@ export const Calendar: React.FC<CalendarProps> = ({
className={styles.calendarBottomText}
>
{bottomValue}
</text>
</text>,
);
if (
i + 1 !== dates.length &&
date.getMonth() !== dates[i + 1].getMonth()
) {
if (i + 1 !== dates.length && date.getMonth() !== dates[i + 1].getMonth()) {
const topValue = getLocaleMonth(date, locale);
topValues.push(
@ -248,14 +234,9 @@ export const Calendar: React.FC<CalendarProps> = ({
x1Line={columnWidth * (i + 1)}
y1Line={0}
y2Line={topDefaultHeight}
xText={
columnWidth * (i + 1) -
getDaysInMonth(date.getMonth(), date.getFullYear()) *
columnWidth *
0.5
}
xText={columnWidth * (i + 1) - getDaysInMonth(date.getMonth(), date.getFullYear()) * columnWidth * 0.5}
yText={topDefaultHeight * 0.9}
/>
/>,
);
}
}
@ -271,7 +252,8 @@ export const Calendar: React.FC<CalendarProps> = ({
for (let i = 0; i < dates.length; i++) {
const date = dates[i];
const bottomValue = getCachedDateTimeFormat(locale, {
hour: "numeric",
hour: 'numeric',
//@ts-ignore
}).format(date);
bottomValues.push(
@ -283,14 +265,13 @@ export const Calendar: React.FC<CalendarProps> = ({
fontFamily={fontFamily}
>
{bottomValue}
</text>
</text>,
);
if (i === 0 || date.getDate() !== dates[i - 1].getDate()) {
const topValue = `${getLocalDayOfWeek(
const topValue = `${getLocalDayOfWeek(date, locale, 'short')}, ${date.getDate()} ${getLocaleMonth(
date,
locale,
"short"
)}, ${date.getDate()} ${getLocaleMonth(date, locale)}`;
)}`;
topValues.push(
<TopPartOfCalendar
key={topValue + date.getFullYear()}
@ -300,7 +281,7 @@ export const Calendar: React.FC<CalendarProps> = ({
y2Line={topDefaultHeight}
xText={columnWidth * i + ticks * columnWidth * 0.5}
yText={topDefaultHeight * 0.9}
/>
/>,
);
}
}
@ -316,7 +297,8 @@ export const Calendar: React.FC<CalendarProps> = ({
for (let i = 0; i < dates.length; i++) {
const date = dates[i];
const bottomValue = getCachedDateTimeFormat(locale, {
hour: "numeric",
hour: 'numeric',
//@ts-ignore
}).format(date);
bottomValues.push(
@ -328,15 +310,14 @@ export const Calendar: React.FC<CalendarProps> = ({
fontFamily={fontFamily}
>
{bottomValue}
</text>
</text>,
);
if (i !== 0 && date.getDate() !== dates[i - 1].getDate()) {
const displayDate = dates[i - 1];
const topValue = `${getLocalDayOfWeek(
const topValue = `${getLocalDayOfWeek(displayDate, locale, 'long')}, ${displayDate.getDate()} ${getLocaleMonth(
displayDate,
locale,
"long"
)}, ${displayDate.getDate()} ${getLocaleMonth(displayDate, locale)}`;
)}`;
const topPosition = (date.getHours() - 24) / 2;
topValues.push(
<TopPartOfCalendar
@ -347,7 +328,7 @@ export const Calendar: React.FC<CalendarProps> = ({
y2Line={topDefaultHeight}
xText={columnWidth * (i + topPosition)}
yText={topDefaultHeight * 0.9}
/>
/>,
);
}
}

View File

@ -18,8 +18,6 @@ import styles from './gantt.module.css';
import { GanttToolbarContext } from '../../context';
import { useDesignable } from '../../../../../schema-component';
import { TableBlockProvider, useGanttBlockContext, useBlockRequestContext } from '../../../../../block-provider';
import { useProps } from '../../../../hooks/useProps';
import { formatData, mockTasks } from './utils';
function Toolbar(props) {
const fieldSchema = useFieldSchema();
@ -47,7 +45,6 @@ const getColumnWidth = (dataSetLength, clientWidth) => {
export const Gantt: any = (props) => {
const { designable } = useDesignable();
const {
headerHeight = designable ? 65 : 55,
listCellWidth = '155px',
@ -81,14 +78,14 @@ export const Gantt: any = (props) => {
onClick,
onDelete,
onSelect,
onExpanderClick,
useProps,
} = props;
const { onExpanderClick, tasks } = useProps();
const ctx = useGanttBlockContext();
const { resource } = useBlockRequestContext();
const fieldSchema = useFieldSchema();
const { fieldNames, dataSource } = useProps(props);
const { fieldNames } = useProps(props);
const viewMode = fieldNames.range || 'day';
const tasks = formatData(dataSource, fieldNames) || [];
const wrapperRef = useRef<HTMLDivElement>(null);
const taskListRef = useRef<HTMLDivElement>(null);
const verticalGanttContainerRef = useRef<HTMLDivElement>(null);
@ -155,7 +152,7 @@ export const Gantt: any = (props) => {
),
);
}, [
dataSource,
tasks,
viewMode,
preStepsCount,
rowHeight,
@ -175,7 +172,6 @@ export const Gantt: any = (props) => {
milestoneBackgroundSelectedColor,
rtl,
scrollX,
onExpanderClick,
]);
useEffect(() => {
@ -366,11 +362,12 @@ export const Gantt: any = (props) => {
}
setSelectedTask(newSelectedTask);
};
// const handleExpanderClick = (task: Task) => {
// if (onExpanderClick && task.hideChildren !== undefined) {
// onExpanderClick({ ...task, hideChildren: !task.hideChildren });
// }
// };
const handleTableExpanderClick = (expanded: boolean, record: any) => {
const task = tasks.find((v) => v.id === record.id + '');
if (onExpanderClick && record.children.length) {
onExpanderClick({ ...task, hideChildren: !expanded });
}
};
const handleProgressChange = async (task) => {
await resource.update({
filterByTk: task.id,
@ -434,38 +431,23 @@ export const Gantt: any = (props) => {
onDelete,
};
// const tableProps: TaskListProps = {
// rowHeight,
// rowWidth: listCellWidth,
// fontFamily,
// fontSize,
// tasks: barTasks,
// locale,
// headerHeight,
// scrollY,
// ganttHeight,
// horizontalContainerClass: styles.horizontalContainer,
// selectedTask,
// taskListRef,
// setSelectedTask: handleSelectedTask,
// onExpanderClick: handleExpanderClick,
// TaskListHeader,
// TaskListTable,
// };
return (
<div>
<Toolbar />
<div>
<TableBlockProvider
service={ctx.service}
{...ctx}
params={{
paginate: false,
}}
service={{
...ctx.service,
}}
onExpandClick={handleTableExpanderClick}
>
<RecursionField name={'table'} schema={fieldSchema.properties.table} />
</TableBlockProvider>
<div className={styles.wrapper} onKeyDown={handleKeyDown} tabIndex={0} ref={wrapperRef}>
<TaskGantt
gridProps={gridProps}

View File

@ -1,112 +0,0 @@
export function mockTasks() {
const currentDate = new Date();
const tasks: any[] = [
{
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",
hideChildren: false,
displayOrder: 1,
},
{
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",
displayOrder: 2,
},
{
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",
displayOrder: 3,
},
{
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",
displayOrder: 4,
},
{
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",
displayOrder: 5,
},
{
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",
displayOrder: 6,
},
{
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",
displayOrder: 7,
},
{
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 const formatData = (data = [], fieldNames) => {
const tasks: any[] = [];
data.forEach((v) => {
tasks.push({
start: new Date(v[fieldNames.start]),
end: new Date(v[fieldNames.end]),
name: v[fieldNames.title],
id: v.id + '',
type: 'task',
progress: v[fieldNames.progress],
});
});
return tasks;
};

View File

@ -43,12 +43,14 @@ const useTableColumns = () => {
...s['x-component-props'],
render: (v, record) => {
const index = field.value?.indexOf(record);
return (
return index >= 0 ? (
<RecordIndexProvider index={record.__index || index}>
<RecordProvider record={record}>
<RecursionField schema={s} name={record.__index || index} onlyRenderProperties />
</RecordProvider>
</RecordIndexProvider>
) : (
v
);
},
} as TableColumnProps<any>;