refactor: gant bar suport click

This commit is contained in:
katherinehhh 2023-02-07 10:40:38 +08:00
parent 54aa2d7f82
commit ad886ac304
5 changed files with 150 additions and 74 deletions

View File

@ -0,0 +1,6 @@
import { observer } from '@formily/react';
import React from 'react';
export const Event = observer((props) => {
return <>{props.children}</>;
});

View File

@ -1,6 +1,7 @@
import React, { useState, SyntheticEvent, useRef, useEffect, useMemo } from 'react'; import React, { useState, SyntheticEvent, useRef, useEffect, useMemo, useCallback } from 'react';
import { useFieldSchema, Schema, RecursionField } from '@formily/react'; import { useFieldSchema, Schema, RecursionField } from '@formily/react';
import { cx } from '@emotion/css'; import { cx } from '@emotion/css';
import { createForm } from '@formily/core';
import { Task } from '../../types/public-types'; import { 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,15 +17,39 @@ import { DateSetup } from '../../types/date-setup';
import { HorizontalScroll } from '../other/horizontal-scroll'; import { HorizontalScroll } from '../other/horizontal-scroll';
import { removeHiddenTasks, sortTasks } from '../../helpers/other-helper'; import { removeHiddenTasks, sortTasks } from '../../helpers/other-helper';
import { wrapper } from './style'; import { wrapper } from './style';
import { ActionContext } from '../../../action';
import { useDesignable } from '../../../../../schema-component'; import { useDesignable } from '../../../../../schema-component';
import { useGanttBlockContext, useBlockRequestContext } from '../../../../../block-provider'; import { useBlockRequestContext } from '../../../../../block-provider';
import { RecordProvider } from '../../../../../record-provider';
const getColumnWidth = (dataSetLength: any, clientWidth: any) => { const getColumnWidth = (dataSetLength: any, clientWidth: any) => {
const columnWidth = clientWidth / dataSetLength > 50 ? Math.floor(clientWidth / dataSetLength) + 20 : 50; const columnWidth = clientWidth / dataSetLength > 50 ? Math.floor(clientWidth / dataSetLength) + 20 : 50;
return columnWidth; return columnWidth;
}; };
export const DeleteEventContext = React.createContext({
close: () => {},
});
const GanttRecordViewer = (props) => {
const { visible, setVisible, record } = props;
const form = useMemo(() => createForm(), [record]);
const fieldSchema = useFieldSchema();
const eventSchema: Schema = fieldSchema.properties.detail;
const close = useCallback(() => {
setVisible(false);
}, []);
return (
eventSchema && (
<DeleteEventContext.Provider value={{ close }}>
<ActionContext.Provider value={{ visible, setVisible }}>
<RecordProvider record={record}>
<RecursionField schema={eventSchema} name={eventSchema.name} />
</RecordProvider>
</ActionContext.Provider>
</DeleteEventContext.Provider>
)
);
};
export const Gantt: any = (props: any) => { export const Gantt: any = (props: any) => {
const { designable } = useDesignable(); const { designable } = useDesignable();
const { const {
@ -63,9 +88,7 @@ export const Gantt: any = (props: any) => {
useProps, useProps,
} = props; } = props;
const { onExpanderClick, tasks } = useProps(); const { onExpanderClick, tasks } = useProps();
// const ctx = useGanttBlockContext();
const { resource, service } = useBlockRequestContext(); const { resource, service } = useBlockRequestContext();
// console.log(ctx,useBlockRequestContext())
const fieldSchema = useFieldSchema(); const fieldSchema = useFieldSchema();
const { fieldNames } = useProps(props); const { fieldNames } = useProps(props);
const viewMode = fieldNames.range || 'day'; const viewMode = fieldNames.range || 'day';
@ -76,6 +99,8 @@ export const Gantt: any = (props: any) => {
const [startDate, endDate] = ganttDateRange(tasks, viewMode, preStepsCount); const [startDate, endDate] = ganttDateRange(tasks, viewMode, preStepsCount);
return { viewMode, dates: seedDates(startDate, endDate, viewMode) }; return { viewMode, dates: seedDates(startDate, endDate, viewMode) };
}); });
const [visible, setVisible] = useState(false);
const [record, setRecord] = useState<any>({});
const [currentViewDate, setCurrentViewDate] = useState<Date | undefined>(undefined); const [currentViewDate, setCurrentViewDate] = useState<Date | undefined>(undefined);
const [taskListWidth, setTaskListWidth] = useState(0); const [taskListWidth, setTaskListWidth] = useState(0);
const [svgContainerWidth, setSvgContainerWidth] = useState(0); const [svgContainerWidth, setSvgContainerWidth] = useState(0);
@ -359,7 +384,7 @@ export const Gantt: any = (props: any) => {
[fieldNames.progress]: task.progress / 100, [fieldNames.progress]: task.progress / 100,
}, },
}); });
await service?.refresh() await service?.refresh();
}; };
const handleTaskChange = async (task: Task) => { const handleTaskChange = async (task: Task) => {
await resource.update({ await resource.update({
@ -370,7 +395,15 @@ export const Gantt: any = (props: any) => {
[fieldNames.end]: task.end, [fieldNames.end]: task.end,
}, },
}); });
await service?.refresh() await service?.refresh();
};
const handleBarClick = (data) => {
const recordData = service.data?.data?.find((item) => item.id === +data.id);
if (!recordData) {
return;
}
setRecord(recordData);
setVisible(true);
}; };
const gridProps: GridProps = { const gridProps: GridProps = {
columnWidth, columnWidth,
@ -412,13 +445,13 @@ export const Gantt: any = (props: any) => {
onDateChange: handleTaskChange, onDateChange: handleTaskChange,
onProgressChange: fieldNames.progress && handleProgressChange, onProgressChange: fieldNames.progress && handleProgressChange,
onDoubleClick, onDoubleClick,
onClick, onClick: handleBarClick,
onDelete, onDelete,
}; };
return ( return (
<div> <div>
<div> <div>
<GanttRecordViewer visible={visible} setVisible={setVisible} record={record} />
<RecursionField name={'anctionBar'} schema={fieldSchema.properties.toolBar} /> <RecursionField name={'anctionBar'} schema={fieldSchema.properties.toolBar} />
<RecursionField name={'table'} schema={fieldSchema.properties.table} /> <RecursionField name={'table'} schema={fieldSchema.properties.table} />
<div className={cx(wrapper)} onKeyDown={handleKeyDown} tabIndex={0} ref={wrapperRef}> <div className={cx(wrapper)} onKeyDown={handleKeyDown} tabIndex={0} ref={wrapperRef}>

View File

@ -1,16 +1,14 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from 'react';
import { EventOption } from "../../types/public-types"; import { EventOption } from '../../types/public-types';
import { BarTask } from "../../types/bar-task"; import { BarTask } from '../../types/bar-task';
import { Arrow } from "../other/arrow"; import { Arrow } from '../other/arrow';
import { handleTaskBySVGMouseEvent } from "../../helpers/bar-helper"; import { handleTaskBySVGMouseEvent } from '../../helpers/bar-helper';
import { isKeyboardEvent } from "../../helpers/other-helper"; import { isKeyboardEvent } from '../../helpers/other-helper';
import { TaskItem } from "../task-item/task-item"; import { TaskItem } from '../task-item/task-item';
import { import { BarMoveAction, GanttContentMoveAction, GanttEvent } from '../../types/gantt-task-actions';
BarMoveAction,
GanttContentMoveAction,
GanttEvent,
} from "../../types/gantt-task-actions";
let lastAction = null;
let lastStart = null;
export type TaskGanttContentProps = { export type TaskGanttContentProps = {
tasks: BarTask[]; tasks: BarTask[];
dates: Date[]; dates: Date[];
@ -78,9 +76,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
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 { isChanged, changedTask } = handleTaskBySVGMouseEvent( const { isChanged, changedTask } = handleTaskBySVGMouseEvent(
cursor.x, cursor.x,
@ -89,7 +85,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
xStep, xStep,
timeStep, timeStep,
initEventX1Delta, initEventX1Delta,
rtl rtl,
); );
if (isChanged) { if (isChanged) {
setGanttEvent({ action: ganttEvent.action, changedTask }); setGanttEvent({ action: ganttEvent.action, changedTask });
@ -98,14 +94,11 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
const handleMouseUp = async (event: MouseEvent) => { const handleMouseUp = async (event: MouseEvent) => {
const { action, originalSelectedTask, changedTask } = ganttEvent; const { action, originalSelectedTask, changedTask } = ganttEvent;
if (!changedTask || !point || !svg?.current || !originalSelectedTask) if (!changedTask || !point || !svg?.current || !originalSelectedTask) 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: newChangedTask } = handleTaskBySVGMouseEvent(
cursor.x, cursor.x,
action as BarMoveAction, action as BarMoveAction,
@ -113,7 +106,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
xStep, xStep,
timeStep, timeStep,
initEventX1Delta, initEventX1Delta,
rtl rtl,
); );
const isNotLikeOriginal = const isNotLikeOriginal =
@ -122,23 +115,16 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
originalSelectedTask.progress !== newChangedTask.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);
setGanttEvent({ action: "" }); setGanttEvent({ action: '' });
setIsMoving(false); setIsMoving(false);
// custom operation start // custom operation start
let operationSuccess: any = true; let operationSuccess: any = true;
if ( if ((action === 'move' || action === 'end' || action === 'start') && onDateChange && isNotLikeOriginal) {
(action === "move" || action === "end" || action === "start") &&
onDateChange &&
isNotLikeOriginal
) {
try { try {
const result = await onDateChange( const result = await onDateChange(newChangedTask, newChangedTask.barChildren);
newChangedTask,
newChangedTask.barChildren
);
if (result !== undefined) { if (result !== undefined) {
operationSuccess = result; operationSuccess = result;
} }
@ -147,10 +133,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
} }
} else if (onProgressChange && isNotLikeOriginal) { } else if (onProgressChange && isNotLikeOriginal) {
try { try {
const result = await onProgressChange( const result = await onProgressChange(newChangedTask, newChangedTask.barChildren);
newChangedTask,
newChangedTask.barChildren
);
if (result !== undefined) { if (result !== undefined) {
operationSuccess = result; operationSuccess = result;
} }
@ -167,14 +150,14 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
if ( if (
!isMoving && !isMoving &&
(ganttEvent.action === "move" || (ganttEvent.action === 'move' ||
ganttEvent.action === "end" || ganttEvent.action === 'end' ||
ganttEvent.action === "start" || ganttEvent.action === 'start' ||
ganttEvent.action === "progress") && ganttEvent.action === 'progress') &&
svg?.current svg?.current
) { ) {
svg.current.addEventListener("mousemove", handleMouseMove); svg.current.addEventListener('mousemove', handleMouseMove);
svg.current.addEventListener("mouseup", handleMouseUp); svg.current.addEventListener('mouseup', handleMouseUp);
setIsMoving(true); setIsMoving(true);
} }
}, [ }, [
@ -198,16 +181,16 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
const handleBarEventStart = async ( const handleBarEventStart = async (
action: GanttContentMoveAction, action: GanttContentMoveAction,
task: BarTask, task: BarTask,
event?: React.MouseEvent | React.KeyboardEvent event?: React.MouseEvent | React.KeyboardEvent,
) => { ) => {
if (!event) { if (!event) {
if (action === "select") { if (action === 'select') {
setSelectedTask(task.id); setSelectedTask(task.id);
} }
} }
// Keyboard events // Keyboard events
else if (isKeyboardEvent(event)) { else if (isKeyboardEvent(event)) {
if (action === "delete") { if (action === 'delete') {
if (onDelete) { if (onDelete) {
try { try {
const result = await onDelete(task); const result = await onDelete(task);
@ -215,13 +198,13 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
setGanttEvent({ action, changedTask: task }); setGanttEvent({ action, changedTask: task });
} }
} catch (error) { } catch (error) {
console.error("Error on Delete. " + error); console.error('Error on Delete. ' + error);
} }
} }
} }
} }
// Mouse Events // Mouse Events
else if (action === "mouseenter") { else if (action === 'mouseenter') {
if (!ganttEvent.action) { if (!ganttEvent.action) {
setGanttEvent({ setGanttEvent({
action, action,
@ -229,22 +212,20 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
originalSelectedTask: task, originalSelectedTask: task,
}); });
} }
} else if (action === "mouseleave") { } else if (action === 'mouseleave') {
if (ganttEvent.action === "mouseenter") { if (ganttEvent.action === 'mouseenter') {
setGanttEvent({ action: "" }); setGanttEvent({ action: '' });
} }
} else if (action === "dblclick") { } else if (action === 'dblclick') {
!!onDoubleClick && onDoubleClick(task); !!onDoubleClick && onDoubleClick(task);
} else if (action === "click") { } else if (action === 'click') {
!!onClick && onClick(task); !!onClick && onClick(task);
} }
// Change task event start // Change task event start
else if (action === "move") { else if (action === 'move') {
if (!svg?.current || !point) return; if (!svg?.current || !point) return;
point.x = event.clientX; point.x = event.clientX;
const cursor = point.matrixTransform( const cursor = point.matrixTransform(svg.current.getScreenCTM()?.inverse());
svg.current.getScreenCTM()?.inverse()
);
setInitEventX1Delta(cursor.x - task.x1); setInitEventX1Delta(cursor.x - task.x1);
setGanttEvent({ setGanttEvent({
action, action,
@ -260,11 +241,27 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
} }
}; };
const handleBarEvent = (action, task, event) => {
if (['click'].includes(action)) {
if (!['start', 'end', 'progress'].includes(lastAction) && (!lastStart || lastStart === task.start)) {
handleBarEventStart(action, task, event);
}
lastAction = null;
lastStart = null;
} else if (['move', 'select'].includes(action)) {
lastStart = task.start;
handleBarEventStart(action, task, event);
} else {
lastStart = task.start;
lastAction = action;
handleBarEventStart(action, task, event);
}
};
return ( return (
<g className="content"> <g className="content">
<g className="arrows" fill={arrowColor} stroke={arrowColor}> <g className="arrows" fill={arrowColor} stroke={arrowColor}>
{tasks.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 ${tasks[child.index].id}`} key={`Arrow from ${task.id} to ${tasks[child.index].id}`}
@ -280,7 +277,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
})} })}
</g> </g>
<g className="bar" fontFamily={fontFamily} fontSize={fontSize}> <g className="bar" fontFamily={fontFamily} fontSize={fontSize}>
{tasks.map(task => { {tasks.map((task) => {
return ( return (
<TaskItem <TaskItem
task={task} task={task}
@ -289,7 +286,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
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={handleBarEvent}
key={task.id} key={task.id}
isSelected={!!selectedTask && task.id === selectedTask.id} isSelected={!!selectedTask && task.id === selectedTask.id}
rtl={rtl} rtl={rtl}

View File

@ -1,13 +1,14 @@
import { ActionBar } from '../action'; import { ActionBar } from '../action';
import { Gantt } from './components/gantt/gantt'; import { Gantt } from './components/gantt/gantt';
import { GanttDesigner } from './Gantt.Designer'; import { GanttDesigner } from './Gantt.Designer';
import { ViewMode } from './types/public-types'; import { ViewMode } from './types/public-types';
import {Event} from './components/gantt/Event';
Gantt.ActionBar = ActionBar; Gantt.ActionBar = ActionBar;
Gantt.ViewMode = ViewMode; Gantt.ViewMode = ViewMode;
Gantt.Designer = GanttDesigner; Gantt.Designer = GanttDesigner;
Gantt.Event = Event;
// const GanttV2 = Gantt; // const GanttV2 = Gantt;

View File

@ -1100,6 +1100,45 @@ export const createGanttBlockSchema = (options) => {
}, },
}, },
}, },
detail: {
type: 'void',
'x-component': 'Gantt.Event',
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
'x-component-props': {
className: 'nb-action-popup',
},
title: '{{ t("View record") }}',
properties: {
tabs: {
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
'x-initializer': 'TabPaneInitializers',
properties: {
tab1: {
type: 'void',
title: '{{t("Details")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
properties: {
grid: {
type: 'void',
'x-component': 'Grid',
'x-initializer': 'RecordBlockInitializers',
properties: {},
},
},
},
},
},
},
},
},
},
}, },
}, },
}, },