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.
Files changed (128) hide show
  1. package/CHANGELOG.md +98 -35
  2. package/dist/Timeline.js +4309 -1677
  3. package/dist/components/Timeline/AutocompleteSelect.js +150 -0
  4. package/dist/components/Timeline/ContextMenu.js +149 -0
  5. package/dist/components/Timeline/DailyView.js +255 -0
  6. package/dist/components/Timeline/DatePickerComponent.js +13 -0
  7. package/{public/dist/dist → dist}/components/Timeline/DragAndDropHandler.js +34 -34
  8. package/dist/components/Timeline/EventBadge.js +26 -0
  9. package/dist/components/Timeline/EventDetailModal.js +138 -0
  10. package/dist/components/Timeline/EventIcon.js +47 -0
  11. package/dist/{dist/components → components}/Timeline/EventTooltip.js +206 -206
  12. package/dist/components/Timeline/FilterPanel.js +179 -0
  13. package/dist/{dist/components → components}/Timeline/Indicator.js +26 -26
  14. package/dist/components/Timeline/LoadingSpinner.js +48 -0
  15. package/dist/{dist/components → components}/Timeline/MasterHeader.js +104 -68
  16. package/{public/dist/dist → dist}/components/Timeline/Resources.js +53 -53
  17. package/dist/{dist/components → components}/Timeline/ResourcesHeader.js +14 -14
  18. package/dist/components/Timeline/Timeline.css +2491 -0
  19. package/dist/components/Timeline/Timeline.js +607 -0
  20. package/dist/{dist/components → components}/Timeline/TimelineCell.js +8 -8
  21. package/dist/components/Timeline/TimelineContent.js +838 -0
  22. package/{public/dist/dist → dist}/components/Timeline/TimelineEvents.js +114 -114
  23. package/dist/components/Timeline/TimelineHeader.js +54 -0
  24. package/{public/dist/dist → dist}/components/Timeline/TimelineMonthContainer.js +29 -29
  25. package/{public/dist/dist → dist}/components/Timeline/TimelineResources.js +16 -16
  26. package/{public/dist/dist → dist}/hooks/useDragAndDrop.js +80 -80
  27. package/dist/{dist/hooks → hooks}/useEventDragDrop.js +126 -126
  28. package/dist/hooks/useEventManagement.js +173 -0
  29. package/dist/hooks/useEventSelection.js +82 -0
  30. package/{public/dist/dist → dist}/hooks/useExtendEvent.js +28 -28
  31. package/dist/hooks/useKeyboardShortcuts.js +158 -0
  32. package/dist/hooks/useTouchGestures.js +90 -0
  33. package/dist/utils/conflictUtils.js +105 -0
  34. package/dist/{dist/utils → utils}/dateUtils.js +36 -36
  35. package/dist/{dist/utils → utils}/filterTimelineData.js +20 -20
  36. package/dist/utils/filterUtils.js +106 -0
  37. package/dist/utils/timeUtils.js +179 -0
  38. package/dist/{dist/utils → utils}/timelineUtils.js +39 -39
  39. package/dist/utils/viewModeUtils.js +54 -0
  40. package/package.json +89 -19
  41. package/src/App.js +300 -19
  42. package/src/components/Timeline/AutocompleteSelect.js +150 -0
  43. package/src/components/Timeline/ContextMenu.js +149 -0
  44. package/src/components/Timeline/DailyView.js +255 -0
  45. package/src/components/Timeline/DatePickerComponent.js +13 -17
  46. package/src/components/Timeline/DragAndDropHandler.js +34 -34
  47. package/src/components/Timeline/EventBadge.js +26 -0
  48. package/src/components/Timeline/EventDetailModal.js +138 -0
  49. package/src/components/Timeline/EventIcon.js +47 -0
  50. package/src/components/Timeline/EventTooltip.js +206 -206
  51. package/src/components/Timeline/FilterPanel.js +179 -0
  52. package/src/components/Timeline/Indicator.js +26 -26
  53. package/src/components/Timeline/LoadingSpinner.js +48 -0
  54. package/src/components/Timeline/MasterHeader.js +104 -68
  55. package/src/components/Timeline/Resources.js +53 -53
  56. package/src/components/Timeline/ResourcesHeader.js +14 -14
  57. package/src/components/Timeline/Timeline.css +2491 -616
  58. package/src/components/Timeline/Timeline.js +607 -309
  59. package/src/components/Timeline/TimelineCell.js +8 -8
  60. package/src/components/Timeline/TimelineContent.js +838 -446
  61. package/src/components/Timeline/TimelineEvents.js +114 -114
  62. package/src/components/Timeline/TimelineHeader.js +54 -43
  63. package/src/components/Timeline/TimelineMonthContainer.js +29 -29
  64. package/src/components/Timeline/TimelineResources.js +16 -16
  65. package/src/demo.css +4 -0
  66. package/src/hooks/useDragAndDrop.js +80 -80
  67. package/src/hooks/useEventDragDrop.js +126 -126
  68. package/src/hooks/useEventManagement.js +173 -0
  69. package/src/hooks/useEventSelection.js +82 -0
  70. package/src/hooks/useExtendEvent.js +28 -28
  71. package/src/hooks/useKeyboardShortcuts.js +158 -0
  72. package/src/hooks/useTouchGestures.js +90 -0
  73. package/src/index.js +1 -7
  74. package/src/library.js +26 -0
  75. package/src/utils/conflictUtils.js +105 -0
  76. package/src/utils/dateUtils.js +36 -36
  77. package/src/utils/filterTimelineData.js +20 -20
  78. package/src/utils/filterUtils.js +106 -0
  79. package/src/utils/timeUtils.js +179 -0
  80. package/src/utils/timelineUtils.js +39 -39
  81. package/src/utils/viewModeUtils.js +54 -0
  82. package/.babelrc +0 -6
  83. package/babel.config.json +0 -4
  84. package/dist/dist/components/Timeline/DatePickerComponent.js +0 -17
  85. package/dist/dist/components/Timeline/DragAndDropHandler.js +0 -35
  86. package/dist/dist/components/Timeline/Resources.js +0 -53
  87. package/dist/dist/components/Timeline/Timeline.css +0 -616
  88. package/dist/dist/components/Timeline/Timeline.js +0 -309
  89. package/dist/dist/components/Timeline/TimelineContent.js +0 -446
  90. package/dist/dist/components/Timeline/TimelineEvents.js +0 -114
  91. package/dist/dist/components/Timeline/TimelineHeader.js +0 -43
  92. package/dist/dist/components/Timeline/TimelineMonthContainer.js +0 -29
  93. package/dist/dist/components/Timeline/TimelineResources.js +0 -16
  94. package/dist/dist/hooks/useDragAndDrop.js +0 -80
  95. package/dist/dist/hooks/useExtendEvent.js +0 -28
  96. package/public/dist/Timeline.js +0 -3277
  97. package/public/dist/dist/components/Timeline/DatePickerComponent.js +0 -17
  98. package/public/dist/dist/components/Timeline/EventTooltip.js +0 -206
  99. package/public/dist/dist/components/Timeline/Indicator.js +0 -29
  100. package/public/dist/dist/components/Timeline/MasterHeader.js +0 -68
  101. package/public/dist/dist/components/Timeline/ResourcesHeader.js +0 -14
  102. package/public/dist/dist/components/Timeline/Timeline.css +0 -616
  103. package/public/dist/dist/components/Timeline/Timeline.js +0 -304
  104. package/public/dist/dist/components/Timeline/TimelineCell.js +0 -8
  105. package/public/dist/dist/components/Timeline/TimelineContent.js +0 -447
  106. package/public/dist/dist/components/Timeline/TimelineHeader.js +0 -43
  107. package/public/dist/dist/hooks/useEventDragDrop.js +0 -126
  108. package/public/dist/dist/utils/HorizontalVirtualScroll.js +0 -0
  109. package/public/dist/dist/utils/dateUtils.js +0 -36
  110. package/public/dist/dist/utils/filterTimelineData.js +0 -21
  111. package/public/dist/dist/utils/timelineUtils.js +0 -40
  112. package/public/favicon.ico +0 -0
  113. package/public/index kutuphane /304/261c/304/261n.html" +0 -43
  114. package/public/index tasarim icin.html +0 -20
  115. package/public/index.html +0 -43
  116. package/public/logo192.png +0 -0
  117. package/public/logo512.png +0 -0
  118. package/public/manifest.json +0 -25
  119. package/public/robots.txt +0 -3
  120. package/src/App.css +0 -38
  121. package/src/App.test.js +0 -8
  122. package/src/dist/Timeline.js +0 -277
  123. package/src/index.css +0 -13
  124. package/src/logo.svg +0 -1
  125. package/src/reportWebVitals.js +0 -13
  126. package/src/setupTests.js +0 -5
  127. package/webpack.config.js +0 -49
  128. /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
- const programDate = "2025-01-08";
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("2025-01-01"),
13
- endDate: new Date("2025-01-03"),
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("2025-01-03"),
21
- endDate: new Date("2025-01-05"),
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("2025-01-05"),
29
- endDate: new Date("2025-01-07"),
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("2025-01-07"),
37
- endDate: new Date("2025-01-10"),
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("2025-01-10"),
45
- endDate: new Date("2025-01-15"),
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 [dayRange, setDayRange] = useState(30);
124
- const [themeType, setThemeType] = useState("dark");
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="2025-01-09" // İstediğiniz tarihi gönderin
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
+