react-weekly-planning 1.0.39 → 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/dist/components/AddTask/index.js +16 -0
- package/dist/components/CalendarForWeek.js +30 -0
- package/dist/components/CalendarForday.js +22 -0
- package/dist/components/DayContainer/index.js +15 -0
- package/dist/components/GroupContainer/index.js +15 -0
- package/dist/components/GroupsHeadContainer/index.js +8 -0
- package/dist/components/SumHoursContainer/index.js +15 -0
- package/dist/components/SumHoursHead/index.js +8 -0
- package/dist/components/TaskContainer/TaskVirtual.js +18 -0
- package/dist/components/TaskContainer/index.js +36 -0
- package/dist/components/TaskList/index.js +4 -0
- package/dist/components/VirtualGroupRow.js +64 -0
- package/dist/components/VirtualGroupRowDay.js +49 -0
- package/dist/components/index.js +70 -0
- package/dist/contexts/CalendarContext.js +8 -0
- package/dist/contexts/CalendarTaskContext.js +32 -0
- package/dist/definitions/index.js +1 -0
- package/dist/hooks/useCalendarDateState.js +19 -0
- 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 +19 -0
- package/dist/lib/slyles.js +21 -0
- package/dist/lib/utils.js +657 -0
- package/dist/types/components/AddTask/index.d.ts +3 -0
- package/dist/types/components/CalendarForWeek.d.ts +3 -0
- package/dist/types/components/CalendarForday.d.ts +5 -0
- package/dist/types/components/DayContainer/index.d.ts +3 -0
- package/dist/types/components/GroupContainer/index.d.ts +3 -0
- package/dist/types/components/GroupsHeadContainer/index.d.ts +3 -0
- package/dist/types/components/SumHoursContainer/index.d.ts +3 -0
- package/dist/types/components/SumHoursHead/index.d.ts +3 -0
- package/dist/types/components/TaskContainer/TaskVirtual.d.ts +4 -0
- package/dist/types/components/TaskContainer/index.d.ts +3 -0
- package/dist/types/components/TaskList/index.d.ts +5 -0
- package/dist/types/components/VirtualGroupRow.d.ts +18 -0
- package/dist/types/components/VirtualGroupRowDay.d.ts +19 -0
- package/dist/types/components/index.d.ts +65 -0
- package/dist/types/contexts/CalendarContext.d.ts +7 -0
- package/dist/types/contexts/CalendarTaskContext.d.ts +23 -0
- package/dist/types/definitions/index.d.ts +417 -0
- package/dist/types/hooks/useCalendarDateState.d.ts +6 -0
- 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 +16 -0
- package/dist/types/lib/slyles.d.ts +4 -0
- package/dist/types/lib/utils.d.ts +90 -0
- package/package.json +1 -1
|
@@ -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
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
// 1. Tu exportes ton composant principal
|
|
3
|
+
export { default as Calendar } from './components/index';
|
|
4
|
+
// 2. Tu exportes tout le contenu de ton fichier utils
|
|
5
|
+
export * from './lib/utils';
|
|
6
|
+
export * from './definitions/index';
|
|
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';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const theadTrStyle = {
|
|
2
|
+
color: "#0f5173",
|
|
3
|
+
fontWeight: "300",
|
|
4
|
+
position: "sticky",
|
|
5
|
+
top: -1,
|
|
6
|
+
};
|
|
7
|
+
export const groupTdStyle = {
|
|
8
|
+
height: "auto",
|
|
9
|
+
width: "150px",
|
|
10
|
+
};
|
|
11
|
+
export const groupContainerStyle = {
|
|
12
|
+
width: "100%",
|
|
13
|
+
height: "100%",
|
|
14
|
+
display: "flex",
|
|
15
|
+
alignItems: "center",
|
|
16
|
+
justifyContent: "space-between",
|
|
17
|
+
gap: "0.75rem", // Correspond à gap-3
|
|
18
|
+
padding: "0.75rem", // Correspond à p-3
|
|
19
|
+
color: "#0f5173",
|
|
20
|
+
cursor: "pointer",
|
|
21
|
+
};
|