akfatimeline 1.0.0
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 +924 -0
- package/babel.config.json +4 -0
- package/package.json +50 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +43 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/robots.txt +3 -0
- package/src/App copy.js +185 -0
- package/src/App.css +38 -0
- package/src/App.js +201 -0
- package/src/App.test.js +8 -0
- package/src/components/Timeline/DragAndDropHandler.js +35 -0
- package/src/components/Timeline/EventTooltip.js +206 -0
- package/src/components/Timeline/Indicator.js +30 -0
- package/src/components/Timeline/MasterHeader.js +55 -0
- package/src/components/Timeline/Resources.js +53 -0
- package/src/components/Timeline/ResourcesHeader.js +14 -0
- package/src/components/Timeline/Timeline.css +534 -0
- package/src/components/Timeline/Timeline.js +277 -0
- package/src/components/Timeline/TimelineCell.js +8 -0
- package/src/components/Timeline/TimelineContent copy.js +421 -0
- package/src/components/Timeline/TimelineContent.js +422 -0
- package/src/components/Timeline/TimelineEvents.js +114 -0
- package/src/components/Timeline/TimelineHeader.js +43 -0
- package/src/components/Timeline/TimelineMonthContainer.js +29 -0
- package/src/components/Timeline/TimelineResources.js +16 -0
- package/src/dist/Timeline.js +277 -0
- package/src/hooks/useDragAndDrop.js +80 -0
- package/src/hooks/useEventDragDrop.js +120 -0
- package/src/hooks/useExtendEvent.js +28 -0
- package/src/index.css +13 -0
- package/src/index.js +17 -0
- package/src/logo.svg +1 -0
- package/src/reportWebVitals.js +13 -0
- package/src/setupTests.js +5 -0
- package/src/utils/HorizontalVirtualScroll.js +0 -0
- package/src/utils/dateUtils.js +36 -0
- package/src/utils/filterTimelineData.js +21 -0
- package/src/utils/timelineUtils.js +40 -0
- package/webpack.config.js +31 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import MasterHeader from "./MasterHeader";
|
|
3
|
+
import ResourcesHeader from "./ResourcesHeader";
|
|
4
|
+
import Resources from "./Resources";
|
|
5
|
+
import TimelineHeader from "./TimelineHeader";
|
|
6
|
+
import TimelineContent from "./TimelineContent";
|
|
7
|
+
import "./Timeline.css";
|
|
8
|
+
import EventTooltip from "./EventTooltip";
|
|
9
|
+
import { generateTimelineData } from "../../utils/timelineUtils";
|
|
10
|
+
|
|
11
|
+
const Timeline = ({
|
|
12
|
+
resources,
|
|
13
|
+
programDate = null,
|
|
14
|
+
events = [],
|
|
15
|
+
resourceSettings = {
|
|
16
|
+
showIdAsName: false,
|
|
17
|
+
isGrouped: true,
|
|
18
|
+
isCollapsible: true,
|
|
19
|
+
},
|
|
20
|
+
indicatorOn = false,
|
|
21
|
+
dropInfo,
|
|
22
|
+
setDropInfo,
|
|
23
|
+
|
|
24
|
+
masterHeaderView = true,
|
|
25
|
+
resourceHeaderContent = "Akfa Timeline",
|
|
26
|
+
eventsDragOn = true,
|
|
27
|
+
eventsExtendOn = true,
|
|
28
|
+
createNewEventOn = true,
|
|
29
|
+
onDragInfo,
|
|
30
|
+
onExtendInfo,
|
|
31
|
+
onCreateEventInfo,
|
|
32
|
+
// İsteğe bağlı event tıklama callback'leri
|
|
33
|
+
onEventClick,
|
|
34
|
+
onEventRightClick,
|
|
35
|
+
|
|
36
|
+
// Yatay scroll özelliği aç/kapa
|
|
37
|
+
horizontalScrollOn = false, // Varsayılan false
|
|
38
|
+
}) => {
|
|
39
|
+
// ---------------------------------------------------------
|
|
40
|
+
// 1) timelineData oluştur (dates, monthHeaders vs.)
|
|
41
|
+
// ---------------------------------------------------------
|
|
42
|
+
const timelineData = generateTimelineData(2020, 2030); // 10 yıllık veri
|
|
43
|
+
const { dates, monthHeaders } = timelineData;
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------
|
|
46
|
+
// 2) local state
|
|
47
|
+
// ---------------------------------------------------------
|
|
48
|
+
const [collapsedGroups, setCollapsedGroups] = useState({});
|
|
49
|
+
const [selectedDate, setSelectedDate] = useState(() => {
|
|
50
|
+
const date = programDate ? new Date(programDate) : new Date();
|
|
51
|
+
date.setDate(date.getDate() - 3);
|
|
52
|
+
return date;
|
|
53
|
+
});
|
|
54
|
+
const [localEvents, setLocalEvents] = useState(events);
|
|
55
|
+
|
|
56
|
+
const [selectedEvent, setSelectedEvent] = useState(null);
|
|
57
|
+
const [tooltipPosition, setTooltipPosition] = useState({ top: 0, left: 0 });
|
|
58
|
+
|
|
59
|
+
// dayRange = ekranda göstermeyi istediğimiz gün/hücre sayısı (ör. 30 gün)
|
|
60
|
+
const [dayRange, setDayRange] = useState(30);
|
|
61
|
+
|
|
62
|
+
const [isDarkMode, setIsDarkMode] = useState(false);
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------
|
|
65
|
+
// 3) Sabit hücre genişliği (örneğin 56.95 px)
|
|
66
|
+
// Container genişliği = dayRange * cellWidth
|
|
67
|
+
// ---------------------------------------------------------
|
|
68
|
+
const cellWidth = 56.95; // her gün/hücre ~57 piksel
|
|
69
|
+
const containerWidth = dayRange * cellWidth;
|
|
70
|
+
// örneğin dayRange=30 => containerWidth=30*56.95=1708.5 px
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------
|
|
73
|
+
// 4) Event Tooltip logic
|
|
74
|
+
// ---------------------------------------------------------
|
|
75
|
+
const handleEventClick = (event, e) => {
|
|
76
|
+
// Harici onEventClick callback'i varsa, önce onu tetikleyelim
|
|
77
|
+
if (onEventClick) {
|
|
78
|
+
onEventClick(event, e);
|
|
79
|
+
}
|
|
80
|
+
// Ardından tooltip göstermek istiyorsak:
|
|
81
|
+
const eventElement = e.currentTarget;
|
|
82
|
+
if (eventElement) {
|
|
83
|
+
const rect = eventElement.getBoundingClientRect();
|
|
84
|
+
setTooltipPosition({
|
|
85
|
+
top: rect.top + window.scrollY,
|
|
86
|
+
left: rect.left + rect.width / 2 + window.scrollX,
|
|
87
|
+
});
|
|
88
|
+
setSelectedEvent(event);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const handleCloseTooltip = () => {
|
|
93
|
+
setSelectedEvent(null);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------
|
|
97
|
+
// 5) Tarih filtreleme => filteredDates
|
|
98
|
+
// ---------------------------------------------------------
|
|
99
|
+
const startIndex = dates.findIndex((d) => d.fullDate >= selectedDate);
|
|
100
|
+
const endIndex = startIndex + dayRange;
|
|
101
|
+
const filteredDates =
|
|
102
|
+
startIndex !== -1 ? dates.slice(startIndex, Math.min(endIndex, dates.length)) : [];
|
|
103
|
+
|
|
104
|
+
const today = programDate ? new Date(programDate) : new Date();
|
|
105
|
+
const todayIndex = filteredDates.findIndex(
|
|
106
|
+
(d) => new Date(d.fullDate).toDateString() === today.toDateString()
|
|
107
|
+
);
|
|
108
|
+
const totalDays = filteredDates.length;
|
|
109
|
+
|
|
110
|
+
// ---------------------------------------------------------
|
|
111
|
+
// 6) Grupları aç/kapa
|
|
112
|
+
// ---------------------------------------------------------
|
|
113
|
+
const toggleGroupCollapse = (groupName) => {
|
|
114
|
+
setCollapsedGroups((prev) => ({
|
|
115
|
+
...prev,
|
|
116
|
+
[groupName]: !prev[groupName],
|
|
117
|
+
}));
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// ---------------------------------------------------------
|
|
121
|
+
// 7) Navigation fonksiyonları
|
|
122
|
+
// ---------------------------------------------------------
|
|
123
|
+
const handleToday = () => {
|
|
124
|
+
const date = programDate ? new Date(programDate) : new Date();
|
|
125
|
+
date.setDate(date.getDate() - 3);
|
|
126
|
+
setSelectedDate(date);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const handleAdvance = () =>
|
|
130
|
+
setSelectedDate((prev) => new Date(prev.getTime() + 5 * 24 * 60 * 60 * 1000));
|
|
131
|
+
|
|
132
|
+
const handleRetreat = () =>
|
|
133
|
+
setSelectedDate((prev) => new Date(prev.getTime() - 5 * 24 * 60 * 60 * 1000));
|
|
134
|
+
|
|
135
|
+
const handleMonthRetreat = () =>
|
|
136
|
+
setSelectedDate((prev) => {
|
|
137
|
+
const newDate = new Date(prev);
|
|
138
|
+
newDate.setMonth(newDate.getMonth() - 1);
|
|
139
|
+
return newDate;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const handleMonthAdvance = () =>
|
|
143
|
+
setSelectedDate((prev) => {
|
|
144
|
+
const newDate = new Date(prev);
|
|
145
|
+
newDate.setMonth(newDate.getMonth() + 1);
|
|
146
|
+
return newDate;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// ---------------------------------------------------------
|
|
150
|
+
// 8) Dark Mode
|
|
151
|
+
// ---------------------------------------------------------
|
|
152
|
+
const toggleDarkMode = () => {
|
|
153
|
+
setIsDarkMode((prevMode) => !prevMode);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
document.body.classList.toggle("dark-mode", isDarkMode);
|
|
158
|
+
}, [isDarkMode]);
|
|
159
|
+
|
|
160
|
+
// ---------------------------------------------------------
|
|
161
|
+
// 9) Ay başlıklarını filtrele
|
|
162
|
+
// ---------------------------------------------------------
|
|
163
|
+
const filteredMonthHeaders = monthHeaders
|
|
164
|
+
.map((header) => {
|
|
165
|
+
const adjustedStartIndex = Math.max(header.startIndex, startIndex);
|
|
166
|
+
const adjustedEndIndex = Math.min(header.endIndex, endIndex - 1);
|
|
167
|
+
return {
|
|
168
|
+
...header,
|
|
169
|
+
startIndex: adjustedStartIndex,
|
|
170
|
+
endIndex: adjustedEndIndex,
|
|
171
|
+
};
|
|
172
|
+
})
|
|
173
|
+
.filter((header) => header.startIndex <= header.endIndex);
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------
|
|
176
|
+
// 10) Return
|
|
177
|
+
// ---------------------------------------------------------
|
|
178
|
+
return (
|
|
179
|
+
<div className={`timeline-container ${isDarkMode ? "dark-mode" : ""}`}>
|
|
180
|
+
{/* Üst kısım: MasterHeader */}
|
|
181
|
+
{masterHeaderView && (
|
|
182
|
+
<div className="timeline-master-header">
|
|
183
|
+
<MasterHeader
|
|
184
|
+
onToday={handleToday}
|
|
185
|
+
onAdvance={handleAdvance}
|
|
186
|
+
onRetreat={handleRetreat}
|
|
187
|
+
onMonthAdvance={handleMonthAdvance}
|
|
188
|
+
onMonthRetreat={handleMonthRetreat}
|
|
189
|
+
dayRange={dayRange}
|
|
190
|
+
setDayRange={setDayRange} // dayRange'ı burada user değiştirebilir
|
|
191
|
+
isDarkMode={isDarkMode}
|
|
192
|
+
toggleDarkMode={toggleDarkMode}
|
|
193
|
+
/>
|
|
194
|
+
</div>
|
|
195
|
+
)}
|
|
196
|
+
{/* Body: Sol kısım => Resources, Sağ kısım => timeline */}
|
|
197
|
+
<div className="timeline-body">
|
|
198
|
+
<div className="timeline-resources-container">
|
|
199
|
+
<ResourcesHeader content={resourceHeaderContent} />
|
|
200
|
+
<Resources
|
|
201
|
+
groupedResources={resources}
|
|
202
|
+
toggleGroupCollapse={toggleGroupCollapse}
|
|
203
|
+
collapsedGroups={collapsedGroups}
|
|
204
|
+
resourceSettings={resourceSettings}
|
|
205
|
+
/>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
{/*
|
|
209
|
+
Dış kap => .timeline-scrollable-container
|
|
210
|
+
horizontalScrollOn => overflow-x auto/hidden
|
|
211
|
+
*/}
|
|
212
|
+
<div
|
|
213
|
+
className="timeline-scrollable-container"
|
|
214
|
+
style={{
|
|
215
|
+
overflowX: horizontalScrollOn ? "auto" : "hidden",
|
|
216
|
+
}}
|
|
217
|
+
>
|
|
218
|
+
{/*
|
|
219
|
+
İç kap => .timeline-header-content-wrapper
|
|
220
|
+
Genişlik => dayRange * cellWidth px (eğer horizontalScrollOn=true)
|
|
221
|
+
Yoksa 100% (scroll devre dışı)
|
|
222
|
+
*/}
|
|
223
|
+
<div
|
|
224
|
+
className="timeline-header-content-wrapper"
|
|
225
|
+
style={{
|
|
226
|
+
width: horizontalScrollOn ? `${containerWidth}px` : "100%",
|
|
227
|
+
}}
|
|
228
|
+
>
|
|
229
|
+
<TimelineHeader
|
|
230
|
+
dates={filteredDates}
|
|
231
|
+
monthHeaders={filteredMonthHeaders}
|
|
232
|
+
/>
|
|
233
|
+
|
|
234
|
+
<TimelineContent
|
|
235
|
+
// Props
|
|
236
|
+
groupedResources={resources}
|
|
237
|
+
dates={filteredDates}
|
|
238
|
+
collapsedGroups={collapsedGroups}
|
|
239
|
+
events={localEvents}
|
|
240
|
+
setEvents={setLocalEvents}
|
|
241
|
+
onEventClick={handleEventClick}
|
|
242
|
+
todayIndex={todayIndex}
|
|
243
|
+
totalDays={totalDays}
|
|
244
|
+
indicatorOn={indicatorOn}
|
|
245
|
+
resourceSettings={resourceSettings}
|
|
246
|
+
toggleGroupCollapse={toggleGroupCollapse}
|
|
247
|
+
setDropInfo={setDropInfo}
|
|
248
|
+
|
|
249
|
+
// Yeni prop'lar
|
|
250
|
+
eventsDragOn={eventsDragOn}
|
|
251
|
+
eventsExtendOn={eventsExtendOn}
|
|
252
|
+
createNewEventOn={createNewEventOn}
|
|
253
|
+
onDragInfo={onDragInfo}
|
|
254
|
+
onExtendInfo={onExtendInfo}
|
|
255
|
+
onCreateEventInfo={onCreateEventInfo}
|
|
256
|
+
onEventRightClick={onEventRightClick}
|
|
257
|
+
/>
|
|
258
|
+
|
|
259
|
+
{/* Tooltip */}
|
|
260
|
+
{selectedEvent && (
|
|
261
|
+
<EventTooltip
|
|
262
|
+
event={selectedEvent}
|
|
263
|
+
position={tooltipPosition}
|
|
264
|
+
onClose={handleCloseTooltip}
|
|
265
|
+
onDelete={(eventId) =>
|
|
266
|
+
setLocalEvents((prev) => prev.filter((e) => e.id !== eventId))
|
|
267
|
+
}
|
|
268
|
+
/>
|
|
269
|
+
)}
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
);
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
export default Timeline;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// src/hooks/useDragAndDrop.js
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { parseDate } from "../utils/dateUtils"; // Named import
|
|
4
|
+
|
|
5
|
+
const useDragAndDrop = (initialEvents = []) => {
|
|
6
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
7
|
+
const [dragStart, setDragStart] = useState(null);
|
|
8
|
+
const [dragEnd, setDragEnd] = useState(null);
|
|
9
|
+
const [events, setEvents] = useState(initialEvents);
|
|
10
|
+
|
|
11
|
+
// Sürükleme başlat
|
|
12
|
+
const startDrag = (resourceId, date) => {
|
|
13
|
+
setIsDragging(true);
|
|
14
|
+
setDragStart({ resourceId, date });
|
|
15
|
+
setDragEnd({ resourceId, date });
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Sürükleme hareketi
|
|
19
|
+
const updateDrag = (resourceId, date) => {
|
|
20
|
+
if (!isDragging) return;
|
|
21
|
+
setDragEnd({ resourceId, date });
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Sürükleme bitişi
|
|
25
|
+
const endDrag = (dates) => {
|
|
26
|
+
if (!isDragging || !dragStart || !dragEnd) return;
|
|
27
|
+
if (dragStart.resourceId !== dragEnd.resourceId) {
|
|
28
|
+
resetDrag();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const startDateIndex = dates.findIndex(
|
|
33
|
+
(d) => parseDate(d.fullDate).toDateString() === parseDate(dragStart.date.fullDate).toDateString()
|
|
34
|
+
);
|
|
35
|
+
const endDateIndex = dates.findIndex(
|
|
36
|
+
(d) => parseDate(d.fullDate).toDateString() === parseDate(dragEnd.date.fullDate).toDateString()
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
if (startDateIndex === -1 || endDateIndex === -1) {
|
|
40
|
+
resetDrag();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const sortedStartIndex = Math.min(startDateIndex, endDateIndex);
|
|
45
|
+
const sortedEndIndex = Math.max(startDateIndex, endDateIndex);
|
|
46
|
+
const startDate = dates[sortedStartIndex].fullDate;
|
|
47
|
+
const endDate = dates[sortedEndIndex].fullDate;
|
|
48
|
+
|
|
49
|
+
const newEvent = {
|
|
50
|
+
id: Date.now(),
|
|
51
|
+
title: "Yeni Etkinlik",
|
|
52
|
+
resourceId: dragStart.resourceId,
|
|
53
|
+
startDate: startDate,
|
|
54
|
+
endDate: endDate,
|
|
55
|
+
color: "#ff7f50",
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
setEvents((prev) => [...prev, newEvent]);
|
|
59
|
+
resetDrag();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const resetDrag = () => {
|
|
63
|
+
setIsDragging(false);
|
|
64
|
+
setDragStart(null);
|
|
65
|
+
setDragEnd(null);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
events,
|
|
70
|
+
isDragging,
|
|
71
|
+
dragStart,
|
|
72
|
+
dragEnd,
|
|
73
|
+
startDrag,
|
|
74
|
+
updateDrag,
|
|
75
|
+
endDrag,
|
|
76
|
+
setEvents,
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export default useDragAndDrop;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
|
|
3
|
+
const useEventDragDrop = (events, setEvents, setDropInfo) => {
|
|
4
|
+
const [draggingEvent, setDraggingEvent] = useState(null);
|
|
5
|
+
const [dragOffset, setDragOffset] = useState(0);
|
|
6
|
+
const [mode, setMode] = useState(null); // "drag" veya "extend"
|
|
7
|
+
|
|
8
|
+
const handleDragStart = (event, eventId) => {
|
|
9
|
+
if (mode === "extend") return; // Uzatma modundaysa taşıma işlemini başlatma
|
|
10
|
+
|
|
11
|
+
event.stopPropagation();
|
|
12
|
+
|
|
13
|
+
const eventElement = event.target;
|
|
14
|
+
const eventRect = eventElement.getBoundingClientRect();
|
|
15
|
+
const offset = event.clientX - eventRect.left;
|
|
16
|
+
|
|
17
|
+
setDraggingEvent(eventId);
|
|
18
|
+
setDragOffset(offset);
|
|
19
|
+
setMode("drag"); // Modu taşıma olarak ayarla
|
|
20
|
+
|
|
21
|
+
const draggedEvent = events.find((evt) => evt.id === eventId);
|
|
22
|
+
if (draggedEvent) {
|
|
23
|
+
console.log("Dragging Event Start:", draggedEvent.startDate);
|
|
24
|
+
console.log("Dragging Event End:", draggedEvent.endDate);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const handleExtendStart = (event, eventId) => {
|
|
29
|
+
event.stopPropagation();
|
|
30
|
+
|
|
31
|
+
setDraggingEvent(eventId);
|
|
32
|
+
setMode("extend"); // Modu uzatma olarak ayarla
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const handleDragOver = (event) => {
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const handleDrop = (event, resourceId, targetDate) => {
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
|
|
42
|
+
if (mode === "drag" && draggingEvent) {
|
|
43
|
+
const draggedEvent = events.find((evt) => evt.id === draggingEvent);
|
|
44
|
+
|
|
45
|
+
if (draggedEvent) {
|
|
46
|
+
const duration = draggedEvent.endDate - draggedEvent.startDate;
|
|
47
|
+
const cellWidth = event.target.offsetWidth || 30;
|
|
48
|
+
const offsetDays = Math.floor(dragOffset / cellWidth);
|
|
49
|
+
const newStartDate = new Date(targetDate.getTime() - offsetDays * 24 * 60 * 60 * 1000);
|
|
50
|
+
const newEndDate = new Date(newStartDate.getTime() + duration);
|
|
51
|
+
|
|
52
|
+
console.log("New Start Date:", newStartDate);
|
|
53
|
+
console.log("New End Date:", newEndDate);
|
|
54
|
+
|
|
55
|
+
setEvents((prevEvents) =>
|
|
56
|
+
prevEvents.map((evt) =>
|
|
57
|
+
evt.id === draggingEvent
|
|
58
|
+
? {
|
|
59
|
+
...evt,
|
|
60
|
+
resourceId,
|
|
61
|
+
startDate: newStartDate,
|
|
62
|
+
endDate: newEndDate,
|
|
63
|
+
}
|
|
64
|
+
: evt
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
if (setDropInfo) {
|
|
69
|
+
setDropInfo({
|
|
70
|
+
id: draggingEvent,
|
|
71
|
+
newResourceId: resourceId,
|
|
72
|
+
newStartDate,
|
|
73
|
+
newEndDate,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
setDraggingEvent(null);
|
|
80
|
+
setDragOffset(0);
|
|
81
|
+
setMode(null);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const handleExtend = (event, eventId, newEndDate) => {
|
|
85
|
+
if (mode !== "extend" || draggingEvent !== eventId) return;
|
|
86
|
+
|
|
87
|
+
setEvents((prevEvents) =>
|
|
88
|
+
prevEvents.map((evt) =>
|
|
89
|
+
evt.id === eventId
|
|
90
|
+
? {
|
|
91
|
+
...evt,
|
|
92
|
+
endDate: newEndDate,
|
|
93
|
+
}
|
|
94
|
+
: evt
|
|
95
|
+
)
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
console.log("Extended Event ID:", eventId, "New End Date:", newEndDate);
|
|
99
|
+
|
|
100
|
+
setDraggingEvent(null);
|
|
101
|
+
setMode(null);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const handleDragEnd = () => {
|
|
105
|
+
setDraggingEvent(null);
|
|
106
|
+
setDragOffset(0);
|
|
107
|
+
setMode(null);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
handleDragStart,
|
|
112
|
+
handleExtendStart,
|
|
113
|
+
handleDragOver,
|
|
114
|
+
handleDrop,
|
|
115
|
+
handleExtend,
|
|
116
|
+
handleDragEnd,
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export default useEventDragDrop;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
|
|
3
|
+
const useExtendEvent = (events, setEvents) => {
|
|
4
|
+
/**
|
|
5
|
+
* Etkinliği uzatmak veya kısaltmak için kullanılan işlev.
|
|
6
|
+
* @param {number} eventId - Güncellenmesi gereken etkinliğin ID'si.
|
|
7
|
+
* @param {Date} newEndDate - Etkinliğin yeni bitiş tarihi.
|
|
8
|
+
*/
|
|
9
|
+
const extendEvent = useCallback(
|
|
10
|
+
(eventId, newEndDate) => {
|
|
11
|
+
setEvents((prevEvents) =>
|
|
12
|
+
prevEvents.map((event) =>
|
|
13
|
+
event.id === eventId
|
|
14
|
+
? {
|
|
15
|
+
...event,
|
|
16
|
+
endDate: newEndDate, // Yeni bitiş tarihini günceller
|
|
17
|
+
}
|
|
18
|
+
: event // Diğer etkinlikler aynı kalır
|
|
19
|
+
)
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
[setEvents]
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
return { extendEvent };
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default useExtendEvent;
|
package/src/index.css
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
body {
|
|
2
|
+
margin: 0;
|
|
3
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
4
|
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
5
|
+
sans-serif;
|
|
6
|
+
-webkit-font-smoothing: antialiased;
|
|
7
|
+
-moz-osx-font-smoothing: grayscale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
code {
|
|
11
|
+
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
12
|
+
monospace;
|
|
13
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom/client';
|
|
3
|
+
import './index.css';
|
|
4
|
+
import App from './App';
|
|
5
|
+
import reportWebVitals from './reportWebVitals';
|
|
6
|
+
|
|
7
|
+
const root = ReactDOM.createRoot(document.getElementById('root'));
|
|
8
|
+
root.render(
|
|
9
|
+
<React.StrictMode>
|
|
10
|
+
<App />
|
|
11
|
+
</React.StrictMode>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
// If you want to start measuring performance in your app, pass a function
|
|
15
|
+
// to log results (for example: reportWebVitals(console.log))
|
|
16
|
+
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
|
17
|
+
reportWebVitals();
|
package/src/logo.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const reportWebVitals = onPerfEntry => {
|
|
2
|
+
if (onPerfEntry && onPerfEntry instanceof Function) {
|
|
3
|
+
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
|
4
|
+
getCLS(onPerfEntry);
|
|
5
|
+
getFID(onPerfEntry);
|
|
6
|
+
getFCP(onPerfEntry);
|
|
7
|
+
getLCP(onPerfEntry);
|
|
8
|
+
getTTFB(onPerfEntry);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default reportWebVitals;
|
|
File without changes
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/utils/dateUtils.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* "dd/mm/yyyy" formatındaki bir tarih string'ini Date objesine dönüştürür.
|
|
5
|
+
* Eğer dateInput bir string değilse, direkt Date objesini döndürür.
|
|
6
|
+
* @param {string | Object | Date} dateInput - "dd/mm/yyyy" formatında tarih stringi veya {fullDate: Date, display: string} objesi veya Date objesi.
|
|
7
|
+
* @returns {Date} - Date objesi.
|
|
8
|
+
*/
|
|
9
|
+
export const parseDate = (dateInput) => {
|
|
10
|
+
if (dateInput instanceof Date) {
|
|
11
|
+
return dateInput;
|
|
12
|
+
}
|
|
13
|
+
if (typeof dateInput === 'string') {
|
|
14
|
+
const [day, month, year] = dateInput.split("/").map(Number);
|
|
15
|
+
return new Date(year, month - 1, day);
|
|
16
|
+
} else if (typeof dateInput === 'object' && dateInput.fullDate instanceof Date) {
|
|
17
|
+
return new Date(dateInput.fullDate.getTime() + dateInput.fullDate.getTimezoneOffset() * 60000);
|
|
18
|
+
} else {
|
|
19
|
+
console.error("parseDate received invalid input:", dateInput);
|
|
20
|
+
return new Date(); // veya hata fırlat
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Bir tarihin belirli bir aralık içinde olup olmadığını kontrol eder.
|
|
26
|
+
* @param {string | Object | Date} date - "dd/mm/yyyy" formatında tarih stringi, {fullDate: Date, display: string} objesi veya Date objesi.
|
|
27
|
+
* @param {string | Object | Date} startDate - "dd/mm/yyyy" formatında başlangıç tarihi stringi, {fullDate: Date, display: string} objesi veya Date objesi.
|
|
28
|
+
* @param {string | Object | Date} endDate - "dd/mm/yyyy" formatında bitiş tarihi stringi, {fullDate: Date, display: string} objesi veya Date objesi.
|
|
29
|
+
* @returns {boolean} - Tarih aralık içinde ise true, değilse false.
|
|
30
|
+
*/
|
|
31
|
+
export const isDateInRange = (date, startDate, endDate) => {
|
|
32
|
+
const d = parseDate(date);
|
|
33
|
+
const start = parseDate(startDate);
|
|
34
|
+
const end = parseDate(endDate);
|
|
35
|
+
return d >= start && d <= end;
|
|
36
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const filterTimelineData = (dates, startDate, dayCount) => {
|
|
2
|
+
const startIndex = dates.findIndex(
|
|
3
|
+
(date) => date.fullDate.toISOString().split("T")[0] === startDate
|
|
4
|
+
);
|
|
5
|
+
|
|
6
|
+
if (startIndex === -1) {
|
|
7
|
+
console.warn("Seçilen başlangıç tarihi bulunamadı.");
|
|
8
|
+
return { filteredDates: [] };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const filteredDates = dates.slice(startIndex, startIndex + dayCount);
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
filteredDates,
|
|
15
|
+
startIndex,
|
|
16
|
+
endIndex: startIndex + dayCount - 1,
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default filterTimelineData;
|
|
21
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/utils/timelineUtils.js
|
|
2
|
+
|
|
3
|
+
export const generateTimelineData = (startYear, endYear) => {
|
|
4
|
+
const dayNames = ["Paz", "Pzt", "Sal", "Çar", "Per", "Cum", "Cmt"];
|
|
5
|
+
const monthNames = [
|
|
6
|
+
"Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran",
|
|
7
|
+
"Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık",
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
const dates = [];
|
|
11
|
+
const monthHeaders = [];
|
|
12
|
+
|
|
13
|
+
for (let year = startYear; year <= endYear; year++) {
|
|
14
|
+
for (let month = 1; month <= 12; month++) {
|
|
15
|
+
const daysInMonth = new Date(year, month, 0).getDate();
|
|
16
|
+
const startIndex = dates.length;
|
|
17
|
+
|
|
18
|
+
for (let day = 1; day <= daysInMonth; day++) {
|
|
19
|
+
const date = new Date(year, month - 1, day);
|
|
20
|
+
const dayName = dayNames[date.getDay()];
|
|
21
|
+
dates.push({
|
|
22
|
+
fullDate: date,
|
|
23
|
+
display: `${day} ${dayName}`,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const endIndex = dates.length - 1;
|
|
28
|
+
monthHeaders.push({
|
|
29
|
+
monthName: monthNames[month - 1],
|
|
30
|
+
year,
|
|
31
|
+
startIndex,
|
|
32
|
+
endIndex,
|
|
33
|
+
totalDays: endIndex - startIndex + 1,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { dates, monthHeaders };
|
|
39
|
+
};
|
|
40
|
+
|