assistant-todo/src/ui/task/calendar/CalShow.tsx

386 lines
16 KiB
TypeScript
Raw Normal View History

2024-05-28 06:54:58 -04:00
'use client'
import React, {Fragment, useCallback, useContext, useEffect, useMemo, useRef, useState} from "react";
import {Calendar, dateFnsLocalizer, dayjsLocalizer, Event, SlotInfo, View} from 'react-big-calendar'
import dayjs, {Dayjs} from 'dayjs'
2024-05-28 06:54:58 -04:00
import 'react-big-calendar/lib/css/react-big-calendar.css'
2024-05-30 02:32:17 -04:00
import 'react-big-calendar/lib/sass/styles.scss'
import 'react-big-calendar/lib/addons/dragAndDrop/styles.scss'
import '@/ui/task/calendar/index.modules.css'
import {commonUpdate, OPERATION_BUTTON_TYPE} from "@/lib/task/project/data";
2024-05-28 06:54:58 -04:00
import {useSearchParams} from "next/dist/client/components/navigation";
import {DetailModelForm} from "@/ui/task/project/DetailModelForm";
2025-08-11 06:30:45 -04:00
import {Request, SearchObject} from "@/lib/definitions";
import LocalContext from "@/ui/LocalContent";
2024-05-30 02:32:17 -04:00
import withDragAndDrop, {EventInteractionArgs} from "react-big-calendar/lib/addons/dragAndDrop";
import {TaskEvent} from "@/lib/task/calendar/data";
2025-08-11 06:30:45 -04:00
import {editExpectAPI, getTaskAndScheduleRecordAPI} from "@/lib/task/calendar/service";
import TaskNameAndIcon from "@/components/TaskNameAndIcon";
2025-08-11 06:30:45 -04:00
import {TaskWebSelectVO} from "@/lib/task/project/definitions";
import {message} from "antd";
import ClickRecord from "@/components/ClickRecord";
import {editClickRecordRangeAPI} from "@/components/service/ScheduleTask";
2024-05-28 06:54:58 -04:00
/**
* https://github.com/jquense/react-big-calendar?tab=readme-ov-file
* @constructor
*/
const localizer = dayjsLocalizer(dayjs)
2024-05-30 02:32:17 -04:00
const DragAndDropCalendar = withDragAndDrop(Calendar)
2024-05-28 06:54:58 -04:00
const CalShow: React.FC = () => {
dayjs.locale('zh-cn')
2024-05-31 02:00:16 -04:00
const [view, setView] = useState<View>('week');
2024-05-30 02:32:17 -04:00
const [date, setDate] = useState<Date>(new Date());
2025-07-18 07:02:14 -04:00
const clickRef = useRef<number | undefined | null>(null)
2024-05-28 06:54:58 -04:00
// 展示在页面的任务,默认获取当前月的信息。
2024-05-30 02:32:17 -04:00
const [events, setEvents] = useState<TaskEvent[]>([]);
const [open, setOpen] = useState(false);
2025-08-11 06:30:45 -04:00
const [openClickRecord, setOpenClickRecord] = useState(false);
2024-05-30 02:32:17 -04:00
const [description, setDescription] = useState('');
const [operationId, setOperationId] = useState(-1);
2024-12-12 05:32:38 -05:00
const [itemId, setItemId] = useState('-1');
2025-08-11 06:30:45 -04:00
const [recordId,setRecordId] = useState();
const [taskName, setTaskName] = useState("");
2024-05-30 02:32:17 -04:00
const [expectedStartTime, setExpectedStartTime] = useState<Dayjs>();
const [expectedEndTime, setExpectedEndTime] = useState<Dayjs>();
2025-07-18 07:02:14 -04:00
const [range, setRange] = useState<{ start: Date; end: Date }>({
2024-05-31 02:00:16 -04:00
start: dayjs(date).startOf('week').toDate(),
end: dayjs(date).endOf('week').toDate()
});
2025-08-11 06:30:45 -04:00
const {taskState:state,taskTypeList} = useContext(LocalContext);
const [searchObject,setSearchObject] =
useState<Request<TaskWebSelectVO>>({
pageSize: 9999,
pageNumber: 1,
data:{
treeList:true,
treeFilter:true,
treeOrList:false,
expectedStartTime:range.start,
expectedEndTime:range.end
}
})
2024-05-30 02:32:17 -04:00
const handleViewChange = (newView: View) => {
2024-05-28 06:54:58 -04:00
setView(newView);
};
var pid = useSearchParams().get('pid');
2025-07-18 07:02:14 -04:00
function clearClickTimeout() {
clickRef && typeof clickRef.current === 'number' && !isNaN(clickRef.current) && isFinite(clickRef.current) && window.clearTimeout(clickRef.current)
2024-05-30 02:32:17 -04:00
}
2025-07-18 07:02:14 -04:00
const handleNavigate = (newDate: Date) => {
console.log('handleNavigate', newDate)
setDate(newDate);
2024-05-30 02:32:17 -04:00
const searchList: SearchObject[] = []
if (pid != null) {
2024-10-14 03:46:11 -04:00
searchList.push({name: "pid", value: pid, operateType: "="},
{name: 'ALL-CHILD', value: "true", operateType: "ALL-CHILD"},
{name: 'TREE-FILTER', value: "true", operateType: "TREE-FILTER"},
);
2025-08-11 06:30:45 -04:00
searchObject.data.pid=pid
setSearchObject({...searchObject})
}
loadData(searchList);
};
2024-05-28 06:54:58 -04:00
useEffect(() => {
2025-07-18 07:02:14 -04:00
console.log("CalShow:useEffect:range", range)
2025-08-11 06:30:45 -04:00
// const searchListE = []
if (pid != null) {
2025-08-11 06:30:45 -04:00
// searchListE.push(
// {name: "pid", value: pid, operateType: "="},
// {name: 'ALL-CHILD', value: "true", operateType: "ALL-CHILD"},
// {name: 'TREE-FILTER', value: "true", operateType: "TREE-FILTER"},
// );
searchObject.data.pid=pid
setSearchObject({...searchObject})
2024-05-28 06:54:58 -04:00
}
2024-10-14 03:46:11 -04:00
// searchListE.push({name: 'expectedStartTime', value: range.start, operateType: ">="})
// searchListE.push({name: 'expectedStartTime', value: range.end, operateType: "<="})
2025-08-11 06:30:45 -04:00
// loadData(searchListE);
loadData()
2024-05-30 02:32:17 -04:00
/**
* What Is This?
* This is to prevent a memory leak, in the off chance that you
* teardown your interface prior to the timed method being called.
*/
return () => {
clearClickTimeout()
}
2025-07-18 07:02:14 -04:00
}, [useContext(LocalContext), range]);
2025-08-11 06:30:45 -04:00
const calMessages = {
week: '周',
work_week: '工作周',
day: '天',
month: '月',
previous: '前',
next: '后',
today: '当下',
agenda: '日程'
}
2025-08-11 06:30:45 -04:00
const loadData = (searchList?: SearchObject[]) => {
2024-05-30 02:32:17 -04:00
if (state.length > 0) {
2025-08-11 06:30:45 -04:00
// searchList.push({name: 'state', value: state, operateType: "IN"})
searchObject.data.state=state
2024-05-28 06:54:58 -04:00
}
2025-08-11 06:30:45 -04:00
if (taskTypeList.length>0){
searchObject.data.taskTypeList=taskTypeList
}
searchObject.data.expectedStartTime=range.start
searchObject.data.expectedEndTime=range.end
setSearchObject({...searchObject})
getTaskAndScheduleRecordAPI(searchObject).then(responseD => {
if (responseD.status.success) {
2025-07-18 07:02:14 -04:00
let result: TaskEvent[] = responseD.data.content.map<TaskEvent>(taskState => {
2024-05-28 06:54:58 -04:00
return {
start: dayjs(taskState.expectedStartTime).toDate(),
end: dayjs(taskState.expectedEndTime).toDate(),
title: <Fragment><TaskNameAndIcon task={taskState}/><div>{taskState.description}</div></Fragment>,
// style: {
// backgroundColor: 'green',
// color: 'white',
// },
2025-08-11 06:30:45 -04:00
name:taskState.name,
2024-05-30 02:32:17 -04:00
resource: taskState.id,
2025-07-18 07:02:14 -04:00
id: taskState.id,
state: taskState.state,
2025-08-11 06:30:45 -04:00
priority: taskState.priority,
taskType:taskState.taskType
2024-05-28 06:54:58 -04:00
}
2024-05-30 02:32:17 -04:00
});
2025-07-18 07:02:14 -04:00
console.log('responseD.data.content:', result)
setEvents([...result])
2024-05-28 06:54:58 -04:00
}
})
2025-08-11 06:30:45 -04:00
// searchList.push({name: 'expectedEndTime', value: dayjs(date).endOf('month'), operateType: "NOT NULL"})
// let request = JSON.stringify({
// pageSize: 9999,
// pageNumber: 1,
// data: searchList,
// startTime: range.start,
// // startTime:dayjs(range.start).format('YYYY-MM-DD HH:mm:ss'),
// endTime: range.end,
// // endTime:dayjs(range.end).format('YYYY-MM-DD HH:mm:ss'),
// startColumn: "expected_start_time",
// endColumn: "expected_end_time"
// })
// getTaskTreeResult(request).then(responseD => {
// if (responseD.status.success) {
// let result: TaskEvent[] = responseD.data.content.map<TaskEvent>(taskState => {
// return {
// start: dayjs(taskState.expectedStartTime).toDate(),
// end: dayjs(taskState.expectedEndTime).toDate(),
// title: <TaskNameAndIcon task={taskState}/>,
// resource: taskState.id,
// id: taskState.id,
// state: taskState.state,
// priority: taskState.priority
// }
// });
// console.log('responseD.data.content:', result)
// setEvents([...result])
// }
// })
}
2024-05-30 02:32:17 -04:00
const reloadData = () => {
setOpen(false)
2024-05-30 02:32:17 -04:00
handleNavigate(expectedStartTime ? expectedStartTime.toDate() : date)
}
const handleSelectSlot = useCallback(
({start, end}: SlotInfo) => {
setExpectedEndTime(dayjs(end))
setExpectedStartTime(dayjs(start))
setOperationId(OPERATION_BUTTON_TYPE.ADD)
setDescription("添加任务")
setOpen(true);
},
[setEvents]
)
2024-05-28 06:54:58 -04:00
const handleSelectEvent = useCallback(
2025-08-11 06:30:45 -04:00
(event: TaskEvent, e: React.SyntheticEvent<HTMLElement>) => {
2024-05-30 02:32:17 -04:00
clearClickTimeout()
2025-07-18 07:02:14 -04:00
clickRef.current = window.setTimeout(() => {
2025-08-11 06:30:45 -04:00
if(event.taskType=='4'){
setTaskName(event.name||"")
setRecordId(event.id)
setOpenClickRecord(true);
}else {
setOperationId(OPERATION_BUTTON_TYPE.DETAIL)
setDescription("任务详情")
setItemId(event.resource)
setOpen(true);
}
2025-07-18 07:02:14 -04:00
}, 250)
},
[]
)
const {defaultDate, scrollToTime} = useMemo(
() => ({
defaultDate: new Date(2015, 3, 12),
scrollToTime: new Date(1970, 1, 1, 6),
}),
[]
)
2024-05-30 02:32:17 -04:00
const doubleClick = (event: TaskEvent, e: React.SyntheticEvent<HTMLElement>) => {
clearClickTimeout()
2025-07-18 07:02:14 -04:00
clickRef.current = window.setTimeout(() => {
2025-08-11 06:30:45 -04:00
if (event.taskType=='4'||event.taskType=='5'){
message.error("计划双击完成,非计划双击无效果。")
return;
}
2024-05-30 02:32:17 -04:00
// 数据落库
commonUpdate({
updateColumnList: [{
name: '任务状态',
code: 'state',
value: 7
}],
conditionColumnList: [{
name: 'id',
code: 'id',
operateType: '=',
value: event.resource
}]
})
setEvents((prev: TaskEvent[]) => {
const existing: TaskEvent | undefined = prev.find((ev: TaskEvent) => ev.resource === event.resource);
const filtered: TaskEvent[] | undefined = prev.filter((ev: TaskEvent) => ev.resource !== event.resource);
let result: TaskEvent[] = [];
2025-07-18 07:02:14 -04:00
if (existing !== undefined && filtered !== undefined) {
result = [...filtered, {...existing, state: 7}];
2024-05-30 02:32:17 -04:00
}
2024-10-14 03:46:11 -04:00
let strings = state.split(",");
2025-07-18 07:02:14 -04:00
console.log('result', result, strings)
return result.filter((ev: TaskEvent) => !ev.state || strings.indexOf(ev.state.toString()) >= 0);
2024-05-30 02:32:17 -04:00
})
2025-07-18 07:02:14 -04:00
}, 250)
2024-05-30 02:32:17 -04:00
}
const moveEvent = useCallback(
2025-07-18 07:02:14 -04:00
async ({event, start, end, isAllDay: droppedOnAllDaySlot = false}: EventInteractionArgs<TaskEvent>) => {
console.log("onEventResize || onEventDrop :", start, end, event)
2024-05-30 02:32:17 -04:00
const {allDay} = event
if (!allDay && droppedOnAllDaySlot) {
event.allDay = true
}
2025-08-11 06:30:45 -04:00
if (event.taskType=='4'){
editClickRecordRangeAPI({
2025-08-11 06:30:45 -04:00
id:event.id,
startDate:dayjs(start!).toDate(),
recordDate:dayjs(end!).toDate(),
});
}else {
editExpectAPI({
expectedStartTime: start,
expectedEndTime: end,
id: event.resource
})
}
2024-05-30 02:32:17 -04:00
setEvents((prev: TaskEvent[]) => {
const existing: TaskEvent | undefined = prev.find((ev: TaskEvent) => ev.resource === event.resource);
const filtered: TaskEvent[] | undefined = prev.filter((ev: TaskEvent) => ev.resource !== event.resource);
if (start instanceof Date && end instanceof Date && existing !== undefined) {
return [...filtered, {...existing, start, end, allDay}];
}
if (filtered !== undefined) {
return [...filtered];
}
return [];
})
},
[setEvents]
)
const eventPropGetter = useCallback(
2025-07-18 07:02:14 -04:00
(event: TaskEvent) => ({
...(event.state === '7'
2025-07-18 07:02:14 -04:00
? {className: 'completeTask'}
: event.priority === '3' ? {className: 'importantUrgentTask'} :
event.priority === '2' ? {className: 'importantNotUrgentTask'} :
event.priority === '1' ? {className: 'notImportantUrgentTask'} :
2025-07-18 07:02:14 -04:00
{className: 'notImportantNotUrgentTask'}),
2024-05-30 02:32:17 -04:00
}),
[setEvents]
)
2025-07-18 07:02:14 -04:00
const rangeChange = (rangeLet: Date[] | { start: Date; end: Date }, current?: View | undefined) => {
console.log("rangeChange:", rangeLet, (current ? current : view))
2024-05-31 02:00:16 -04:00
// view 为天的时候类型为数组index:0为当天
2025-07-18 07:02:14 -04:00
if ((current ? current : view) === "day" && Array.isArray(rangeLet)) {
if (range.start.valueOf() > rangeLet[0].valueOf()) {
setRange({...range, start: rangeLet[0]})
} else if (range.end.valueOf() < rangeLet[0].valueOf()) {
setRange({...range, end: rangeLet[0]})
2024-05-31 02:00:16 -04:00
}
}
// 为周的时候类型为数组,周一到周日七天
2025-07-18 07:02:14 -04:00
if ((current ? current : view) === "week" && Array.isArray(rangeLet)) {
if (range.start.valueOf() > rangeLet[0].valueOf()) {
setRange({...range, start: rangeLet[0]})
2024-05-31 02:00:16 -04:00
}
2025-07-18 07:02:14 -04:00
if (range.end.valueOf() < rangeLet[6].valueOf()) {
setRange({...range, end: rangeLet[6]})
2024-05-31 02:00:16 -04:00
}
}
// 为周的时候类型为对象
2025-07-18 07:02:14 -04:00
if ((current ? current : view) === "month" && rangeLet && !Array.isArray(rangeLet)) {
if (range.start.valueOf() > rangeLet.start.valueOf()) {
setRange({...range, start: rangeLet.start})
2024-05-31 02:00:16 -04:00
}
2025-07-18 07:02:14 -04:00
if (range.end.valueOf() < rangeLet.end.valueOf()) {
setRange({...range, end: rangeLet.end})
2024-05-31 02:00:16 -04:00
}
}
}
return <div className="App" style={{height: '90vh'}}>
2025-07-29 06:47:26 -04:00
{open && <DetailModelForm operationId={operationId}
description={description}
open={open}
haveButton={false}
2025-07-18 07:02:14 -04:00
itemId={itemId}
reloadData={reloadData} expectedStartTime={expectedStartTime}
closeOpen={() => setOpen(false)}
2025-07-18 07:02:14 -04:00
expectedEndTime={expectedEndTime}/>}
2025-08-11 06:30:45 -04:00
{openClickRecord &&
<ClickRecord recordId={recordId} taskName={taskName}
openClickRecord={openClickRecord} setOpenClickRecord={setOpenClickRecord}
reloadData={loadData}/>}
2024-05-30 02:32:17 -04:00
<DragAndDropCalendar
// 本地设置
2024-05-28 06:54:58 -04:00
localizer={localizer}
2025-08-11 06:30:45 -04:00
messages={calMessages}
2024-05-30 02:32:17 -04:00
// 修改style
eventPropGetter={eventPropGetter}
2024-05-28 06:54:58 -04:00
events={events}
2024-05-30 02:32:17 -04:00
// 界面
2024-05-28 06:54:58 -04:00
view={view}
2024-05-30 02:32:17 -04:00
// 界面改变
2024-05-28 06:54:58 -04:00
onView={handleViewChange}
2024-05-31 02:00:16 -04:00
onRangeChange={rangeChange}
2024-05-30 02:32:17 -04:00
// 时间
2024-05-28 06:54:58 -04:00
date={date}
2024-05-30 02:32:17 -04:00
// 条目信息改变
2024-05-28 06:54:58 -04:00
onNavigate={handleNavigate}
2024-05-30 02:32:17 -04:00
// 点击
selectable
scrollToTime={scrollToTime}
// 双击
onDoubleClickEvent={doubleClick}
// 点击任务
onSelectEvent={handleSelectEvent}
2024-05-30 02:32:17 -04:00
// 点击空白处
onSelectSlot={handleSelectSlot}
2024-05-30 02:32:17 -04:00
// 改变时间长短
resizable
onEventResize={moveEvent}
onEventDrop={moveEvent}
2024-05-28 06:54:58 -04:00
/>
</div>
}
export default CalShow;