mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-09 07:29:24 +08:00
refactor: gant bar suport click
This commit is contained in:
parent
54aa2d7f82
commit
ad886ac304
@ -0,0 +1,6 @@
|
||||
import { observer } from '@formily/react';
|
||||
import React from 'react';
|
||||
|
||||
export const Event = observer((props) => {
|
||||
return <>{props.children}</>;
|
||||
});
|
@ -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 { cx } from '@emotion/css';
|
||||
import { createForm } from '@formily/core';
|
||||
import { Task } from '../../types/public-types';
|
||||
import { GridProps } from '../grid/grid';
|
||||
import { ganttDateRange, seedDates } from '../../helpers/date-helper';
|
||||
@ -16,15 +17,39 @@ import { DateSetup } from '../../types/date-setup';
|
||||
import { HorizontalScroll } from '../other/horizontal-scroll';
|
||||
import { removeHiddenTasks, sortTasks } from '../../helpers/other-helper';
|
||||
import { wrapper } from './style';
|
||||
import { ActionContext } from '../../../action';
|
||||
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 columnWidth = clientWidth / dataSetLength > 50 ? Math.floor(clientWidth / dataSetLength) + 20 : 50;
|
||||
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) => {
|
||||
const { designable } = useDesignable();
|
||||
const {
|
||||
@ -63,9 +88,7 @@ export const Gantt: any = (props: any) => {
|
||||
useProps,
|
||||
} = props;
|
||||
const { onExpanderClick, tasks } = useProps();
|
||||
// const ctx = useGanttBlockContext();
|
||||
const { resource ,service} = useBlockRequestContext();
|
||||
// console.log(ctx,useBlockRequestContext())
|
||||
const { resource, service } = useBlockRequestContext();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { fieldNames } = useProps(props);
|
||||
const viewMode = fieldNames.range || 'day';
|
||||
@ -76,6 +99,8 @@ export const Gantt: any = (props: any) => {
|
||||
const [startDate, endDate] = ganttDateRange(tasks, viewMode, preStepsCount);
|
||||
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 [taskListWidth, setTaskListWidth] = useState(0);
|
||||
const [svgContainerWidth, setSvgContainerWidth] = useState(0);
|
||||
@ -359,7 +384,7 @@ export const Gantt: any = (props: any) => {
|
||||
[fieldNames.progress]: task.progress / 100,
|
||||
},
|
||||
});
|
||||
await service?.refresh()
|
||||
await service?.refresh();
|
||||
};
|
||||
const handleTaskChange = async (task: Task) => {
|
||||
await resource.update({
|
||||
@ -370,7 +395,15 @@ export const Gantt: any = (props: any) => {
|
||||
[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 = {
|
||||
columnWidth,
|
||||
@ -412,13 +445,13 @@ export const Gantt: any = (props: any) => {
|
||||
onDateChange: handleTaskChange,
|
||||
onProgressChange: fieldNames.progress && handleProgressChange,
|
||||
onDoubleClick,
|
||||
onClick,
|
||||
onClick: handleBarClick,
|
||||
onDelete,
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<GanttRecordViewer visible={visible} setVisible={setVisible} record={record} />
|
||||
<RecursionField name={'anctionBar'} schema={fieldSchema.properties.toolBar} />
|
||||
<RecursionField name={'table'} schema={fieldSchema.properties.table} />
|
||||
<div className={cx(wrapper)} onKeyDown={handleKeyDown} tabIndex={0} ref={wrapperRef}>
|
||||
|
@ -1,16 +1,14 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { EventOption } from "../../types/public-types";
|
||||
import { BarTask } from "../../types/bar-task";
|
||||
import { Arrow } from "../other/arrow";
|
||||
import { handleTaskBySVGMouseEvent } from "../../helpers/bar-helper";
|
||||
import { isKeyboardEvent } from "../../helpers/other-helper";
|
||||
import { TaskItem } from "../task-item/task-item";
|
||||
import {
|
||||
BarMoveAction,
|
||||
GanttContentMoveAction,
|
||||
GanttEvent,
|
||||
} from "../../types/gantt-task-actions";
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { EventOption } from '../../types/public-types';
|
||||
import { BarTask } from '../../types/bar-task';
|
||||
import { Arrow } from '../other/arrow';
|
||||
import { handleTaskBySVGMouseEvent } from '../../helpers/bar-helper';
|
||||
import { isKeyboardEvent } from '../../helpers/other-helper';
|
||||
import { TaskItem } from '../task-item/task-item';
|
||||
import { BarMoveAction, GanttContentMoveAction, GanttEvent } from '../../types/gantt-task-actions';
|
||||
|
||||
let lastAction = null;
|
||||
let lastStart = null;
|
||||
export type TaskGanttContentProps = {
|
||||
tasks: BarTask[];
|
||||
dates: Date[];
|
||||
@ -78,9 +76,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||
event.preventDefault();
|
||||
|
||||
point.x = event.clientX;
|
||||
const cursor = point.matrixTransform(
|
||||
svg?.current.getScreenCTM()?.inverse()
|
||||
);
|
||||
const cursor = point.matrixTransform(svg?.current.getScreenCTM()?.inverse());
|
||||
|
||||
const { isChanged, changedTask } = handleTaskBySVGMouseEvent(
|
||||
cursor.x,
|
||||
@ -89,7 +85,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||
xStep,
|
||||
timeStep,
|
||||
initEventX1Delta,
|
||||
rtl
|
||||
rtl,
|
||||
);
|
||||
if (isChanged) {
|
||||
setGanttEvent({ action: ganttEvent.action, changedTask });
|
||||
@ -98,14 +94,11 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||
|
||||
const handleMouseUp = async (event: MouseEvent) => {
|
||||
const { action, originalSelectedTask, changedTask } = ganttEvent;
|
||||
if (!changedTask || !point || !svg?.current || !originalSelectedTask)
|
||||
return;
|
||||
if (!changedTask || !point || !svg?.current || !originalSelectedTask) return;
|
||||
event.preventDefault();
|
||||
|
||||
point.x = event.clientX;
|
||||
const cursor = point.matrixTransform(
|
||||
svg?.current.getScreenCTM()?.inverse()
|
||||
);
|
||||
const cursor = point.matrixTransform(svg?.current.getScreenCTM()?.inverse());
|
||||
const { changedTask: newChangedTask } = handleTaskBySVGMouseEvent(
|
||||
cursor.x,
|
||||
action as BarMoveAction,
|
||||
@ -113,7 +106,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||
xStep,
|
||||
timeStep,
|
||||
initEventX1Delta,
|
||||
rtl
|
||||
rtl,
|
||||
);
|
||||
|
||||
const isNotLikeOriginal =
|
||||
@ -122,23 +115,16 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||
originalSelectedTask.progress !== newChangedTask.progress;
|
||||
|
||||
// remove listeners
|
||||
svg.current.removeEventListener("mousemove", handleMouseMove);
|
||||
svg.current.removeEventListener("mouseup", handleMouseUp);
|
||||
setGanttEvent({ action: "" });
|
||||
svg.current.removeEventListener('mousemove', handleMouseMove);
|
||||
svg.current.removeEventListener('mouseup', handleMouseUp);
|
||||
setGanttEvent({ action: '' });
|
||||
setIsMoving(false);
|
||||
|
||||
// custom operation start
|
||||
let operationSuccess:any = true;
|
||||
if (
|
||||
(action === "move" || action === "end" || action === "start") &&
|
||||
onDateChange &&
|
||||
isNotLikeOriginal
|
||||
) {
|
||||
let operationSuccess: any = true;
|
||||
if ((action === 'move' || action === 'end' || action === 'start') && onDateChange && isNotLikeOriginal) {
|
||||
try {
|
||||
const result = await onDateChange(
|
||||
newChangedTask,
|
||||
newChangedTask.barChildren
|
||||
);
|
||||
const result = await onDateChange(newChangedTask, newChangedTask.barChildren);
|
||||
if (result !== undefined) {
|
||||
operationSuccess = result;
|
||||
}
|
||||
@ -147,10 +133,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||
}
|
||||
} else if (onProgressChange && isNotLikeOriginal) {
|
||||
try {
|
||||
const result = await onProgressChange(
|
||||
newChangedTask,
|
||||
newChangedTask.barChildren
|
||||
);
|
||||
const result = await onProgressChange(newChangedTask, newChangedTask.barChildren);
|
||||
if (result !== undefined) {
|
||||
operationSuccess = result;
|
||||
}
|
||||
@ -167,14 +150,14 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||
|
||||
if (
|
||||
!isMoving &&
|
||||
(ganttEvent.action === "move" ||
|
||||
ganttEvent.action === "end" ||
|
||||
ganttEvent.action === "start" ||
|
||||
ganttEvent.action === "progress") &&
|
||||
(ganttEvent.action === 'move' ||
|
||||
ganttEvent.action === 'end' ||
|
||||
ganttEvent.action === 'start' ||
|
||||
ganttEvent.action === 'progress') &&
|
||||
svg?.current
|
||||
) {
|
||||
svg.current.addEventListener("mousemove", handleMouseMove);
|
||||
svg.current.addEventListener("mouseup", handleMouseUp);
|
||||
svg.current.addEventListener('mousemove', handleMouseMove);
|
||||
svg.current.addEventListener('mouseup', handleMouseUp);
|
||||
setIsMoving(true);
|
||||
}
|
||||
}, [
|
||||
@ -198,16 +181,16 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||
const handleBarEventStart = async (
|
||||
action: GanttContentMoveAction,
|
||||
task: BarTask,
|
||||
event?: React.MouseEvent | React.KeyboardEvent
|
||||
event?: React.MouseEvent | React.KeyboardEvent,
|
||||
) => {
|
||||
if (!event) {
|
||||
if (action === "select") {
|
||||
if (action === 'select') {
|
||||
setSelectedTask(task.id);
|
||||
}
|
||||
}
|
||||
// Keyboard events
|
||||
else if (isKeyboardEvent(event)) {
|
||||
if (action === "delete") {
|
||||
if (action === 'delete') {
|
||||
if (onDelete) {
|
||||
try {
|
||||
const result = await onDelete(task);
|
||||
@ -215,13 +198,13 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||
setGanttEvent({ action, changedTask: task });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error on Delete. " + error);
|
||||
console.error('Error on Delete. ' + error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Mouse Events
|
||||
else if (action === "mouseenter") {
|
||||
else if (action === 'mouseenter') {
|
||||
if (!ganttEvent.action) {
|
||||
setGanttEvent({
|
||||
action,
|
||||
@ -229,22 +212,20 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||
originalSelectedTask: task,
|
||||
});
|
||||
}
|
||||
} else if (action === "mouseleave") {
|
||||
if (ganttEvent.action === "mouseenter") {
|
||||
setGanttEvent({ action: "" });
|
||||
} else if (action === 'mouseleave') {
|
||||
if (ganttEvent.action === 'mouseenter') {
|
||||
setGanttEvent({ action: '' });
|
||||
}
|
||||
} else if (action === "dblclick") {
|
||||
} else if (action === 'dblclick') {
|
||||
!!onDoubleClick && onDoubleClick(task);
|
||||
} else if (action === "click") {
|
||||
} else if (action === 'click') {
|
||||
!!onClick && onClick(task);
|
||||
}
|
||||
// Change task event start
|
||||
else if (action === "move") {
|
||||
else if (action === 'move') {
|
||||
if (!svg?.current || !point) return;
|
||||
point.x = event.clientX;
|
||||
const cursor = point.matrixTransform(
|
||||
svg.current.getScreenCTM()?.inverse()
|
||||
);
|
||||
const cursor = point.matrixTransform(svg.current.getScreenCTM()?.inverse());
|
||||
setInitEventX1Delta(cursor.x - task.x1);
|
||||
setGanttEvent({
|
||||
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 (
|
||||
<g className="content">
|
||||
<g className="arrows" fill={arrowColor} stroke={arrowColor}>
|
||||
{tasks.map(task => {
|
||||
return task.barChildren.map(child => {
|
||||
{tasks.map((task) => {
|
||||
return task.barChildren.map((child) => {
|
||||
return (
|
||||
<Arrow
|
||||
key={`Arrow from ${task.id} to ${tasks[child.index].id}`}
|
||||
@ -280,7 +277,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||
})}
|
||||
</g>
|
||||
<g className="bar" fontFamily={fontFamily} fontSize={fontSize}>
|
||||
{tasks.map(task => {
|
||||
{tasks.map((task) => {
|
||||
return (
|
||||
<TaskItem
|
||||
task={task}
|
||||
@ -289,7 +286,7 @@ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||
isProgressChangeable={!!onProgressChange && !task.isDisabled}
|
||||
isDateChangeable={!!onDateChange && !task.isDisabled}
|
||||
isDelete={!task.isDisabled}
|
||||
onEventStart={handleBarEventStart}
|
||||
onEventStart={handleBarEvent}
|
||||
key={task.id}
|
||||
isSelected={!!selectedTask && task.id === selectedTask.id}
|
||||
rtl={rtl}
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { ActionBar } from '../action';
|
||||
import { Gantt } from './components/gantt/gantt';
|
||||
import { GanttDesigner } from './Gantt.Designer';
|
||||
|
||||
import { ViewMode } from './types/public-types';
|
||||
import {Event} from './components/gantt/Event';
|
||||
|
||||
Gantt.ActionBar = ActionBar;
|
||||
|
||||
Gantt.ViewMode = ViewMode;
|
||||
Gantt.Designer = GanttDesigner;
|
||||
Gantt.Event = Event;
|
||||
|
||||
// const GanttV2 = Gantt;
|
||||
|
||||
|
@ -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: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user