react-weekly-planning 1.0.38 → 1.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +219 -98
- package/dist/components/AddTask/index.js +1 -2
- package/dist/components/CalendarForWeek.js +25 -44
- package/dist/components/CalendarForday.js +14 -25
- package/dist/components/GroupsHeadContainer/index.js +1 -2
- package/dist/components/SumHoursContainer/index.js +1 -2
- package/dist/components/SumHoursHead/index.js +1 -2
- package/dist/components/TaskContainer/TaskVirtual.js +18 -0
- package/dist/components/TaskContainer/index.js +5 -4
- package/dist/components/VirtualGroupRow.js +64 -0
- package/dist/components/VirtualGroupRowDay.js +49 -0
- package/dist/components/index.js +3 -2
- package/dist/contexts/CalendarContext.js +3 -7
- package/dist/contexts/CalendarTaskContext.js +32 -0
- package/dist/hooks/useCalendarDateState.js +3 -3
- package/dist/hooks/useCalendarTask.js +191 -0
- package/dist/hooks/useContainerScroll.js +14 -0
- package/dist/hooks/useData.js +7 -0
- package/dist/hooks/useGridContainer.js +24 -0
- package/dist/hooks/useIntersectionObserver.js +19 -0
- package/dist/hooks/useMainContainerItemContent.js +16 -0
- package/dist/hooks/useWindowsSize.js +19 -0
- package/dist/index.js +13 -1
- package/dist/lib/utils.js +43 -31
- package/dist/types/components/AddTask/index.d.ts +2 -2
- package/dist/types/components/CalendarForWeek.d.ts +1 -2
- package/dist/types/components/GroupsHeadContainer/index.d.ts +2 -2
- package/dist/types/components/SumHoursContainer/index.d.ts +1 -1
- package/dist/types/components/SumHoursHead/index.d.ts +2 -2
- package/dist/types/components/TaskContainer/TaskVirtual.d.ts +4 -0
- package/dist/types/components/VirtualGroupRow.d.ts +18 -0
- package/dist/types/components/VirtualGroupRowDay.d.ts +19 -0
- package/dist/types/contexts/CalendarContext.d.ts +2 -10
- package/dist/types/contexts/CalendarTaskContext.d.ts +23 -0
- package/dist/types/definitions/index.d.ts +30 -8
- package/dist/types/hooks/useCalendarTask.d.ts +16 -0
- package/dist/types/hooks/useContainerScroll.d.ts +3 -0
- package/dist/types/hooks/useData.d.ts +4 -0
- package/dist/types/hooks/useGridContainer.d.ts +5 -0
- package/dist/types/hooks/useIntersectionObserver.d.ts +5 -0
- package/dist/types/hooks/useMainContainerItemContent.d.ts +4 -0
- package/dist/types/hooks/useWindowsSize.d.ts +4 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/lib/utils.d.ts +8 -4
- package/package.json +49 -1
- package/dist/components/style.css +0 -123
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { memo, useRef, useMemo } from "react";
|
|
3
|
+
import { v4 as uuidv4 } from "uuid";
|
|
4
|
+
import { useIntersectionObserver } from "../hooks/useIntersectionObserver";
|
|
5
|
+
import GroupContainer from "./GroupContainer";
|
|
6
|
+
import TaskVirtual from "./TaskContainer/TaskVirtual";
|
|
7
|
+
import AddTask from "./AddTask";
|
|
8
|
+
import { getHash, getSessionStorageRecordForDragAndDrop, getUnqueId, updateOffsetWithDateCalendar, } from "../lib/utils";
|
|
9
|
+
import { groupTdStyle } from "../lib/slyles";
|
|
10
|
+
const VirtualGroupRow = ({ group, i, props, getTasks, isValidTask, addTask, deleteTask, getTask, dailyHours, hashScope, tasks, sumHoursByGroupsCount }) => {
|
|
11
|
+
const ref = useRef(null);
|
|
12
|
+
const entry = useIntersectionObserver(ref, {
|
|
13
|
+
rootMargin: "600px",
|
|
14
|
+
threshold: 0,
|
|
15
|
+
});
|
|
16
|
+
const isVisible = !!(entry === null || entry === void 0 ? void 0 : entry.isIntersecting);
|
|
17
|
+
const offset = useMemo(() => updateOffsetWithDateCalendar(props.date), [props.date]);
|
|
18
|
+
const cellData = useMemo(() => {
|
|
19
|
+
if (!isVisible)
|
|
20
|
+
return [];
|
|
21
|
+
return dailyHours.map((_, positionDay) => {
|
|
22
|
+
const hash = getHash(offset, group.id, positionDay);
|
|
23
|
+
const cellTasks = getTasks(hash[hashScope]);
|
|
24
|
+
return {
|
|
25
|
+
hash: hash[hashScope],
|
|
26
|
+
tasks: cellTasks,
|
|
27
|
+
positionDay
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
}, [isVisible, offset, group.id, dailyHours, hashScope, getTasks, isValidTask, tasks]);
|
|
31
|
+
return (_jsx("div", { ref: ref, className: `planningCalendarRow ${props.rowsClassName}`, style: Object.assign(Object.assign({}, props.rowsStyle), { minHeight: isVisible ? "auto" : "60px" }), children: isVisible ? (_jsxs(_Fragment, { children: [_jsx("div", { className: `groupCol ${props.groupsColsClassName}`, style: Object.assign(Object.assign({}, groupTdStyle), props.groupsColsStyle), children: _jsx(GroupContainer, { style: props.groupContainerStyle, className: props.groupContainerClassName, groupRender: props.groupRender, currentGroup: group, handleClickGroup: props.handleClickGroup }) }, group.id), cellData.map((cell) => {
|
|
32
|
+
return (_jsx("div", { onDragOver: (e) => e.preventDefault(), onDrop: (event) => {
|
|
33
|
+
const dropInfo = getSessionStorageRecordForDragAndDrop(cell.tasks, cell.positionDay, group.id, getTask, cell.hash);
|
|
34
|
+
if (!dropInfo)
|
|
35
|
+
return;
|
|
36
|
+
if (props.drop === "copy") {
|
|
37
|
+
addTask(Object.assign(Object.assign({}, dropInfo.newTask), { id: uuidv4() }));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
deleteTask(dropInfo.newTask.draghash, dropInfo.newTask.id);
|
|
41
|
+
addTask(Object.assign(Object.assign({}, dropInfo.newTask), { id: getUnqueId() }));
|
|
42
|
+
}, id: `col-${group.id}day-i`, className: `dayCol ${props.dayColsClassName}`, style: props.dayColsStyle, children: _jsxs("div", { style: {
|
|
43
|
+
display: "flex",
|
|
44
|
+
width: "100%",
|
|
45
|
+
height: "100%",
|
|
46
|
+
flexDirection: "column",
|
|
47
|
+
padding: "5px",
|
|
48
|
+
}, children: [cell.tasks.map((task) => {
|
|
49
|
+
if (task.dayIndex === cell.positionDay &&
|
|
50
|
+
task.groupId === group.id && isValidTask(task)) {
|
|
51
|
+
return (_jsx(TaskVirtual, { handleDragTask: props.handleDragTask, taskRender: props.taskRender, handleDragTaskEnd: props.handleDragTaskEnd, style: props.taskContainerStyle, className: `${props.taskContainerClassName}`, currentTask: task, handleClickTask: props.handleClickTask }, `${task.id} task`));
|
|
52
|
+
}
|
|
53
|
+
else
|
|
54
|
+
return null;
|
|
55
|
+
}), _jsx(AddTask, { addTaskStyle: props.addTaskStyle, addTaskClassName: props.addTaskClassName, currentGroup: group, dayInfo: dailyHours[cell.positionDay], addTaskRender: props.addTaskRender, handleAddTask: props.handleAddTask })] }) }, `col-${group.id}day-i${cell.positionDay}`));
|
|
56
|
+
})] })) : (_jsx("div", { style: { height: "60px", width: "100%" } })) }));
|
|
57
|
+
};
|
|
58
|
+
export default memo(VirtualGroupRow, (prev, next) => {
|
|
59
|
+
return (prev.group.id === next.group.id &&
|
|
60
|
+
prev.i === next.i &&
|
|
61
|
+
prev.tasks === next.tasks &&
|
|
62
|
+
prev.props.date.getTime() === next.props.date.getTime() &&
|
|
63
|
+
prev.sumHoursByGroupsCount === next.sumHoursByGroupsCount);
|
|
64
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { memo, useRef, useMemo } from "react";
|
|
3
|
+
import { useIntersectionObserver } from "../hooks/useIntersectionObserver";
|
|
4
|
+
import GroupContainer from "./GroupContainer";
|
|
5
|
+
import TaskVirtual from "./TaskContainer/TaskVirtual";
|
|
6
|
+
import AddTask from "./AddTask";
|
|
7
|
+
import { getHash, getSessionStorageRecordForDragAndDrop, getUnqueId, updateOffsetWithDateCalendar, } from "../lib/utils";
|
|
8
|
+
const VirtualGroupRowDay = ({ group, i, props, getTasks, isValidTask, addTask, deleteTask, updateTask, getTask, dailyHours, dayOffset, hashScope, tasks, }) => {
|
|
9
|
+
const ref = useRef(null);
|
|
10
|
+
const entry = useIntersectionObserver(ref, {
|
|
11
|
+
rootMargin: "600px",
|
|
12
|
+
threshold: 0,
|
|
13
|
+
});
|
|
14
|
+
const isVisible = !!(entry === null || entry === void 0 ? void 0 : entry.isIntersecting);
|
|
15
|
+
const offset = useMemo(() => updateOffsetWithDateCalendar(props.date), [props.date]);
|
|
16
|
+
const currentDailyHours = dailyHours[dayOffset];
|
|
17
|
+
const hash = useMemo(() => getHash(offset, group.id, dayOffset), [offset, group.id, dayOffset]);
|
|
18
|
+
const cellTasks = useMemo(() => getTasks(hash[hashScope]), [getTasks, hash, hashScope, tasks]);
|
|
19
|
+
const handleDragOver = (event) => {
|
|
20
|
+
event.preventDefault();
|
|
21
|
+
};
|
|
22
|
+
return (_jsx("div", { ref: ref, style: Object.assign({ width: "100%", height: "auto", padding: "5px", borderBottom: "1.5px solid #0f52737e", borderRight: "0.74px solid rgba(198, 219, 225, 0.68)", borderLeft: "0.74px solid rgba(198, 219, 225, 0.68)", minHeight: isVisible ? "auto" : "60px" }, props.rowsStyle), className: `CalendarTableForDayRow ${props.rowsClassName}`, children: isVisible ? (_jsxs(_Fragment, { children: [_jsx("div", { style: Object.assign({ width: "auto", height: "auto" }, props.groupsColsStyle), className: props.groupsColsClassName, children: _jsx(GroupContainer, { style: props.groupContainerStyle, className: props.groupContainerClassName, groupRender: props.groupRender, currentGroup: group, handleClickGroup: props.handleClickGroup }) }), _jsxs("div", { className: "CalendarTableForDayGroupTasks", onDragOver: handleDragOver, onDrop: (event) => {
|
|
23
|
+
if (!cellTasks)
|
|
24
|
+
return;
|
|
25
|
+
const dropInfo = getSessionStorageRecordForDragAndDrop(cellTasks, currentDailyHours.positionDay, group.id, getTask, hash[hashScope]);
|
|
26
|
+
if (!dropInfo)
|
|
27
|
+
return;
|
|
28
|
+
if (props.drop === "copy") {
|
|
29
|
+
addTask(Object.assign(Object.assign({}, dropInfo.newTask), { id: getUnqueId() }));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
updateTask(hash[hashScope], dropInfo.newTask.id, dropInfo.newTask);
|
|
33
|
+
}, children: [cellTasks.map((task) => {
|
|
34
|
+
if (task.dayIndex === dayOffset &&
|
|
35
|
+
task.groupId === group.id &&
|
|
36
|
+
isValidTask(task)) {
|
|
37
|
+
return (_jsx(TaskVirtual, { handleDragTask: props.handleDragTask, taskRender: props.taskRender, handleDragTaskEnd: props.handleDragTaskEnd, style: props.taskContainerStyle, className: `${props.taskContainerClassName}`, currentTask: task, handleClickTask: props.handleClickTask }, `${task.id} task`));
|
|
38
|
+
}
|
|
39
|
+
else
|
|
40
|
+
return null;
|
|
41
|
+
}), _jsx(AddTask, { addTaskStyle: props.addTaskStyle, addTaskClassName: props.addTaskClassName, currentGroup: group, dayInfo: currentDailyHours, addTaskRender: props.addTaskRender, handleAddTask: props.handleAddTask })] })] })) : (_jsx("div", { style: { height: "60px", width: "100%" } })) }));
|
|
42
|
+
};
|
|
43
|
+
export default memo(VirtualGroupRowDay, (prev, next) => {
|
|
44
|
+
return (prev.group.id === next.group.id &&
|
|
45
|
+
prev.i === next.i &&
|
|
46
|
+
prev.tasks === next.tasks &&
|
|
47
|
+
prev.props.date.getTime() === next.props.date.getTime() &&
|
|
48
|
+
prev.dayOffset === next.dayOffset);
|
|
49
|
+
});
|
package/dist/components/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import "./style.css";
|
|
3
3
|
import CalendarForWeek from "./CalendarForWeek";
|
|
4
4
|
import CalendarForDay from "./CalendarForday";
|
|
5
|
+
import CalendarTaskContextProvider from "../contexts/CalendarTaskContext";
|
|
5
6
|
/**
|
|
6
7
|
* Calendar component to display tasks and groups in a weekly view.
|
|
7
8
|
*
|
|
@@ -64,6 +65,6 @@ import CalendarForDay from "./CalendarForday";
|
|
|
64
65
|
* @param {string} [props.hoursColsClassName] - Additional class names for the hours columns.
|
|
65
66
|
*/
|
|
66
67
|
const Calendar = (props) => {
|
|
67
|
-
return (_jsx(
|
|
68
|
+
return (_jsx(CalendarTaskContextProvider, { hashScope: "week", children: props.scope === "day" ? _jsx(CalendarForDay, Object.assign({}, props)) : _jsx(CalendarForWeek, Object.assign({}, props)) }));
|
|
68
69
|
};
|
|
69
70
|
export default Calendar;
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createContext, useContext } from "react";
|
|
3
|
-
const CalendarContext = createContext({
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
date: new Date()
|
|
7
|
-
});
|
|
8
|
-
const CalendarContextProvider = ({ groups, weekOffset, children, date }) => {
|
|
9
|
-
return (_jsx(CalendarContext.Provider, { value: { groups, weekOffset, date }, children: children }));
|
|
3
|
+
const CalendarContext = createContext({});
|
|
4
|
+
const CalendarContextProvider = ({ children, }) => {
|
|
5
|
+
return (_jsx(CalendarContext.Provider, { value: {}, children: children }));
|
|
10
6
|
};
|
|
11
7
|
export const useCalendarContext = () => useContext(CalendarContext);
|
|
12
8
|
export default CalendarContextProvider;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
import { useCalendarTask } from "../hooks/useCalendarTask";
|
|
4
|
+
const CalendarTaskContext = createContext({
|
|
5
|
+
tasks: { buckets: {}, dataLength: 0, taskCache: {} },
|
|
6
|
+
addTask: () => { },
|
|
7
|
+
getTasks: () => [],
|
|
8
|
+
updateTask: () => { },
|
|
9
|
+
deleteTask: () => { },
|
|
10
|
+
cleanExpiredTasks: () => { },
|
|
11
|
+
cleanExpiredTasksByHash: () => { },
|
|
12
|
+
isValidTask: () => false,
|
|
13
|
+
getTask: () => undefined,
|
|
14
|
+
hashScope: "week"
|
|
15
|
+
});
|
|
16
|
+
const CalendarTaskContextProvider = ({ children, hashScope }) => {
|
|
17
|
+
const { tasks, addTask, getTasks, updateTask, deleteTask, cleanExpiredTasks, cleanExpiredTasksByHash, isValidTask, getTask } = useCalendarTask(hashScope || "week");
|
|
18
|
+
return (_jsx(CalendarTaskContext.Provider, { value: {
|
|
19
|
+
tasks,
|
|
20
|
+
addTask,
|
|
21
|
+
getTasks,
|
|
22
|
+
updateTask,
|
|
23
|
+
deleteTask,
|
|
24
|
+
cleanExpiredTasks,
|
|
25
|
+
cleanExpiredTasksByHash,
|
|
26
|
+
isValidTask,
|
|
27
|
+
getTask,
|
|
28
|
+
hashScope
|
|
29
|
+
}, children: children }));
|
|
30
|
+
};
|
|
31
|
+
export const useCalendarTaskContext = () => useContext(CalendarTaskContext);
|
|
32
|
+
export default CalendarTaskContextProvider;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { calculateWeekDifference, getDateObjectInTimeZone, getDayHourly, getWeekDays, } from "../lib/utils";
|
|
3
3
|
function useCalendarDateState(date, weekOffset, timeZone) {
|
|
4
4
|
const [calendarDateState, setCalendarDateState] = useState({ dailyHours: [], weekDays: [] });
|
|
5
5
|
useEffect(() => {
|
|
6
6
|
const weekOffsetByDate = timeZone
|
|
7
|
-
?
|
|
8
|
-
:
|
|
7
|
+
? calculateWeekDifference(getDateObjectInTimeZone(timeZone), timeZone)
|
|
8
|
+
: calculateWeekDifference(date, timeZone);
|
|
9
9
|
const weekDays = getWeekDays(weekOffsetByDate || weekOffset || 0, timeZone);
|
|
10
10
|
const dailyHours = getDayHourly(weekOffsetByDate || weekOffset || 0, timeZone);
|
|
11
11
|
const calData = {
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { useEffect, useRef, useState, useMemo } from "react";
|
|
2
|
+
import { getHash, updateOffsetWithDateCalendar } from "../lib/utils";
|
|
3
|
+
const STORAGE_KEY = "calendar_tasks";
|
|
4
|
+
export function useCalendarTask(hashScope, timeZone) {
|
|
5
|
+
const tasksRef = useRef({ buckets: {}, dataLength: 0, taskCache: {} });
|
|
6
|
+
const scheduleCleanRef = useRef(null);
|
|
7
|
+
const [render, forceRender] = useState(0);
|
|
8
|
+
const cleanExpiredTasks = () => {
|
|
9
|
+
const store = tasksRef.current;
|
|
10
|
+
let newDataLength = 0;
|
|
11
|
+
for (const hash in store.buckets) {
|
|
12
|
+
const bucket = store.buckets[hash];
|
|
13
|
+
const validTasks = bucket.list.filter(isValidTask);
|
|
14
|
+
newDataLength += validTasks.length;
|
|
15
|
+
if (validTasks.length === bucket.list.length)
|
|
16
|
+
continue;
|
|
17
|
+
const newIndexMap = {};
|
|
18
|
+
let sumOfTaskDuration = 0;
|
|
19
|
+
validTasks.forEach((task, index) => {
|
|
20
|
+
newIndexMap[task.id] = index;
|
|
21
|
+
sumOfTaskDuration += task.taskEnd - task.taskStart;
|
|
22
|
+
});
|
|
23
|
+
store.buckets[hash] = {
|
|
24
|
+
list: validTasks,
|
|
25
|
+
indexMap: newIndexMap,
|
|
26
|
+
sumOfTaskDuration,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
store.dataLength = newDataLength;
|
|
30
|
+
saveToStorage();
|
|
31
|
+
forceRender((x) => x + 1);
|
|
32
|
+
};
|
|
33
|
+
const cleanExpiredTasksByHash = (hash) => {
|
|
34
|
+
const bucket = tasksRef.current.buckets[hash];
|
|
35
|
+
if (!bucket)
|
|
36
|
+
return;
|
|
37
|
+
const validTasks = bucket.list.filter(isValidTask);
|
|
38
|
+
if (validTasks.length === bucket.list.length)
|
|
39
|
+
return;
|
|
40
|
+
const removedCount = bucket.list.length - validTasks.length;
|
|
41
|
+
tasksRef.current.dataLength -= removedCount;
|
|
42
|
+
const newIndexMap = {};
|
|
43
|
+
let sumOfTaskDuration = 0;
|
|
44
|
+
validTasks.forEach((task, index) => {
|
|
45
|
+
newIndexMap[task.id] = index;
|
|
46
|
+
sumOfTaskDuration += task.taskEnd - task.taskStart;
|
|
47
|
+
});
|
|
48
|
+
tasksRef.current.buckets[hash] = {
|
|
49
|
+
list: validTasks,
|
|
50
|
+
indexMap: newIndexMap,
|
|
51
|
+
sumOfTaskDuration,
|
|
52
|
+
};
|
|
53
|
+
saveToStorage();
|
|
54
|
+
forceRender((x) => x + 1);
|
|
55
|
+
};
|
|
56
|
+
if (!scheduleCleanRef.current) {
|
|
57
|
+
scheduleCleanRef.current = (() => {
|
|
58
|
+
let timeout;
|
|
59
|
+
return () => {
|
|
60
|
+
clearTimeout(timeout);
|
|
61
|
+
timeout = setTimeout(() => {
|
|
62
|
+
if ("requestIdleCallback" in window) {
|
|
63
|
+
requestIdleCallback(() => {
|
|
64
|
+
cleanExpiredTasks();
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
setTimeout(() => cleanExpiredTasks(), 0);
|
|
69
|
+
}
|
|
70
|
+
}, 2000);
|
|
71
|
+
};
|
|
72
|
+
})();
|
|
73
|
+
}
|
|
74
|
+
const scheduleClean = scheduleCleanRef.current;
|
|
75
|
+
const getNow = () => {
|
|
76
|
+
const now = new Date();
|
|
77
|
+
if (timeZone) {
|
|
78
|
+
return new Date(now.toLocaleString("en-US", { timeZone }));
|
|
79
|
+
}
|
|
80
|
+
return now;
|
|
81
|
+
};
|
|
82
|
+
const isValidTask = (task) => {
|
|
83
|
+
if (!task.taskExpiryDate)
|
|
84
|
+
return false;
|
|
85
|
+
return new Date(task.taskExpiryDate) > getNow();
|
|
86
|
+
};
|
|
87
|
+
// 💾 SAVE → O(1)
|
|
88
|
+
const saveToStorage = () => {
|
|
89
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(tasksRef.current));
|
|
90
|
+
};
|
|
91
|
+
// 📦 LOAD (sans clean)
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
94
|
+
if (!stored)
|
|
95
|
+
return;
|
|
96
|
+
tasksRef.current = JSON.parse(stored);
|
|
97
|
+
forceRender((x) => x + 1);
|
|
98
|
+
}, []);
|
|
99
|
+
// ➕ ADD → O(1)
|
|
100
|
+
const addTask = (task) => {
|
|
101
|
+
const offset = updateOffsetWithDateCalendar(task.taskDate);
|
|
102
|
+
const hash = getHash(offset, task.groupId, task.dayIndex)[hashScope];
|
|
103
|
+
if (!tasksRef.current.buckets[hash]) {
|
|
104
|
+
tasksRef.current.buckets[hash] = {
|
|
105
|
+
list: [],
|
|
106
|
+
indexMap: {},
|
|
107
|
+
sumOfTaskDuration: 0,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const bucket = tasksRef.current.buckets[hash];
|
|
111
|
+
const index = bucket.list.length;
|
|
112
|
+
bucket.list.push(Object.assign(Object.assign({}, task), { hash }));
|
|
113
|
+
bucket.indexMap[task.id] = index;
|
|
114
|
+
bucket.sumOfTaskDuration += task.taskEnd - task.taskStart;
|
|
115
|
+
tasksRef.current.dataLength++;
|
|
116
|
+
saveToStorage();
|
|
117
|
+
scheduleClean();
|
|
118
|
+
forceRender((x) => x + 1);
|
|
119
|
+
};
|
|
120
|
+
const getTasks = (hash) => {
|
|
121
|
+
const bucket = tasksRef.current.buckets[hash];
|
|
122
|
+
if (!bucket)
|
|
123
|
+
return [];
|
|
124
|
+
return bucket.list;
|
|
125
|
+
};
|
|
126
|
+
const getTask = (hash, taskId) => {
|
|
127
|
+
const bucket = tasksRef.current.buckets[hash];
|
|
128
|
+
if (!bucket)
|
|
129
|
+
return;
|
|
130
|
+
const index = bucket.indexMap[taskId];
|
|
131
|
+
if (index === undefined)
|
|
132
|
+
return;
|
|
133
|
+
return bucket.list[index];
|
|
134
|
+
};
|
|
135
|
+
// ✏️ UPDATE → O(1)
|
|
136
|
+
const updateTask = (hash, taskId, updatedTask) => {
|
|
137
|
+
const bucket = tasksRef.current.buckets[hash];
|
|
138
|
+
if (!bucket)
|
|
139
|
+
return;
|
|
140
|
+
const index = bucket.indexMap[taskId];
|
|
141
|
+
if (index === undefined)
|
|
142
|
+
return;
|
|
143
|
+
const oldTAskDuration = bucket.list[index].taskEnd - bucket.list[index].taskStart;
|
|
144
|
+
if (updatedTask.taskEnd && updatedTask.taskStart) {
|
|
145
|
+
const newTAskDuration = updatedTask.taskEnd - updatedTask.taskStart;
|
|
146
|
+
if (newTAskDuration !== oldTAskDuration) {
|
|
147
|
+
bucket.sumOfTaskDuration = bucket.sumOfTaskDuration - oldTAskDuration + newTAskDuration;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
bucket.list[index] = Object.assign(Object.assign({}, bucket.list[index]), updatedTask);
|
|
151
|
+
saveToStorage();
|
|
152
|
+
scheduleClean();
|
|
153
|
+
forceRender((x) => x + 1);
|
|
154
|
+
};
|
|
155
|
+
// ❌ DELETE → O(1)
|
|
156
|
+
const deleteTask = (hash, taskId) => {
|
|
157
|
+
const bucket = tasksRef.current.buckets[hash];
|
|
158
|
+
if (!bucket)
|
|
159
|
+
return;
|
|
160
|
+
const index = bucket.indexMap[taskId];
|
|
161
|
+
if (index === undefined)
|
|
162
|
+
return;
|
|
163
|
+
const lastIndex = bucket.list.length - 1;
|
|
164
|
+
const lastTask = bucket.list[lastIndex];
|
|
165
|
+
const oldTAskDuration = bucket.list[index].taskEnd - bucket.list[index].taskStart;
|
|
166
|
+
[bucket.list[index], bucket.list[lastIndex]] = [
|
|
167
|
+
bucket.list[lastIndex],
|
|
168
|
+
bucket.list[index],
|
|
169
|
+
];
|
|
170
|
+
bucket.indexMap[lastTask.id] = index;
|
|
171
|
+
bucket.list.pop();
|
|
172
|
+
delete bucket.indexMap[taskId];
|
|
173
|
+
bucket.sumOfTaskDuration -= oldTAskDuration;
|
|
174
|
+
tasksRef.current.dataLength--;
|
|
175
|
+
saveToStorage();
|
|
176
|
+
scheduleClean();
|
|
177
|
+
forceRender((x) => x + 1);
|
|
178
|
+
};
|
|
179
|
+
const tasks = useMemo(() => (Object.assign({}, tasksRef.current)), [render]);
|
|
180
|
+
return {
|
|
181
|
+
tasks,
|
|
182
|
+
addTask,
|
|
183
|
+
getTasks,
|
|
184
|
+
updateTask,
|
|
185
|
+
deleteTask,
|
|
186
|
+
cleanExpiredTasks,
|
|
187
|
+
cleanExpiredTasksByHash,
|
|
188
|
+
isValidTask,
|
|
189
|
+
getTask
|
|
190
|
+
};
|
|
191
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
export function useContainerScroll(mainContaierRef, cardHeight, gridGap) {
|
|
3
|
+
const [sliceIndex, setSliceIndex] = useState(0);
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
const mainContainer = mainContaierRef.current;
|
|
6
|
+
const handleScroll = () => {
|
|
7
|
+
const computeIndex = Math.floor(mainContainer.scrollTop / (cardHeight + gridGap));
|
|
8
|
+
setSliceIndex(computeIndex);
|
|
9
|
+
};
|
|
10
|
+
mainContainer.addEventListener("scroll", handleScroll);
|
|
11
|
+
return () => mainContainer.removeEventListener("scroll", handleScroll);
|
|
12
|
+
}, [cardHeight, gridGap]);
|
|
13
|
+
return { sliceIndex };
|
|
14
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
export const useData = (sliceIndex, itemsByLine, windowLines, tasks, bottomBufferSize, topBufferSize) => {
|
|
3
|
+
console.log(sliceIndex, itemsByLine, windowLines, tasks, "virtual");
|
|
4
|
+
const visibleTasks = useMemo(() => tasks.slice(Math.max(sliceIndex - topBufferSize, 0) * itemsByLine, Math.min(tasks.length, (windowLines + bottomBufferSize) * itemsByLine +
|
|
5
|
+
sliceIndex * itemsByLine)), [sliceIndex, itemsByLine, windowLines, tasks]);
|
|
6
|
+
return { visibleTasks };
|
|
7
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { useWindowSize } from "./useWindowsSize";
|
|
3
|
+
export const useGridContainer = (gridContaineRef) => {
|
|
4
|
+
const [cardCompH, setCardCompH] = useState(0);
|
|
5
|
+
const [itemsByLine, setItemsByLines] = useState(1);
|
|
6
|
+
const cardRef = useRef(undefined);
|
|
7
|
+
const { width } = useWindowSize();
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (typeof window === "undefined")
|
|
10
|
+
return;
|
|
11
|
+
//si le parent n'a aucun enfant on sort
|
|
12
|
+
if (gridContaineRef.current.hasChildNodes() === false)
|
|
13
|
+
return;
|
|
14
|
+
//la hauteur du premier enfant de la grid
|
|
15
|
+
const cardH = gridContaineRef.current.firstChild.clientHeight;
|
|
16
|
+
if (cardH !== cardCompH) {
|
|
17
|
+
const gridW = gridContaineRef.current.clientWidth;
|
|
18
|
+
const cardW = gridContaineRef.current.firstChild.clientWidth;
|
|
19
|
+
setCardCompH(cardH);
|
|
20
|
+
setItemsByLines(Math.floor(gridW / cardW));
|
|
21
|
+
}
|
|
22
|
+
}, [width]);
|
|
23
|
+
return { cardCompH, cardRef, itemsByLine };
|
|
24
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
export function useIntersectionObserver(elementRef, { threshold = 0, root = null, rootMargin = "0%", freezeOnceVisible = false, } = {}) {
|
|
3
|
+
const [entry, setEntry] = useState();
|
|
4
|
+
const frozen = (entry === null || entry === void 0 ? void 0 : entry.isIntersecting) && freezeOnceVisible;
|
|
5
|
+
const updateEntry = ([entry]) => {
|
|
6
|
+
setEntry(entry);
|
|
7
|
+
};
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const node = elementRef === null || elementRef === void 0 ? void 0 : elementRef.current; // DOM node
|
|
10
|
+
const hasIOSupport = !!window.IntersectionObserver;
|
|
11
|
+
if (!hasIOSupport || frozen || !node)
|
|
12
|
+
return;
|
|
13
|
+
const observerParams = { threshold, root, rootMargin };
|
|
14
|
+
const observer = new IntersectionObserver(updateEntry, observerParams);
|
|
15
|
+
observer.observe(node);
|
|
16
|
+
return () => observer.disconnect();
|
|
17
|
+
}, [elementRef, JSON.stringify(threshold), root, rootMargin, frozen]);
|
|
18
|
+
return entry;
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
export function useMainContainerContent(mainContaierRef, cardCompH) {
|
|
3
|
+
const [windowLines, setWindowLine] = useState(0);
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
if (typeof window === "undefined")
|
|
6
|
+
return;
|
|
7
|
+
if (cardCompH === 0)
|
|
8
|
+
return;
|
|
9
|
+
const windowContent = Math.ceil(mainContaierRef.current.clientHeight / cardCompH);
|
|
10
|
+
if (windowContent !== windowLines)
|
|
11
|
+
setWindowLine(windowContent);
|
|
12
|
+
}, [cardCompH]);
|
|
13
|
+
//le nombre de lignes que peut contenir la fenetre
|
|
14
|
+
return { windowLines };
|
|
15
|
+
}
|
|
16
|
+
export default useMainContainerContent;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
export function useWindowSize() {
|
|
3
|
+
const [windowSize, setWindowSize] = useState({
|
|
4
|
+
width: window.innerWidth,
|
|
5
|
+
height: window.innerHeight,
|
|
6
|
+
});
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
function handleResize() {
|
|
9
|
+
setWindowSize({
|
|
10
|
+
width: window.innerWidth,
|
|
11
|
+
height: window.innerHeight,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
window.addEventListener('resize', handleResize);
|
|
15
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
16
|
+
}, []);
|
|
17
|
+
return windowSize;
|
|
18
|
+
}
|
|
19
|
+
// Utilisation dans un composant
|
package/dist/index.js
CHANGED
|
@@ -4,4 +4,16 @@ export { default as Calendar } from './components/index';
|
|
|
4
4
|
// 2. Tu exportes tout le contenu de ton fichier utils
|
|
5
5
|
export * from './lib/utils';
|
|
6
6
|
export * from './definitions/index';
|
|
7
|
-
|
|
7
|
+
export * from './hooks/useCalendarTask';
|
|
8
|
+
export * from './contexts/CalendarTaskContext';
|
|
9
|
+
export * from './components/TaskContainer/';
|
|
10
|
+
export * from './components/TaskContainer/TaskVirtual';
|
|
11
|
+
export * from './components/AddTask';
|
|
12
|
+
export * from './components/SumHoursContainer';
|
|
13
|
+
export * from './components/SumHoursHead';
|
|
14
|
+
export * from './components/GroupContainer';
|
|
15
|
+
export * from './components/VirtualGroupRow';
|
|
16
|
+
export * from './components/VirtualGroupRowDay';
|
|
17
|
+
export * from './components/DayContainer';
|
|
18
|
+
export * from './hooks/useCalendarDateState';
|
|
19
|
+
export * from './contexts/CalendarTaskContext';
|