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,421 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from "react";
|
|
2
|
+
import { parseDate } from "../../utils/dateUtils";
|
|
3
|
+
import useDragAndDrop from "../../hooks/useDragAndDrop";
|
|
4
|
+
import useEventDragDrop from "../../hooks/useEventDragDrop";
|
|
5
|
+
import EventTooltip from "./EventTooltip";
|
|
6
|
+
import Indicator from "./Indicator";
|
|
7
|
+
|
|
8
|
+
const TimelineContent = ({ groupedResources, dates, collapsedGroups, events, setEvents, onEventClick, todayIndex, indicatorOn, resourceSettings,
|
|
9
|
+
|
|
10
|
+
}) => {
|
|
11
|
+
|
|
12
|
+
const containerRef = useRef(null);
|
|
13
|
+
const { isDragging, dragStart, dragEnd } = useDragAndDrop(events, setEvents);
|
|
14
|
+
const totalDays = dates.length; // Toplam gün sayısı hesaplandı
|
|
15
|
+
const { handleDragStart, handleDragOver, handleDrop, handleDragEnd } = useEventDragDrop(events, setEvents);
|
|
16
|
+
|
|
17
|
+
console.log("events content:", events);
|
|
18
|
+
console.log("Event Resource ID:", events.resourceId);
|
|
19
|
+
console.log("Current Resource ID:", groupedResources);
|
|
20
|
+
|
|
21
|
+
// Tooltip için state
|
|
22
|
+
const [selectedEvent, setSelectedEvent] = useState(null);
|
|
23
|
+
const [tooltipPosition, setTooltipPosition] = useState({ top: 0, left: 0 });
|
|
24
|
+
// Yeni Event oluşturma durumu ve geçici event
|
|
25
|
+
const [isCreating, setIsCreating] = useState(false);
|
|
26
|
+
const [tempEvent, setTempEvent] = useState(null);
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
const handleEventClick = (event, e) => {
|
|
30
|
+
e.stopPropagation();
|
|
31
|
+
const eventElement = e.currentTarget;
|
|
32
|
+
if (eventElement) {
|
|
33
|
+
const rect = eventElement.getBoundingClientRect();
|
|
34
|
+
setTooltipPosition({
|
|
35
|
+
top: rect.top + window.scrollY,
|
|
36
|
+
left: rect.left + rect.width / 2 + window.scrollX,
|
|
37
|
+
});
|
|
38
|
+
setSelectedEvent(event); // Seçilen event'i state'e kaydediyoruz
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
// Mouse hareketiyle geçici event'in bitiş tarihini güncelle
|
|
44
|
+
const handleMouseMove = (e) => {
|
|
45
|
+
if (isCreating && tempEvent) {
|
|
46
|
+
const cell = document.elementFromPoint(e.clientX, e.clientY);
|
|
47
|
+
|
|
48
|
+
// Hücre genişliğini dinamik olarak hesapla
|
|
49
|
+
const cellWidth = cell?.offsetWidth || 30;
|
|
50
|
+
|
|
51
|
+
const startX = tempEvent.startX || e.clientX; // İlk tıklamanın X pozisyonu
|
|
52
|
+
const deltaX = e.clientX - startX; // Hareket edilen mesafe
|
|
53
|
+
const daysToAdd = Math.max(1, Math.floor(deltaX / cellWidth)); // Gün hesaplama (en az 1 gün)
|
|
54
|
+
|
|
55
|
+
// EndDate'i güncelle
|
|
56
|
+
const newEndDate = new Date(tempEvent.startDate.getTime());
|
|
57
|
+
newEndDate.setDate(newEndDate.getDate() + daysToAdd);
|
|
58
|
+
|
|
59
|
+
// TempEvent'i güncelle ve yeni başlığı ayarla
|
|
60
|
+
setTempEvent({
|
|
61
|
+
...tempEvent,
|
|
62
|
+
endDate: newEndDate,
|
|
63
|
+
startX: startX,
|
|
64
|
+
title: `${daysToAdd} Gece`, // Gün sayısını ve "Gece" kelimesini başlığa ekle
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
console.log("Mouse X Delta:", deltaX);
|
|
68
|
+
console.log("Days to Add:", daysToAdd);
|
|
69
|
+
console.log("New End Date:", newEndDate);
|
|
70
|
+
console.log("Cell Width:", cellWidth);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
// Mouse bırakıldığında geçici event'i kaydet
|
|
79
|
+
const handleMouseUp = () => {
|
|
80
|
+
if (isCreating && tempEvent) {
|
|
81
|
+
setEvents([...events, tempEvent]); // Geçici event kalıcı hale gelir
|
|
82
|
+
setTempEvent(null); // Geçici eventi sıfırla
|
|
83
|
+
setIsCreating(false); // Event oluşturmayı kapat
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Yeni event oluşturma fonksiyonu
|
|
88
|
+
|
|
89
|
+
const handleCellClick = (resourceId, date) => {
|
|
90
|
+
const startDate = parseDate(date.fullDate);
|
|
91
|
+
const newEvent = {
|
|
92
|
+
id: Date.now(),
|
|
93
|
+
title: "1 Gece",
|
|
94
|
+
startDate,
|
|
95
|
+
endDate: new Date(startDate.getTime() + 24 * 60 * 60 * 1000),
|
|
96
|
+
resourceId,
|
|
97
|
+
color: "#ff5722",
|
|
98
|
+
};
|
|
99
|
+
setTempEvent(newEvent); // Geçici event state'e ekle
|
|
100
|
+
setIsCreating(true); // Yeni bir event oluşturuldu
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Event silme fonksiyonu
|
|
104
|
+
const handleDeleteEvent = (eventId) => {
|
|
105
|
+
setEvents(events.filter((event) => event.id !== eventId));
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (isCreating) {
|
|
111
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
112
|
+
window.addEventListener("mouseup", handleMouseUp);
|
|
113
|
+
|
|
114
|
+
return () => {
|
|
115
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
116
|
+
window.removeEventListener("mouseup", handleMouseUp);
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}, );
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
// Sürükleme sırasında seçilen hücreleri vurgulamak için fonksiyon
|
|
123
|
+
const isCellSelected = (resourceId, date) => {
|
|
124
|
+
if (!dragStart || !dragEnd) return false;
|
|
125
|
+
if (resourceId !== dragStart.resourceId) return false;
|
|
126
|
+
|
|
127
|
+
const startIndex = dates.findIndex(
|
|
128
|
+
(d) => parseDate(d.fullDate).getTime() === parseDate(dragStart.date).getTime()
|
|
129
|
+
);
|
|
130
|
+
const endIndex = dates.findIndex(
|
|
131
|
+
(d) => parseDate(d.fullDate).getTime() === parseDate(dragEnd.date).getTime()
|
|
132
|
+
);
|
|
133
|
+
const currentIndex = dates.findIndex(
|
|
134
|
+
(d) => parseDate(d.fullDate).getTime() === parseDate(date.fullDate).getTime()
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (startIndex === -1 || endIndex === -1 || currentIndex === -1) return false;
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
currentIndex >= Math.min(startIndex, endIndex) &&
|
|
141
|
+
currentIndex <= Math.max(startIndex, endIndex)
|
|
142
|
+
);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const calculatePosition = (event, dates) => {
|
|
146
|
+
const startDate = parseDate(event.startDate);
|
|
147
|
+
const endDate = parseDate(event.endDate);
|
|
148
|
+
|
|
149
|
+
const startIndex = dates.findIndex(
|
|
150
|
+
(d) => parseDate(d.fullDate).toDateString() === startDate.toDateString()
|
|
151
|
+
);
|
|
152
|
+
const endIndex = dates.findIndex(
|
|
153
|
+
(d) => parseDate(d.fullDate).toDateString() === endDate.toDateString()
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const totalDays = dates.length;
|
|
157
|
+
|
|
158
|
+
console.log("Total Days:", totalDays);
|
|
159
|
+
// Eğer başlangıç ve bitiş timeline'ın tamamen dışında ise, görünmez olarak işaretle
|
|
160
|
+
if (startIndex < 0 && endIndex < 0) {
|
|
161
|
+
// Eğer hem başlangıç hem de bitiş timeline'ın sol tarafında ise, tamamen görünmez
|
|
162
|
+
console.log("Event completely out of bounds on the left:", event.title);
|
|
163
|
+
return {
|
|
164
|
+
isVisible: false,
|
|
165
|
+
left: 0,
|
|
166
|
+
width: 0,
|
|
167
|
+
isPartialStart: false,
|
|
168
|
+
isPartialEnd: false,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (startIndex >= totalDays && endIndex >= totalDays) {
|
|
173
|
+
// Eğer hem başlangıç hem de bitiş timeline'ın sağ tarafında ise, tamamen görünmez
|
|
174
|
+
console.log("Event completely out of bounds on the right:", event.title);
|
|
175
|
+
return {
|
|
176
|
+
isVisible: false,
|
|
177
|
+
left: 0,
|
|
178
|
+
width: 0,
|
|
179
|
+
isPartialStart: false,
|
|
180
|
+
isPartialEnd: false,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
// Etkin Başlangıç ve Bitiş İndeksleri
|
|
186
|
+
const effectiveStartIndex = Math.max(startIndex, 0); // Başlangıç en az 0 olmalı
|
|
187
|
+
const effectiveEndIndex = Math.min(endIndex, totalDays - 1); // Bitiş en fazla toplam gün sayısı - 1 olmalı
|
|
188
|
+
|
|
189
|
+
// Kesik başlangıç veya bitiş kontrolü
|
|
190
|
+
const isPartialStart = startIndex < 0; // Başlangıç timeline dışında mı?
|
|
191
|
+
const isPartialEnd = endIndex >= totalDays; // Bitiş timeline dışında mı?
|
|
192
|
+
|
|
193
|
+
// Sol tarafın ekran dışında kalması durumu
|
|
194
|
+
if (isPartialStart) {
|
|
195
|
+
console.log("Event partially visible on the left:", event.title);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Sağ tarafın ekran dışında kalması durumu
|
|
199
|
+
if (isPartialEnd) {
|
|
200
|
+
console.log("Event partially visible on the right:", event.title);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Başlangıç ve bitiş pozisyonlarını ayrı ayrı hesapla
|
|
204
|
+
const leftPercentage = ((effectiveStartIndex + (isPartialStart ? 0 : 0.5)) / totalDays) * 100;
|
|
205
|
+
const rightPercentage = ((effectiveEndIndex + (isPartialEnd ? 1 : 0.5)) / totalDays) * 100;
|
|
206
|
+
|
|
207
|
+
// Genişlik hesaplama
|
|
208
|
+
const widthPercentage = rightPercentage - leftPercentage;
|
|
209
|
+
|
|
210
|
+
// Sonuçları döndür
|
|
211
|
+
return {
|
|
212
|
+
isVisible: true,
|
|
213
|
+
left: `${leftPercentage}%`,
|
|
214
|
+
width: `${widthPercentage}%`,
|
|
215
|
+
isPartialStart,
|
|
216
|
+
isPartialEnd,
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
if (isCreating) {
|
|
229
|
+
window.addEventListener("mousemove", handleMouseMove);
|
|
230
|
+
window.addEventListener("mouseup", handleMouseUp);
|
|
231
|
+
|
|
232
|
+
return () => {
|
|
233
|
+
window.removeEventListener("mousemove", handleMouseMove);
|
|
234
|
+
window.removeEventListener("mouseup", handleMouseUp);
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}, );
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
const handleCloseTooltip = () => {
|
|
243
|
+
setSelectedEvent(null);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const handleDelete = (eventId) => {
|
|
247
|
+
handleDeleteEvent(eventId);
|
|
248
|
+
handleCloseTooltip();
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
return (
|
|
252
|
+
<div
|
|
253
|
+
ref={containerRef}
|
|
254
|
+
style={{
|
|
255
|
+
display: "flex",
|
|
256
|
+
flexDirection: "column",
|
|
257
|
+
width: "100%",
|
|
258
|
+
userSelect: isDragging ? "none" : "auto",
|
|
259
|
+
position: "relative",
|
|
260
|
+
}}
|
|
261
|
+
>
|
|
262
|
+
|
|
263
|
+
{indicatorOn && <Indicator todayIndex={todayIndex} totalDays={totalDays} />}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
{groupedResources.map((group, groupIndex) => (
|
|
267
|
+
<div key={groupIndex} style={{ marginBottom: "0px" }}>
|
|
268
|
+
{/* Grup Başlığı */}
|
|
269
|
+
{resourceSettings.isGrouped && (
|
|
270
|
+
<div style={{ display: "flex", marginTop: "-0.08rem" }}>
|
|
271
|
+
{dates.map((dateObj, colIndex) => (
|
|
272
|
+
<div
|
|
273
|
+
key={`group-header-${groupIndex}-${colIndex}`}
|
|
274
|
+
style={{
|
|
275
|
+
flex: 1,
|
|
276
|
+
height: "2.58rem",
|
|
277
|
+
backgroundColor: "#4b5563",
|
|
278
|
+
border: "1px solid #ccc",
|
|
279
|
+
boxSizing: "border-box",
|
|
280
|
+
display: "flex",
|
|
281
|
+
alignItems: "center",
|
|
282
|
+
justifyContent: "center",
|
|
283
|
+
}}
|
|
284
|
+
></div>
|
|
285
|
+
))}
|
|
286
|
+
</div>
|
|
287
|
+
)}
|
|
288
|
+
|
|
289
|
+
{/* Kaynaklar */}
|
|
290
|
+
{!collapsedGroups[group.groupName] &&
|
|
291
|
+
group.resources.map((resource, rowIndex) => {
|
|
292
|
+
const resourceEvents = events.filter(
|
|
293
|
+
(event) => event.resourceId === resource.id
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
<div
|
|
298
|
+
key={resource.id}
|
|
299
|
+
style={{
|
|
300
|
+
display: "flex",
|
|
301
|
+
boxSizing: "border-box",
|
|
302
|
+
position: "relative",
|
|
303
|
+
height: "40px",
|
|
304
|
+
border: "1px solid #ccc",
|
|
305
|
+
}}
|
|
306
|
+
>
|
|
307
|
+
{/* Event'leri Render Et */}
|
|
308
|
+
{resourceEvents.map((event) => {
|
|
309
|
+
const { isVisible, left, width, isPartialStart, isPartialEnd } =
|
|
310
|
+
calculatePosition(event, dates);
|
|
311
|
+
|
|
312
|
+
if (!isVisible) return null;
|
|
313
|
+
|
|
314
|
+
return (
|
|
315
|
+
<div
|
|
316
|
+
key={event.id}
|
|
317
|
+
draggable
|
|
318
|
+
onDragStart={(e) => handleDragStart(e, event.id)} // Sürükleme başlat
|
|
319
|
+
onDragEnd={() => handleDragEnd()} // Sürükleme bitir
|
|
320
|
+
onClick={(e) => handleEventClick(event, e)} // Tıklama olayını burada tetikliyoruz
|
|
321
|
+
style={{
|
|
322
|
+
position: "absolute",
|
|
323
|
+
top: "5px",
|
|
324
|
+
left: left,
|
|
325
|
+
width: width,
|
|
326
|
+
height: "80%",
|
|
327
|
+
backgroundColor: event.color || "#ff7f50",
|
|
328
|
+
color: "#fff",
|
|
329
|
+
fontSize: "14px",
|
|
330
|
+
padding: "5px",
|
|
331
|
+
borderTopLeftRadius: isPartialStart ? "0px" : "20px",
|
|
332
|
+
borderBottomLeftRadius: isPartialStart ? "0px" : "20px",
|
|
333
|
+
borderTopRightRadius: isPartialEnd ? "0px" : "20px",
|
|
334
|
+
borderBottomRightRadius: isPartialEnd ? "0px" : "20px",
|
|
335
|
+
textAlign: "left",
|
|
336
|
+
display: "flex",
|
|
337
|
+
alignItems: "center",
|
|
338
|
+
justifyContent: "center",
|
|
339
|
+
boxSizing: "border-box",
|
|
340
|
+
zIndex: 10,
|
|
341
|
+
overflow: "hidden",
|
|
342
|
+
textOverflow: "ellipsis",
|
|
343
|
+
whiteSpace: "nowrap",
|
|
344
|
+
border: "1px solid #fff",
|
|
345
|
+
cursor: "grab", // Sürükleme ikonu
|
|
346
|
+
}}
|
|
347
|
+
>
|
|
348
|
+
{event.title}
|
|
349
|
+
</div>
|
|
350
|
+
);
|
|
351
|
+
})}
|
|
352
|
+
|
|
353
|
+
{tempEvent && tempEvent.resourceId === resource.id && (
|
|
354
|
+
<div
|
|
355
|
+
style={{
|
|
356
|
+
position: "absolute",
|
|
357
|
+
...calculatePosition(tempEvent, dates),
|
|
358
|
+
top: "5px",
|
|
359
|
+
height: "80%",
|
|
360
|
+
backgroundColor: tempEvent.color,
|
|
361
|
+
opacity: 0.7,
|
|
362
|
+
borderRadius: "20px",
|
|
363
|
+
zIndex: 9,
|
|
364
|
+
display: "flex",
|
|
365
|
+
alignItems: "center",
|
|
366
|
+
justifyContent: "center",
|
|
367
|
+
color: "#fff",
|
|
368
|
+
fontSize: "14px",
|
|
369
|
+
}}
|
|
370
|
+
>
|
|
371
|
+
{tempEvent.title}
|
|
372
|
+
</div>
|
|
373
|
+
)}
|
|
374
|
+
|
|
375
|
+
{/* Tarih Hücreleri */}
|
|
376
|
+
{dates.map((dateObj, colIndex) => (
|
|
377
|
+
<div
|
|
378
|
+
key={`cell-${groupIndex}-${rowIndex}-${colIndex}`}
|
|
379
|
+
data-date={JSON.stringify(dateObj)}
|
|
380
|
+
data-resource-id={resource.id}
|
|
381
|
+
onMouseDown={() => handleCellClick(resource.id, dateObj)}
|
|
382
|
+
onDragOver={(e) => handleDragOver(e)} // Hücre üzerine sürükleme
|
|
383
|
+
onDrop={(e) =>
|
|
384
|
+
handleDrop(e, resource.id, parseDate(dateObj.fullDate)) // `dateObj.fullDate`'yi doğru şekilde geçiyoruz
|
|
385
|
+
} // Bırakma olayı
|
|
386
|
+
style={{
|
|
387
|
+
flex: 1,
|
|
388
|
+
height: "100%",
|
|
389
|
+
position: "relative",
|
|
390
|
+
borderLeft: colIndex === 0 ? "1px solid #ccc" : "none",
|
|
391
|
+
borderRight: "1px solid #ccc",
|
|
392
|
+
backgroundColor: isCellSelected(resource.id, dateObj)
|
|
393
|
+
? "rgba(59, 130, 246, 0.3)"
|
|
394
|
+
: "transparent",
|
|
395
|
+
cursor: "pointer",
|
|
396
|
+
}}
|
|
397
|
+
></div>
|
|
398
|
+
))}
|
|
399
|
+
</div>
|
|
400
|
+
);
|
|
401
|
+
})}
|
|
402
|
+
</div>
|
|
403
|
+
))}
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
{selectedEvent && (
|
|
410
|
+
<EventTooltip
|
|
411
|
+
event={selectedEvent}
|
|
412
|
+
position={tooltipPosition}
|
|
413
|
+
onClose={handleCloseTooltip}
|
|
414
|
+
onDelete={() => handleDelete(selectedEvent.id)}
|
|
415
|
+
/>
|
|
416
|
+
)}
|
|
417
|
+
</div>
|
|
418
|
+
);
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
export default TimelineContent;
|