akfatimeline 1.0.6 → 1.2.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/CHANGELOG.md +98 -35
- package/dist/Timeline.js +4309 -1677
- package/dist/components/Timeline/AutocompleteSelect.js +150 -0
- package/dist/components/Timeline/ContextMenu.js +149 -0
- package/dist/components/Timeline/DailyView.js +255 -0
- package/dist/components/Timeline/DatePickerComponent.js +13 -0
- package/{public/dist/dist → dist}/components/Timeline/DragAndDropHandler.js +34 -34
- package/dist/components/Timeline/EventBadge.js +26 -0
- package/dist/components/Timeline/EventDetailModal.js +138 -0
- package/dist/components/Timeline/EventIcon.js +47 -0
- package/dist/{dist/components → components}/Timeline/EventTooltip.js +206 -206
- package/dist/components/Timeline/FilterPanel.js +179 -0
- package/dist/{dist/components → components}/Timeline/Indicator.js +26 -26
- package/dist/components/Timeline/LoadingSpinner.js +48 -0
- package/dist/{dist/components → components}/Timeline/MasterHeader.js +104 -68
- package/{public/dist/dist → dist}/components/Timeline/Resources.js +53 -53
- package/dist/{dist/components → components}/Timeline/ResourcesHeader.js +14 -14
- package/dist/components/Timeline/Timeline.css +2491 -0
- package/dist/components/Timeline/Timeline.js +607 -0
- package/dist/{dist/components → components}/Timeline/TimelineCell.js +8 -8
- package/dist/components/Timeline/TimelineContent.js +838 -0
- package/{public/dist/dist → dist}/components/Timeline/TimelineEvents.js +114 -114
- package/dist/components/Timeline/TimelineHeader.js +54 -0
- package/{public/dist/dist → dist}/components/Timeline/TimelineMonthContainer.js +29 -29
- package/{public/dist/dist → dist}/components/Timeline/TimelineResources.js +16 -16
- package/{public/dist/dist → dist}/hooks/useDragAndDrop.js +80 -80
- package/dist/{dist/hooks → hooks}/useEventDragDrop.js +126 -126
- package/dist/hooks/useEventManagement.js +173 -0
- package/dist/hooks/useEventSelection.js +82 -0
- package/{public/dist/dist → dist}/hooks/useExtendEvent.js +28 -28
- package/dist/hooks/useKeyboardShortcuts.js +158 -0
- package/dist/hooks/useTouchGestures.js +90 -0
- package/dist/utils/conflictUtils.js +105 -0
- package/dist/{dist/utils → utils}/dateUtils.js +36 -36
- package/dist/{dist/utils → utils}/filterTimelineData.js +20 -20
- package/dist/utils/filterUtils.js +106 -0
- package/dist/utils/timeUtils.js +179 -0
- package/dist/{dist/utils → utils}/timelineUtils.js +39 -39
- package/dist/utils/viewModeUtils.js +54 -0
- package/package.json +89 -19
- package/src/App.js +300 -19
- package/src/components/Timeline/AutocompleteSelect.js +150 -0
- package/src/components/Timeline/ContextMenu.js +149 -0
- package/src/components/Timeline/DailyView.js +255 -0
- package/src/components/Timeline/DatePickerComponent.js +13 -17
- package/src/components/Timeline/DragAndDropHandler.js +34 -34
- package/src/components/Timeline/EventBadge.js +26 -0
- package/src/components/Timeline/EventDetailModal.js +138 -0
- package/src/components/Timeline/EventIcon.js +47 -0
- package/src/components/Timeline/EventTooltip.js +206 -206
- package/src/components/Timeline/FilterPanel.js +179 -0
- package/src/components/Timeline/Indicator.js +26 -26
- package/src/components/Timeline/LoadingSpinner.js +48 -0
- package/src/components/Timeline/MasterHeader.js +104 -68
- package/src/components/Timeline/Resources.js +53 -53
- package/src/components/Timeline/ResourcesHeader.js +14 -14
- package/src/components/Timeline/Timeline.css +2491 -616
- package/src/components/Timeline/Timeline.js +607 -309
- package/src/components/Timeline/TimelineCell.js +8 -8
- package/src/components/Timeline/TimelineContent.js +838 -446
- package/src/components/Timeline/TimelineEvents.js +114 -114
- package/src/components/Timeline/TimelineHeader.js +54 -43
- package/src/components/Timeline/TimelineMonthContainer.js +29 -29
- package/src/components/Timeline/TimelineResources.js +16 -16
- package/src/demo.css +4 -0
- package/src/hooks/useDragAndDrop.js +80 -80
- package/src/hooks/useEventDragDrop.js +126 -126
- package/src/hooks/useEventManagement.js +173 -0
- package/src/hooks/useEventSelection.js +82 -0
- package/src/hooks/useExtendEvent.js +28 -28
- package/src/hooks/useKeyboardShortcuts.js +158 -0
- package/src/hooks/useTouchGestures.js +90 -0
- package/src/index.js +1 -7
- package/src/library.js +26 -0
- package/src/utils/conflictUtils.js +105 -0
- package/src/utils/dateUtils.js +36 -36
- package/src/utils/filterTimelineData.js +20 -20
- package/src/utils/filterUtils.js +106 -0
- package/src/utils/timeUtils.js +179 -0
- package/src/utils/timelineUtils.js +39 -39
- package/src/utils/viewModeUtils.js +54 -0
- package/.babelrc +0 -6
- package/babel.config.json +0 -4
- package/dist/dist/components/Timeline/DatePickerComponent.js +0 -17
- package/dist/dist/components/Timeline/DragAndDropHandler.js +0 -35
- package/dist/dist/components/Timeline/Resources.js +0 -53
- package/dist/dist/components/Timeline/Timeline.css +0 -616
- package/dist/dist/components/Timeline/Timeline.js +0 -309
- package/dist/dist/components/Timeline/TimelineContent.js +0 -446
- package/dist/dist/components/Timeline/TimelineEvents.js +0 -114
- package/dist/dist/components/Timeline/TimelineHeader.js +0 -43
- package/dist/dist/components/Timeline/TimelineMonthContainer.js +0 -29
- package/dist/dist/components/Timeline/TimelineResources.js +0 -16
- package/dist/dist/hooks/useDragAndDrop.js +0 -80
- package/dist/dist/hooks/useExtendEvent.js +0 -28
- package/public/dist/Timeline.js +0 -3277
- package/public/dist/dist/components/Timeline/DatePickerComponent.js +0 -17
- package/public/dist/dist/components/Timeline/EventTooltip.js +0 -206
- package/public/dist/dist/components/Timeline/Indicator.js +0 -29
- package/public/dist/dist/components/Timeline/MasterHeader.js +0 -68
- package/public/dist/dist/components/Timeline/ResourcesHeader.js +0 -14
- package/public/dist/dist/components/Timeline/Timeline.css +0 -616
- package/public/dist/dist/components/Timeline/Timeline.js +0 -304
- package/public/dist/dist/components/Timeline/TimelineCell.js +0 -8
- package/public/dist/dist/components/Timeline/TimelineContent.js +0 -447
- package/public/dist/dist/components/Timeline/TimelineHeader.js +0 -43
- package/public/dist/dist/hooks/useEventDragDrop.js +0 -126
- package/public/dist/dist/utils/HorizontalVirtualScroll.js +0 -0
- package/public/dist/dist/utils/dateUtils.js +0 -36
- package/public/dist/dist/utils/filterTimelineData.js +0 -21
- package/public/dist/dist/utils/timelineUtils.js +0 -40
- package/public/favicon.ico +0 -0
- package/public/index kutuphane /304/261c/304/261n.html" +0 -43
- package/public/index tasarim icin.html +0 -20
- package/public/index.html +0 -43
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +0 -25
- package/public/robots.txt +0 -3
- package/src/App.css +0 -38
- package/src/App.test.js +0 -8
- package/src/dist/Timeline.js +0 -277
- package/src/index.css +0 -13
- package/src/logo.svg +0 -1
- package/src/reportWebVitals.js +0 -13
- package/src/setupTests.js +0 -5
- package/webpack.config.js +0 -49
- /package/dist/{dist/utils → utils}/HorizontalVirtualScroll.js +0 -0
package/src/App.js
CHANGED
|
@@ -1,54 +1,170 @@
|
|
|
1
1
|
import React, { useState } from "react";
|
|
2
2
|
import Timeline from "./components/Timeline/Timeline";
|
|
3
3
|
import EventTooltip from "./components/Timeline/EventTooltip"; // Tooltip bileşenini import ediyoruz
|
|
4
|
+
import AutocompleteSelect from "./components/Timeline/AutocompleteSelect"; // AutocompleteSelect bileşenini import ediyoruz
|
|
4
5
|
|
|
5
6
|
const App = () => {
|
|
6
|
-
|
|
7
|
+
// Bugünün tarihini al ve programDate olarak kullan
|
|
8
|
+
const today = new Date();
|
|
9
|
+
const programDate = today.toISOString().split('T')[0]; // YYYY-MM-DD formatı
|
|
10
|
+
const todayDate = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
|
7
11
|
|
|
12
|
+
const [dayRange, setDayRange] = useState(30);
|
|
13
|
+
const [themeType, setThemeType] = useState("dark");
|
|
14
|
+
const [eventAlignmentMode, setEventAlignmentMode] = useState("center"); // "center" | "full"
|
|
15
|
+
const [zoomLevel, setZoomLevel] = useState(1.0); // Zoom seviyesi (1.0 = %100)
|
|
16
|
+
const [selectedResource, setSelectedResource] = useState(null); // Autocomplete için seçili değer
|
|
17
|
+
const [cellContextMenuOn, setCellContextMenuOn] = useState(true); // Cell context menu açık/kapalı
|
|
18
|
+
|
|
19
|
+
// Event Management ve Keyboard Shortcuts
|
|
20
|
+
const [eventManagementOn, setEventManagementOn] = useState(true);
|
|
21
|
+
const [keyboardShortcutsOn, setKeyboardShortcutsOn] = useState(true);
|
|
22
|
+
|
|
23
|
+
// Event Icons & Badges
|
|
24
|
+
const [eventIconsOn, setEventIconsOn] = useState(true);
|
|
25
|
+
const [eventBadgesOn, setEventBadgesOn] = useState(true);
|
|
26
|
+
|
|
27
|
+
// Loading State
|
|
28
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
29
|
+
|
|
30
|
+
// Cell Tooltip için örnek fiyat verisi
|
|
31
|
+
// Her gün için farklı fiyatlar tanımlanabilir
|
|
32
|
+
const getCellTooltipContent = (resource, dateObj) => {
|
|
33
|
+
const date = new Date(dateObj.fullDate);
|
|
34
|
+
const dateString = date.toISOString().split('T')[0]; // YYYY-MM-DD formatı
|
|
35
|
+
|
|
36
|
+
// Örnek: Her resource ve tarih için farklı fiyatlar
|
|
37
|
+
// Bu veri API'den, state'ten veya başka bir kaynaktan gelebilir
|
|
38
|
+
const priceMap = {
|
|
39
|
+
// Resource ID'ye göre fiyatlar
|
|
40
|
+
"lux-101": {
|
|
41
|
+
// Tarih bazlı fiyatlar (opsiyonel - yoksa default kullanılır)
|
|
42
|
+
"2025-12-15": 250,
|
|
43
|
+
"2025-12-16": 280,
|
|
44
|
+
"2025-12-17": 300,
|
|
45
|
+
// Varsayılan fiyatlar
|
|
46
|
+
base: 150,
|
|
47
|
+
weekend: 200,
|
|
48
|
+
},
|
|
49
|
+
"lux-102": {
|
|
50
|
+
"2025-01-15": 240,
|
|
51
|
+
"2025-01-16": 270,
|
|
52
|
+
base: 150,
|
|
53
|
+
weekend: 200,
|
|
54
|
+
},
|
|
55
|
+
"deluxe-201": {
|
|
56
|
+
"2025-01-15": 180,
|
|
57
|
+
"2025-01-16": 200,
|
|
58
|
+
base: 100,
|
|
59
|
+
weekend: 150,
|
|
60
|
+
},
|
|
61
|
+
"deluxe-202": {
|
|
62
|
+
base: 100,
|
|
63
|
+
weekend: 150,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const dayOfWeek = date.getDay();
|
|
68
|
+
const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
|
|
69
|
+
|
|
70
|
+
// Fiyat hesaplama: Önce tarih bazlı fiyat, yoksa hafta sonu/base fiyat
|
|
71
|
+
const resourcePrices = priceMap[resource.id] || { base: 80, weekend: 120 };
|
|
72
|
+
let price = resourcePrices[dateString]; // Tarih bazlı fiyat var mı?
|
|
73
|
+
|
|
74
|
+
if (price === undefined) {
|
|
75
|
+
// Tarih bazlı fiyat yoksa, hafta sonu/base fiyatı kullan
|
|
76
|
+
price = isWeekend ? resourcePrices.weekend : resourcePrices.base;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div>
|
|
81
|
+
<div style={{ fontWeight: 'bold', marginBottom: '4px' }}>
|
|
82
|
+
{resource.name}
|
|
83
|
+
</div>
|
|
84
|
+
<div style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>
|
|
85
|
+
{dateObj.display}
|
|
86
|
+
</div>
|
|
87
|
+
<div style={{ marginTop: '4px', fontSize: '14px', fontWeight: '600' }}>
|
|
88
|
+
Fiyat: {price}₺
|
|
89
|
+
</div>
|
|
90
|
+
{isWeekend && !resourcePrices[dateString] && (
|
|
91
|
+
<div style={{ fontSize: '11px', color: 'var(--text-tertiary)', marginTop: '2px' }}>
|
|
92
|
+
(Hafta Sonu)
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
95
|
+
{resourcePrices[dateString] && (
|
|
96
|
+
<div style={{ fontSize: '11px', color: 'var(--text-tertiary)', marginTop: '2px' }}>
|
|
97
|
+
(Özel Fiyat)
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Event tarihlerini bugünden itibaren oluştur
|
|
8
105
|
const events = [
|
|
9
106
|
{
|
|
10
107
|
id: "lux-101-1",
|
|
11
108
|
title: "Room 101 Cleaning",
|
|
12
|
-
startDate: new Date(
|
|
13
|
-
endDate: new Date(
|
|
109
|
+
startDate: new Date(todayDate.getTime()),
|
|
110
|
+
endDate: new Date(todayDate.getTime() + 2 * 24 * 60 * 60 * 1000), // +2 gün
|
|
14
111
|
resourceId: "lux-101",
|
|
15
112
|
status: "Completed", // Tamamlanmış
|
|
113
|
+
balanceWarning: false, // Bakiye uyarısı yok (bakiye = 0)
|
|
114
|
+
balance: 0, // Bakiye: 0 TL
|
|
115
|
+
hasImportantNote: true, // Önemli not var
|
|
116
|
+
note: "Özel temizlik talimatları: Halılar özel şampuan ile temizlenecek",
|
|
16
117
|
},
|
|
17
118
|
{
|
|
18
119
|
id: "lux-101-2",
|
|
19
120
|
title: "Room 101 Maintenance",
|
|
20
|
-
startDate: new Date(
|
|
21
|
-
endDate: new Date(
|
|
121
|
+
startDate: new Date(todayDate.getTime() + 2 * 24 * 60 * 60 * 1000), // +2 gün
|
|
122
|
+
endDate: new Date(todayDate.getTime() + 4 * 24 * 60 * 60 * 1000), // +4 gün
|
|
22
123
|
resourceId: "lux-101",
|
|
23
124
|
status: "In-progress", // Devam Ediyor
|
|
125
|
+
balanceWarning: true, // Bakiye uyarısı var (bakiye > 0)
|
|
126
|
+
balance: 1500, // Kalan bakiye: 1500 TL
|
|
127
|
+
paymentPending: true, // Ödeme bekliyor
|
|
128
|
+
isUrgent: true, // Acil
|
|
129
|
+
note: "Acil bakım gerekiyor - Klima arızası",
|
|
24
130
|
},
|
|
25
131
|
{
|
|
26
132
|
id: "lux-101-3",
|
|
27
133
|
title: "Room 101 Inspection",
|
|
28
|
-
startDate: new Date(
|
|
29
|
-
endDate: new Date(
|
|
134
|
+
startDate: new Date(todayDate.getTime() + 4 * 24 * 60 * 60 * 1000), // +4 gün
|
|
135
|
+
endDate: new Date(todayDate.getTime() + 6 * 24 * 60 * 60 * 1000), // +6 gün
|
|
30
136
|
resourceId: "lux-101",
|
|
31
137
|
status: "Cancelled", // İptal
|
|
138
|
+
balanceWarning: false, // Bakiye uyarısı yok
|
|
139
|
+
balance: 0, // Bakiye: 0 TL
|
|
140
|
+
hasImportantNote: false, // Not yok
|
|
32
141
|
},
|
|
33
142
|
{
|
|
34
143
|
id: "lux-102-1",
|
|
35
144
|
title: "Room 102 Cleaning",
|
|
36
|
-
startDate: new Date(
|
|
37
|
-
endDate: new Date(
|
|
145
|
+
startDate: new Date(todayDate.getTime() + 6 * 24 * 60 * 60 * 1000), // +6 gün
|
|
146
|
+
endDate: new Date(todayDate.getTime() + 9 * 24 * 60 * 60 * 1000), // +9 gün
|
|
38
147
|
resourceId: "lux-102",
|
|
39
148
|
status: "Completed",
|
|
149
|
+
isNew: true, // Yeni event
|
|
150
|
+
balanceWarning: false, // Bakiye uyarısı yok
|
|
151
|
+
balance: 0, // Bakiye: 0 TL
|
|
152
|
+
hasImportantNote: false, // Not yok
|
|
40
153
|
},
|
|
41
154
|
{
|
|
42
155
|
id: "lux-102-2",
|
|
43
156
|
title: "Room 102 Maintenance",
|
|
44
|
-
startDate: new Date(
|
|
45
|
-
endDate: new Date(
|
|
157
|
+
startDate: new Date(todayDate.getTime() + 9 * 24 * 60 * 60 * 1000), // +9 gün
|
|
158
|
+
endDate: new Date(todayDate.getTime() + 14 * 24 * 60 * 60 * 1000), // +14 gün
|
|
46
159
|
resourceId: "lux-102",
|
|
47
160
|
status: "In-progress",
|
|
161
|
+
isImportant: true, // Önemli event
|
|
162
|
+
balanceWarning: true, // Bakiye uyarısı var
|
|
163
|
+
balance: 2500, // Kalan bakiye: 2500 TL
|
|
164
|
+
hasImportantNote: true, // Önemli not var
|
|
165
|
+
note: "VIP müşteri - Özel istekler: Yatak odası çiçeklerle süslenecek",
|
|
48
166
|
},
|
|
49
167
|
];
|
|
50
|
-
|
|
51
|
-
|
|
52
168
|
|
|
53
169
|
const resources = [
|
|
54
170
|
{
|
|
@@ -118,10 +234,84 @@ const resources = [
|
|
|
118
234
|
};
|
|
119
235
|
}
|
|
120
236
|
};
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
237
|
+
|
|
238
|
+
// Event Icon Resolver - Event'e göre ikon tipi döndürür
|
|
239
|
+
const eventIconResolver = (event) => {
|
|
240
|
+
// Öncelik sırası: Bakiye uyarısı > Önemli not > Ödeme bekliyor > Durum
|
|
241
|
+
|
|
242
|
+
// Bakiye kontrolü - Eğer balanceWarning true ise veya balance > 0 ise
|
|
243
|
+
if (event.balanceWarning || (event.balance && event.balance > 0)) {
|
|
244
|
+
return 'balance-warning';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Önemli not kontrolü
|
|
248
|
+
if (event.hasImportantNote || event.note) {
|
|
249
|
+
return 'important-note';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Ödeme bekliyor
|
|
253
|
+
if (event.paymentPending) {
|
|
254
|
+
return 'payment-pending';
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Durum bazlı ikonlar
|
|
258
|
+
switch (event.status) {
|
|
259
|
+
case "Completed":
|
|
260
|
+
return 'completed';
|
|
261
|
+
case "In-progress":
|
|
262
|
+
return 'in-progress';
|
|
263
|
+
case "Cancelled":
|
|
264
|
+
return 'cancelled';
|
|
265
|
+
case "Pending":
|
|
266
|
+
return 'pending';
|
|
267
|
+
default:
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// Event Badge Resolver - Event'e göre badge bilgisi döndürür
|
|
273
|
+
const eventBadgeResolver = (event) => {
|
|
274
|
+
// Öncelik sırası: Acil > Önemli > Yeni > Özel badge
|
|
275
|
+
|
|
276
|
+
// Acil event için badge
|
|
277
|
+
if (event.isUrgent) {
|
|
278
|
+
return {
|
|
279
|
+
text: 'ACİL',
|
|
280
|
+
type: 'urgent',
|
|
281
|
+
position: 'top-right',
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Önemli event için badge
|
|
286
|
+
if (event.isImportant) {
|
|
287
|
+
return {
|
|
288
|
+
text: 'ÖNEMLİ',
|
|
289
|
+
type: 'important',
|
|
290
|
+
position: 'top-right',
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Yeni event için badge
|
|
295
|
+
if (event.isNew) {
|
|
296
|
+
return {
|
|
297
|
+
text: 'YENİ',
|
|
298
|
+
type: 'new',
|
|
299
|
+
position: 'top-left',
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Özel badge
|
|
304
|
+
if (event.badge) {
|
|
305
|
+
return {
|
|
306
|
+
text: event.badge.text,
|
|
307
|
+
type: event.badge.type || 'custom',
|
|
308
|
+
position: event.badge.position || 'top-right',
|
|
309
|
+
style: event.badge.style,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return null;
|
|
314
|
+
};
|
|
125
315
|
|
|
126
316
|
const toggleTheme = () => {
|
|
127
317
|
setThemeType((prev) => (prev === "light" ? "dark" : "light"));
|
|
@@ -166,7 +356,25 @@ const resources = [
|
|
|
166
356
|
<button onClick={toggleTheme}>
|
|
167
357
|
Temayı Değiştir ({themeType === "dark" ? "Karanlık" : "Aydınlık"})
|
|
168
358
|
</button>
|
|
169
|
-
<div style={{ margin: "20px 0" }}>
|
|
359
|
+
<div style={{ margin: "20px 0", display: "flex", gap: "10px", flexWrap: "wrap" }}>
|
|
360
|
+
<button onClick={() => setEventAlignmentMode(eventAlignmentMode === "center" ? "full" : "center")}>
|
|
361
|
+
Hizalama: {eventAlignmentMode === "center" ? "Gün Ortası" : "Tam Gün"}
|
|
362
|
+
</button>
|
|
363
|
+
<button onClick={() => setEventManagementOn(!eventManagementOn)}>
|
|
364
|
+
Event Yönetimi: {eventManagementOn ? "Açık" : "Kapalı"}
|
|
365
|
+
</button>
|
|
366
|
+
<button onClick={() => setKeyboardShortcutsOn(!keyboardShortcutsOn)}>
|
|
367
|
+
Klavye Kısayolları: {keyboardShortcutsOn ? "Açık" : "Kapalı"}
|
|
368
|
+
</button>
|
|
369
|
+
<button onClick={() => setEventIconsOn(!eventIconsOn)}>
|
|
370
|
+
Event İkonları: {eventIconsOn ? "Açık" : "Kapalı"}
|
|
371
|
+
</button>
|
|
372
|
+
<button onClick={() => setEventBadgesOn(!eventBadgesOn)}>
|
|
373
|
+
Event Badge'leri: {eventBadgesOn ? "Açık" : "Kapalı"}
|
|
374
|
+
</button>
|
|
375
|
+
<button onClick={() => setIsLoading(!isLoading)}>
|
|
376
|
+
Loading: {isLoading ? "Açık" : "Kapalı"}
|
|
377
|
+
</button>
|
|
170
378
|
<button onClick={handleToday}>Bugüne Git</button>
|
|
171
379
|
<button onClick={handleRetreat}>5 Gün Geri</button>
|
|
172
380
|
<button onClick={handleAdvance}>5 Gün İleri</button>
|
|
@@ -183,6 +391,12 @@ const resources = [
|
|
|
183
391
|
dayRange={dayRange}
|
|
184
392
|
setDayRange={setDayRange}
|
|
185
393
|
themeType={themeType} // Tema türü prop olarak geçiliyor
|
|
394
|
+
zoomLevel={zoomLevel}
|
|
395
|
+
setZoomLevel={setZoomLevel}
|
|
396
|
+
zoomOn={true}
|
|
397
|
+
minZoomLevel={0.5}
|
|
398
|
+
maxZoomLevel={3.0}
|
|
399
|
+
zoomStep={0.25}
|
|
186
400
|
onToday={handleToday}
|
|
187
401
|
onAdvance={handleAdvance}
|
|
188
402
|
onRetreat={handleRetreat}
|
|
@@ -200,7 +414,74 @@ const resources = [
|
|
|
200
414
|
setDropInfo={handleDropInfo} // Callback'i buradan bağlıyoruz
|
|
201
415
|
onExtendInfo={handleExtendInfo} // Uzatma bilgisi
|
|
202
416
|
onCreateEventInfo={handleCreateEventInfo} // Yeni etkinlik bilgisi
|
|
203
|
-
indicatorDate=
|
|
417
|
+
indicatorDate={programDate} // Bugünün tarihi
|
|
418
|
+
eventAlignmentMode={eventAlignmentMode}
|
|
419
|
+
preventPastEvents={true} // Geçmiş tarihlere rezervasyon oluşturmayı engelle
|
|
420
|
+
highlightWeekends={true} // Hafta sonlarını farklı renkte göster
|
|
421
|
+
cellTooltipOn={true} // Cell tooltip'lerini aktif et
|
|
422
|
+
cellTooltipResolver={getCellTooltipContent} // Fiyat bilgisi göster
|
|
423
|
+
eventIconsOn={eventIconsOn} // Event ikonlarını aktif et
|
|
424
|
+
eventIconResolver={eventIconResolver} // İkon resolver
|
|
425
|
+
eventBadgesOn={eventBadgesOn} // Event badge'lerini aktif et
|
|
426
|
+
eventBadgeResolver={eventBadgeResolver} // Badge resolver
|
|
427
|
+
isLoading={isLoading} // Loading state
|
|
428
|
+
loadingType="spinner" // Loading tipi
|
|
429
|
+
cellContextMenuOn={cellContextMenuOn} // Cell context menu aktif
|
|
430
|
+
cellContextMenuItems={[
|
|
431
|
+
{
|
|
432
|
+
id: 'daily-timeline',
|
|
433
|
+
label: 'Günlük Timeline Görüntüsü Oluştur',
|
|
434
|
+
icon: '📊',
|
|
435
|
+
onClick: (resource, date) => {
|
|
436
|
+
// Timeline.js'te handle edilecek
|
|
437
|
+
console.log('Daily Timeline:', { resource, date });
|
|
438
|
+
},
|
|
439
|
+
tooltip: 'Seçili resource ve tarih için günlük timeline görüntüsü oluştur',
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
id: 'separator-1',
|
|
443
|
+
separator: true,
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
id: 'view-details',
|
|
447
|
+
label: 'Detayları Görüntüle',
|
|
448
|
+
icon: '👁️',
|
|
449
|
+
onClick: (resource, date) => {
|
|
450
|
+
console.log('View Details:', { resource, date });
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
id: 'create-event',
|
|
455
|
+
label: 'Yeni Rezervasyon Oluştur',
|
|
456
|
+
icon: '➕',
|
|
457
|
+
onClick: (resource, date) => {
|
|
458
|
+
const dateObj = new Date(date.fullDate);
|
|
459
|
+
alert(`"${resource.name || resource.id}" için ${dateObj.toLocaleDateString('tr-TR')} tarihinde yeni rezervasyon oluşturulacak.`);
|
|
460
|
+
},
|
|
461
|
+
},
|
|
462
|
+
]}
|
|
463
|
+
onCellContextMenu={(resource, date, event) => {
|
|
464
|
+
console.log('Context menu opened:', { resource, date, event });
|
|
465
|
+
}}
|
|
466
|
+
resourceHeaderContent={
|
|
467
|
+
<AutocompleteSelect
|
|
468
|
+
options={[
|
|
469
|
+
{ id: 1, name: "Luxury Rooms", group: "Luxury" },
|
|
470
|
+
{ id: 2, name: "Deluxe Rooms", group: "Deluxe" },
|
|
471
|
+
{ id: 3, name: "Standard Rooms", group: "Standard" },
|
|
472
|
+
{ id: 4, name: "Suite Rooms", group: "Suite" },
|
|
473
|
+
{ id: 5, name: "Penthouse", group: "Luxury" },
|
|
474
|
+
]}
|
|
475
|
+
value={selectedResource}
|
|
476
|
+
onChange={(value, option) => {
|
|
477
|
+
setSelectedResource(value);
|
|
478
|
+
console.log("Seçilen:", option);
|
|
479
|
+
}}
|
|
480
|
+
placeholder="Oda grubu seçiniz..."
|
|
481
|
+
getOptionLabel={(option) => option.name}
|
|
482
|
+
getOptionValue={(option) => option.id}
|
|
483
|
+
/>
|
|
484
|
+
}
|
|
204
485
|
|
|
205
486
|
/>
|
|
206
487
|
</div>
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect } from "react";
|
|
2
|
+
import "./Timeline.css";
|
|
3
|
+
|
|
4
|
+
const AutocompleteSelect = ({
|
|
5
|
+
options = [],
|
|
6
|
+
value = null,
|
|
7
|
+
onChange = () => {},
|
|
8
|
+
placeholder = "Seçiniz...",
|
|
9
|
+
getOptionLabel = (option) => option?.label || option?.name || String(option),
|
|
10
|
+
getOptionValue = (option) => option?.value || option?.id || option,
|
|
11
|
+
filterOptions = (options, inputValue) => {
|
|
12
|
+
if (!inputValue) return options;
|
|
13
|
+
const lowerInput = inputValue.toLowerCase();
|
|
14
|
+
return options.filter((option) => {
|
|
15
|
+
const label = getOptionLabel(option).toLowerCase();
|
|
16
|
+
return label.includes(lowerInput);
|
|
17
|
+
});
|
|
18
|
+
},
|
|
19
|
+
}) => {
|
|
20
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
21
|
+
const [inputValue, setInputValue] = useState("");
|
|
22
|
+
const [filteredOptions, setFilteredOptions] = useState(options);
|
|
23
|
+
const containerRef = useRef(null);
|
|
24
|
+
const inputRef = useRef(null);
|
|
25
|
+
|
|
26
|
+
// Seçili değerin label'ını bul
|
|
27
|
+
const selectedOption = options.find(
|
|
28
|
+
(opt) => getOptionValue(opt) === value
|
|
29
|
+
);
|
|
30
|
+
const displayValue = selectedOption
|
|
31
|
+
? getOptionLabel(selectedOption)
|
|
32
|
+
: inputValue || placeholder;
|
|
33
|
+
|
|
34
|
+
// Input değiştiğinde filtrele
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (isOpen) {
|
|
37
|
+
const filtered = filterOptions(options, inputValue);
|
|
38
|
+
setFilteredOptions(filtered);
|
|
39
|
+
} else {
|
|
40
|
+
setFilteredOptions(options);
|
|
41
|
+
}
|
|
42
|
+
}, [inputValue, isOpen, options, filterOptions]);
|
|
43
|
+
|
|
44
|
+
// Dışarı tıklandığında kapat
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const handleClickOutside = (event) => {
|
|
47
|
+
if (containerRef.current && !containerRef.current.contains(event.target)) {
|
|
48
|
+
setIsOpen(false);
|
|
49
|
+
setInputValue("");
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
if (isOpen) {
|
|
54
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
55
|
+
return () => {
|
|
56
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}, [isOpen]);
|
|
60
|
+
|
|
61
|
+
const handleInputChange = (e) => {
|
|
62
|
+
const newValue = e.target.value;
|
|
63
|
+
setInputValue(newValue);
|
|
64
|
+
setIsOpen(true);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const handleSelect = (option) => {
|
|
68
|
+
const optionValue = getOptionValue(option);
|
|
69
|
+
onChange(optionValue, option);
|
|
70
|
+
setInputValue("");
|
|
71
|
+
setIsOpen(false);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const handleFocus = () => {
|
|
75
|
+
setIsOpen(true);
|
|
76
|
+
if (selectedOption) {
|
|
77
|
+
setInputValue(getOptionLabel(selectedOption));
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const handleBlur = () => {
|
|
82
|
+
// Input blur olduğunda hemen kapatma, click outside ile kapatılacak
|
|
83
|
+
setTimeout(() => {
|
|
84
|
+
if (!containerRef.current?.contains(document.activeElement)) {
|
|
85
|
+
setIsOpen(false);
|
|
86
|
+
if (selectedOption) {
|
|
87
|
+
setInputValue("");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}, 200);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div className="autocomplete-select-container" ref={containerRef}>
|
|
95
|
+
<div
|
|
96
|
+
className={`autocomplete-select-input ${isOpen ? "open" : ""}`}
|
|
97
|
+
onClick={() => {
|
|
98
|
+
setIsOpen(!isOpen);
|
|
99
|
+
inputRef.current?.focus();
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
<input
|
|
103
|
+
ref={inputRef}
|
|
104
|
+
type="text"
|
|
105
|
+
value={isOpen ? inputValue : displayValue}
|
|
106
|
+
onChange={handleInputChange}
|
|
107
|
+
onFocus={handleFocus}
|
|
108
|
+
onBlur={handleBlur}
|
|
109
|
+
placeholder={placeholder}
|
|
110
|
+
className="autocomplete-select-input-field"
|
|
111
|
+
/>
|
|
112
|
+
<span className="autocomplete-select-arrow">
|
|
113
|
+
{isOpen ? "▲" : "▼"}
|
|
114
|
+
</span>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
{isOpen && (
|
|
118
|
+
<div className="autocomplete-select-dropdown">
|
|
119
|
+
{filteredOptions.length > 0 ? (
|
|
120
|
+
filteredOptions.map((option, index) => {
|
|
121
|
+
const optionValue = getOptionValue(option);
|
|
122
|
+
const optionLabel = getOptionLabel(option);
|
|
123
|
+
const isSelected = optionValue === value;
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div
|
|
127
|
+
key={index}
|
|
128
|
+
className={`autocomplete-select-option ${
|
|
129
|
+
isSelected ? "selected" : ""
|
|
130
|
+
}`}
|
|
131
|
+
onClick={() => handleSelect(option)}
|
|
132
|
+
onMouseDown={(e) => e.preventDefault()} // Blur'u engelle
|
|
133
|
+
>
|
|
134
|
+
{optionLabel}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
})
|
|
138
|
+
) : (
|
|
139
|
+
<div className="autocomplete-select-no-results">
|
|
140
|
+
Sonuç bulunamadı
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export default AutocompleteSelect;
|
|
150
|
+
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import './Timeline.css';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Context Menu Component
|
|
6
|
+
* Sağ tık menüsü için özelleştirilebilir menü bileşeni
|
|
7
|
+
*/
|
|
8
|
+
const ContextMenu = ({
|
|
9
|
+
isOpen,
|
|
10
|
+
position,
|
|
11
|
+
onClose,
|
|
12
|
+
menuItems = [],
|
|
13
|
+
resource = null,
|
|
14
|
+
date = null,
|
|
15
|
+
}) => {
|
|
16
|
+
const menuRef = useRef(null);
|
|
17
|
+
const [adjustedPosition, setAdjustedPosition] = useState(position);
|
|
18
|
+
|
|
19
|
+
// Menü pozisyonunu mouse'a yakın tut ve ekran sınırları içinde tut
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!menuRef.current || !position || !isOpen) {
|
|
22
|
+
setAdjustedPosition(position);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Menü render edildikten sonra pozisyonu ayarla
|
|
27
|
+
const updatePosition = () => {
|
|
28
|
+
const menuRect = menuRef.current.getBoundingClientRect();
|
|
29
|
+
const viewportWidth = window.innerWidth;
|
|
30
|
+
const viewportHeight = window.innerHeight;
|
|
31
|
+
|
|
32
|
+
let adjustedX = position.x;
|
|
33
|
+
let adjustedY = position.y;
|
|
34
|
+
|
|
35
|
+
// Sağa taşma kontrolü
|
|
36
|
+
if (position.x + menuRect.width > viewportWidth) {
|
|
37
|
+
adjustedX = position.x - menuRect.width;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Aşağıya taşma kontrolü
|
|
41
|
+
if (position.y + menuRect.height > viewportHeight) {
|
|
42
|
+
adjustedY = position.y - menuRect.height;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Sola taşma kontrolü
|
|
46
|
+
if (adjustedX < 10) {
|
|
47
|
+
adjustedX = 10;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Yukarıya taşma kontrolü
|
|
51
|
+
if (adjustedY < 10) {
|
|
52
|
+
adjustedY = 10;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
setAdjustedPosition({ x: adjustedX, y: adjustedY });
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Menü render edildikten sonra pozisyonu güncelle
|
|
59
|
+
setTimeout(updatePosition, 0);
|
|
60
|
+
}, [position, isOpen]);
|
|
61
|
+
|
|
62
|
+
// Menü dışına tıklanınca kapat
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
const handleClickOutside = (event) => {
|
|
65
|
+
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
|
66
|
+
onClose();
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const handleEscape = (event) => {
|
|
71
|
+
if (event.key === 'Escape') {
|
|
72
|
+
onClose();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (isOpen) {
|
|
77
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
78
|
+
document.addEventListener('keydown', handleEscape);
|
|
79
|
+
// Scroll olduğunda menüyü kapat
|
|
80
|
+
document.addEventListener('scroll', onClose, true);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return () => {
|
|
84
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
85
|
+
document.removeEventListener('keydown', handleEscape);
|
|
86
|
+
document.removeEventListener('scroll', onClose, true);
|
|
87
|
+
};
|
|
88
|
+
}, [isOpen, onClose]);
|
|
89
|
+
|
|
90
|
+
if (!isOpen || !position) return null;
|
|
91
|
+
|
|
92
|
+
const handleItemClick = (item) => {
|
|
93
|
+
if (item.onClick) {
|
|
94
|
+
item.onClick(resource, date);
|
|
95
|
+
}
|
|
96
|
+
if (item.closeOnClick !== false) {
|
|
97
|
+
onClose();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div
|
|
103
|
+
ref={menuRef}
|
|
104
|
+
className="context-menu"
|
|
105
|
+
style={{
|
|
106
|
+
position: 'fixed',
|
|
107
|
+
left: `${(adjustedPosition?.x ?? position.x) - 150}px`,
|
|
108
|
+
top: `${(adjustedPosition?.y ?? position.y) - 150}px`,
|
|
109
|
+
zIndex: 10005,
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
<div className="context-menu-content">
|
|
113
|
+
{menuItems.length === 0 ? (
|
|
114
|
+
<div className="context-menu-item context-menu-item-disabled">
|
|
115
|
+
Menü öğesi yok
|
|
116
|
+
</div>
|
|
117
|
+
) : (
|
|
118
|
+
menuItems.map((item, index) => {
|
|
119
|
+
if (item.separator) {
|
|
120
|
+
return <div key={`separator-${index}`} className="context-menu-separator" />;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (item.hidden) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div
|
|
129
|
+
key={item.id || index}
|
|
130
|
+
className={`context-menu-item ${item.disabled ? 'context-menu-item-disabled' : ''} ${item.danger ? 'context-menu-item-danger' : ''}`}
|
|
131
|
+
onClick={() => !item.disabled && handleItemClick(item)}
|
|
132
|
+
title={item.tooltip || item.label}
|
|
133
|
+
>
|
|
134
|
+
{item.icon && <span className="context-menu-item-icon">{item.icon}</span>}
|
|
135
|
+
<span className="context-menu-item-label">{item.label}</span>
|
|
136
|
+
{item.shortcut && (
|
|
137
|
+
<span className="context-menu-item-shortcut">{item.shortcut}</span>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
})
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export default ContextMenu;
|
|
149
|
+
|