akfatimeline 1.2.0 → 2.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/dist/Timeline.js +16 -48
- package/{src/components/Timeline/DailyView.js → dist/components/Timeline/DailyView.jsx} +1 -0
- package/{src/components/Timeline/EventTooltip.js → dist/components/Timeline/EventTooltip.jsx} +207 -206
- package/{src/components/Timeline/Indicator.js → dist/components/Timeline/Indicator.jsx} +27 -26
- package/{src/components/Timeline/MasterHeader.js → dist/components/Timeline/MasterHeader.jsx} +105 -104
- package/{src/components/Timeline/Resources.js → dist/components/Timeline/Resources.jsx} +54 -53
- package/{src/components/Timeline/ResourcesHeader.js → dist/components/Timeline/ResourcesHeader.jsx} +15 -14
- package/{src/components/Timeline/Timeline.js → dist/components/Timeline/Timeline.jsx} +572 -607
- package/{src/components/Timeline/TimelineContent.js → dist/components/Timeline/TimelineContent.jsx} +837 -838
- package/{src/components/Timeline/TimelineHeader.js → dist/components/Timeline/TimelineHeader.jsx} +55 -54
- package/dist/components/Timeline/TimelineMonthContainer.js +2 -2
- package/package.json +25 -25
- package/src/components/Timeline/AutocompleteSelect.jsx +150 -0
- package/src/components/Timeline/ContextMenu.jsx +149 -0
- package/src/components/Timeline/DailyView.jsx +256 -0
- package/src/components/Timeline/EventBadge.jsx +26 -0
- package/src/components/Timeline/EventDetailModal.jsx +138 -0
- package/src/components/Timeline/EventIcon.jsx +47 -0
- package/src/components/Timeline/EventTooltip.jsx +207 -0
- package/src/components/Timeline/Indicator.jsx +27 -0
- package/src/components/Timeline/LoadingSpinner.jsx +48 -0
- package/src/components/Timeline/MasterHeader.jsx +105 -0
- package/src/components/Timeline/Resources.jsx +54 -0
- package/src/components/Timeline/ResourcesHeader.jsx +15 -0
- package/src/components/Timeline/Timeline.jsx +572 -0
- package/src/components/Timeline/TimelineContent.jsx +837 -0
- package/src/components/Timeline/TimelineHeader.jsx +55 -0
- package/src/components/Timeline/TimelineMonthContainer.js +2 -2
- package/src/library.js +8 -8
- /package/{src/components/Timeline/AutocompleteSelect.js → dist/components/Timeline/AutocompleteSelect.jsx} +0 -0
- /package/{src/components/Timeline/ContextMenu.js → dist/components/Timeline/ContextMenu.jsx} +0 -0
- /package/{src/components/Timeline/EventBadge.js → dist/components/Timeline/EventBadge.jsx} +0 -0
- /package/{src/components/Timeline/EventDetailModal.js → dist/components/Timeline/EventDetailModal.jsx} +0 -0
- /package/{src/components/Timeline/EventIcon.js → dist/components/Timeline/EventIcon.jsx} +0 -0
- /package/{src/components/Timeline/LoadingSpinner.js → dist/components/Timeline/LoadingSpinner.jsx} +0 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
|
2
|
+
import './Timeline.css';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Daily View Component
|
|
6
|
+
* Seçili resource ve tarih için günlük saat bazlı timeline görünümü
|
|
7
|
+
*/
|
|
8
|
+
const DailyView = ({
|
|
9
|
+
isOpen,
|
|
10
|
+
onClose,
|
|
11
|
+
resource,
|
|
12
|
+
date,
|
|
13
|
+
events = [],
|
|
14
|
+
onEventCreate,
|
|
15
|
+
onEventUpdate,
|
|
16
|
+
onEventDelete,
|
|
17
|
+
themeType = 'light',
|
|
18
|
+
}) => {
|
|
19
|
+
const [isCreating, setIsCreating] = useState(false);
|
|
20
|
+
const [tempEvent, setTempEvent] = useState(null);
|
|
21
|
+
const containerRef = useRef(null);
|
|
22
|
+
const timelineRef = useRef(null);
|
|
23
|
+
|
|
24
|
+
// Saatleri oluştur (0-23)
|
|
25
|
+
const hours = Array.from({ length: 24 }, (_, i) => i);
|
|
26
|
+
|
|
27
|
+
// O güne ait event'leri filtrele
|
|
28
|
+
const dayEvents = events.filter(event => {
|
|
29
|
+
if (!event.startDate || !date) return false;
|
|
30
|
+
const eventDate = new Date(event.startDate);
|
|
31
|
+
const selectedDate = new Date(date.fullDate || date);
|
|
32
|
+
return (
|
|
33
|
+
eventDate.getFullYear() === selectedDate.getFullYear() &&
|
|
34
|
+
eventDate.getMonth() === selectedDate.getMonth() &&
|
|
35
|
+
eventDate.getDate() === selectedDate.getDate() &&
|
|
36
|
+
event.resourceId === resource?.id
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Saat bazlı event pozisyonu hesapla
|
|
41
|
+
const getEventPosition = (event) => {
|
|
42
|
+
const startDate = new Date(event.startDate);
|
|
43
|
+
const endDate = new Date(event.endDate);
|
|
44
|
+
const startHour = startDate.getHours() + startDate.getMinutes() / 60;
|
|
45
|
+
const endHour = endDate.getHours() + endDate.getMinutes() / 60;
|
|
46
|
+
const duration = endHour - startHour;
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
top: `${(startHour / 24) * 100}%`,
|
|
50
|
+
height: `${(duration / 24) * 100}%`,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Timeline'a tıklandığında
|
|
55
|
+
const handleTimelineClick = (e) => {
|
|
56
|
+
if (!onEventCreate || !timelineRef.current) return;
|
|
57
|
+
|
|
58
|
+
const rect = timelineRef.current.getBoundingClientRect();
|
|
59
|
+
const clickedY = e.clientY - rect.top;
|
|
60
|
+
const timelineHeight = rect.height;
|
|
61
|
+
const totalMinutes = Math.max(0, Math.min((clickedY / timelineHeight) * (24 * 60), 24 * 60));
|
|
62
|
+
|
|
63
|
+
const startHour = Math.floor(totalMinutes / 60);
|
|
64
|
+
const startMinutes = Math.floor(totalMinutes % 60);
|
|
65
|
+
|
|
66
|
+
const startDate = new Date(date.fullDate || date);
|
|
67
|
+
startDate.setHours(startHour, startMinutes, 0, 0);
|
|
68
|
+
|
|
69
|
+
const endDate = new Date(startDate);
|
|
70
|
+
endDate.setTime(startDate.getTime() + 30 * 60 * 1000); // Varsayılan 30 dakika
|
|
71
|
+
|
|
72
|
+
setIsCreating(true);
|
|
73
|
+
setTempEvent({
|
|
74
|
+
startDate,
|
|
75
|
+
endDate,
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Mouse hareketi ile event oluşturma
|
|
80
|
+
const handleMouseMove = useCallback((e) => {
|
|
81
|
+
if (!isCreating || !tempEvent || !timelineRef.current) return;
|
|
82
|
+
|
|
83
|
+
const rect = timelineRef.current.getBoundingClientRect();
|
|
84
|
+
const mouseY = e.clientY - rect.top;
|
|
85
|
+
const timelineHeight = rect.height;
|
|
86
|
+
const totalMinutes = Math.max(0, Math.min((mouseY / timelineHeight) * (24 * 60), 24 * 60));
|
|
87
|
+
|
|
88
|
+
const endHour = Math.floor(totalMinutes / 60);
|
|
89
|
+
const endMinutes = Math.floor(totalMinutes % 60);
|
|
90
|
+
|
|
91
|
+
const newEndDate = new Date(tempEvent.startDate);
|
|
92
|
+
newEndDate.setHours(endHour, endMinutes, 0, 0);
|
|
93
|
+
|
|
94
|
+
// Bitiş saati başlangıçtan önce olamaz
|
|
95
|
+
if (newEndDate <= tempEvent.startDate) {
|
|
96
|
+
newEndDate.setTime(tempEvent.startDate.getTime() + 15 * 60 * 1000); // En az 15 dakika
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
setTempEvent(prev => ({
|
|
100
|
+
...prev,
|
|
101
|
+
endDate: newEndDate,
|
|
102
|
+
}));
|
|
103
|
+
}, [isCreating, tempEvent]);
|
|
104
|
+
|
|
105
|
+
// Mouse bırakıldığında event oluştur
|
|
106
|
+
const handleMouseUp = useCallback(() => {
|
|
107
|
+
if (!isCreating || !tempEvent || !onEventCreate) return;
|
|
108
|
+
|
|
109
|
+
// Minimum süre kontrolü (en az 15 dakika)
|
|
110
|
+
const duration = tempEvent.endDate.getTime() - tempEvent.startDate.getTime();
|
|
111
|
+
if (duration < 15 * 60 * 1000) {
|
|
112
|
+
setIsCreating(false);
|
|
113
|
+
setTempEvent(null);
|
|
114
|
+
return; // Çok kısa, event oluşturma
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const newEvent = {
|
|
118
|
+
id: `daily-${Date.now()}`,
|
|
119
|
+
title: 'Yeni Randevu',
|
|
120
|
+
startDate: tempEvent.startDate,
|
|
121
|
+
endDate: tempEvent.endDate,
|
|
122
|
+
resourceId: resource?.id,
|
|
123
|
+
isHourly: true, // Saatlik rezervasyon flag'i
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
onEventCreate(newEvent);
|
|
127
|
+
setIsCreating(false);
|
|
128
|
+
setTempEvent(null);
|
|
129
|
+
}, [isCreating, tempEvent, onEventCreate, resource]);
|
|
130
|
+
|
|
131
|
+
// Global mouse event listener'ları
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (!isCreating) return;
|
|
134
|
+
|
|
135
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
136
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
137
|
+
|
|
138
|
+
return () => {
|
|
139
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
140
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
141
|
+
};
|
|
142
|
+
}, [isCreating, handleMouseMove, handleMouseUp]);
|
|
143
|
+
|
|
144
|
+
// Tarih formatı
|
|
145
|
+
const formatDate = (dateObj) => {
|
|
146
|
+
if (!dateObj) return '';
|
|
147
|
+
const d = new Date(dateObj.fullDate || dateObj);
|
|
148
|
+
return d.toLocaleDateString('tr-TR', {
|
|
149
|
+
weekday: 'long',
|
|
150
|
+
year: 'numeric',
|
|
151
|
+
month: 'long',
|
|
152
|
+
day: 'numeric',
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Saat formatı
|
|
157
|
+
const formatHour = (hour) => {
|
|
158
|
+
return `${hour.toString().padStart(2, '0')}:00`;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (!isOpen || !resource || !date) return null;
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div
|
|
165
|
+
className={`daily-view-overlay ${themeType === 'dark' ? 'dark-mode' : ''}`}
|
|
166
|
+
onClick={onClose}
|
|
167
|
+
>
|
|
168
|
+
<div
|
|
169
|
+
ref={containerRef}
|
|
170
|
+
className="daily-view-container"
|
|
171
|
+
onClick={(e) => e.stopPropagation()}
|
|
172
|
+
>
|
|
173
|
+
{/* Header */}
|
|
174
|
+
<div className="daily-view-header">
|
|
175
|
+
<div className="daily-view-header-content">
|
|
176
|
+
<h2 className="daily-view-title">{resource.name || resource.id}</h2>
|
|
177
|
+
<p className="daily-view-date">{formatDate(date)}</p>
|
|
178
|
+
</div>
|
|
179
|
+
<button className="daily-view-close" onClick={onClose}>
|
|
180
|
+
✕
|
|
181
|
+
</button>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
{/* Timeline Body */}
|
|
185
|
+
<div className="daily-view-body">
|
|
186
|
+
{/* Saatler (Sol Taraf) */}
|
|
187
|
+
<div className="daily-view-hours">
|
|
188
|
+
{hours.map(hour => (
|
|
189
|
+
<div key={hour} className="daily-view-hour-label">
|
|
190
|
+
{formatHour(hour)}
|
|
191
|
+
</div>
|
|
192
|
+
))}
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
{/* Timeline Content */}
|
|
196
|
+
<div
|
|
197
|
+
ref={timelineRef}
|
|
198
|
+
className="daily-view-timeline"
|
|
199
|
+
onClick={handleTimelineClick}
|
|
200
|
+
>
|
|
201
|
+
{/* Saat hücreleri */}
|
|
202
|
+
{hours.map(hour => (
|
|
203
|
+
<div
|
|
204
|
+
key={hour}
|
|
205
|
+
className="daily-view-hour-cell"
|
|
206
|
+
/>
|
|
207
|
+
))}
|
|
208
|
+
|
|
209
|
+
{/* Event'ler (timeline üzerinde absolute position) */}
|
|
210
|
+
{dayEvents.map(event => {
|
|
211
|
+
const position = getEventPosition(event);
|
|
212
|
+
return (
|
|
213
|
+
<div
|
|
214
|
+
key={event.id}
|
|
215
|
+
className="daily-view-event"
|
|
216
|
+
style={position}
|
|
217
|
+
onClick={(e) => {
|
|
218
|
+
e.stopPropagation();
|
|
219
|
+
// Event detayları göster
|
|
220
|
+
}}
|
|
221
|
+
>
|
|
222
|
+
<span className="daily-view-event-title">{event.title}</span>
|
|
223
|
+
<span className="daily-view-event-time">
|
|
224
|
+
{new Date(event.startDate).toLocaleTimeString('tr-TR', {
|
|
225
|
+
hour: '2-digit',
|
|
226
|
+
minute: '2-digit'
|
|
227
|
+
})} - {new Date(event.endDate).toLocaleTimeString('tr-TR', {
|
|
228
|
+
hour: '2-digit',
|
|
229
|
+
minute: '2-digit'
|
|
230
|
+
})}
|
|
231
|
+
</span>
|
|
232
|
+
</div>
|
|
233
|
+
);
|
|
234
|
+
})}
|
|
235
|
+
|
|
236
|
+
{/* Geçici event (oluşturuluyor) */}
|
|
237
|
+
{isCreating && tempEvent && (
|
|
238
|
+
<div
|
|
239
|
+
className="daily-view-temp-event"
|
|
240
|
+
style={getEventPosition({
|
|
241
|
+
startDate: tempEvent.startDate,
|
|
242
|
+
endDate: tempEvent.endDate,
|
|
243
|
+
})}
|
|
244
|
+
>
|
|
245
|
+
Yeni Randevu
|
|
246
|
+
</div>
|
|
247
|
+
)}
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
export default DailyView;
|
|
256
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import './Timeline.css';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Event Badge Component
|
|
6
|
+
* Önemli event'ler için badge gösterir
|
|
7
|
+
*/
|
|
8
|
+
const EventBadge = ({
|
|
9
|
+
text,
|
|
10
|
+
type = 'default', // 'default', 'important', 'urgent', 'new', 'custom'
|
|
11
|
+
position = 'top-right', // 'top-right', 'top-left', 'bottom-right', 'bottom-left'
|
|
12
|
+
className = '',
|
|
13
|
+
style = {}
|
|
14
|
+
}) => {
|
|
15
|
+
return (
|
|
16
|
+
<span
|
|
17
|
+
className={`event-badge event-badge-${type} event-badge-${position} ${className}`}
|
|
18
|
+
style={style}
|
|
19
|
+
>
|
|
20
|
+
{text}
|
|
21
|
+
</span>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default EventBadge;
|
|
26
|
+
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import './Timeline.css';
|
|
3
|
+
|
|
4
|
+
const EventDetailModal = ({
|
|
5
|
+
event,
|
|
6
|
+
isOpen,
|
|
7
|
+
onClose,
|
|
8
|
+
onSave,
|
|
9
|
+
onDelete,
|
|
10
|
+
resources = [],
|
|
11
|
+
}) => {
|
|
12
|
+
const [formData, setFormData] = useState({
|
|
13
|
+
title: '',
|
|
14
|
+
startDate: '',
|
|
15
|
+
endDate: '',
|
|
16
|
+
resourceId: '',
|
|
17
|
+
...event,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (event) {
|
|
22
|
+
setFormData({
|
|
23
|
+
title: event.title || '',
|
|
24
|
+
startDate: event.startDate
|
|
25
|
+
? new Date(event.startDate).toISOString().split('T')[0]
|
|
26
|
+
: '',
|
|
27
|
+
endDate: event.endDate
|
|
28
|
+
? new Date(event.endDate).toISOString().split('T')[0]
|
|
29
|
+
: '',
|
|
30
|
+
resourceId: event.resourceId || '',
|
|
31
|
+
...event,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}, [event]);
|
|
35
|
+
|
|
36
|
+
if (!isOpen || !event) return null;
|
|
37
|
+
|
|
38
|
+
const handleSubmit = (e) => {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
if (onSave) {
|
|
41
|
+
const updatedEvent = {
|
|
42
|
+
...event,
|
|
43
|
+
...formData,
|
|
44
|
+
startDate: new Date(formData.startDate),
|
|
45
|
+
endDate: new Date(formData.endDate),
|
|
46
|
+
};
|
|
47
|
+
onSave(updatedEvent);
|
|
48
|
+
}
|
|
49
|
+
onClose();
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const handleDelete = () => {
|
|
53
|
+
if (onDelete && window.confirm('Bu event\'i silmek istediğinize emin misiniz?')) {
|
|
54
|
+
onDelete(event.id);
|
|
55
|
+
onClose();
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className="modal-overlay" onClick={onClose}>
|
|
61
|
+
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
|
|
62
|
+
<div className="modal-header">
|
|
63
|
+
<h2>Event Düzenle</h2>
|
|
64
|
+
<button className="modal-close-btn" onClick={onClose}>
|
|
65
|
+
×
|
|
66
|
+
</button>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<form onSubmit={handleSubmit} className="modal-form">
|
|
70
|
+
<div className="form-group">
|
|
71
|
+
<label>Başlık</label>
|
|
72
|
+
<input
|
|
73
|
+
type="text"
|
|
74
|
+
value={formData.title}
|
|
75
|
+
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
|
|
76
|
+
required
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<div className="form-group">
|
|
81
|
+
<label>Resource</label>
|
|
82
|
+
<select
|
|
83
|
+
value={formData.resourceId}
|
|
84
|
+
onChange={(e) => setFormData({ ...formData, resourceId: e.target.value })}
|
|
85
|
+
required
|
|
86
|
+
>
|
|
87
|
+
<option value="">Seçiniz...</option>
|
|
88
|
+
{resources.map((resource) => (
|
|
89
|
+
<option key={resource.id} value={resource.id}>
|
|
90
|
+
{resource.name}
|
|
91
|
+
</option>
|
|
92
|
+
))}
|
|
93
|
+
</select>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div className="form-row">
|
|
97
|
+
<div className="form-group">
|
|
98
|
+
<label>Başlangıç Tarihi</label>
|
|
99
|
+
<input
|
|
100
|
+
type="date"
|
|
101
|
+
value={formData.startDate}
|
|
102
|
+
onChange={(e) => setFormData({ ...formData, startDate: e.target.value })}
|
|
103
|
+
required
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div className="form-group">
|
|
108
|
+
<label>Bitiş Tarihi</label>
|
|
109
|
+
<input
|
|
110
|
+
type="date"
|
|
111
|
+
value={formData.endDate}
|
|
112
|
+
onChange={(e) => setFormData({ ...formData, endDate: e.target.value })}
|
|
113
|
+
required
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<div className="modal-actions">
|
|
119
|
+
<button type="button" className="btn-delete" onClick={handleDelete}>
|
|
120
|
+
Sil
|
|
121
|
+
</button>
|
|
122
|
+
<div>
|
|
123
|
+
<button type="button" className="btn-cancel" onClick={onClose}>
|
|
124
|
+
İptal
|
|
125
|
+
</button>
|
|
126
|
+
<button type="submit" className="btn-save">
|
|
127
|
+
Kaydet
|
|
128
|
+
</button>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</form>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export default EventDetailModal;
|
|
138
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import './Timeline.css';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Event Icon Component
|
|
6
|
+
* Event'lerde gösterilecek ikonları render eder
|
|
7
|
+
*/
|
|
8
|
+
const EventIcon = ({ type, className = '' }) => {
|
|
9
|
+
const getIconContent = () => {
|
|
10
|
+
switch (type) {
|
|
11
|
+
case 'balance-warning':
|
|
12
|
+
return '⚠️'; // Bakiye uyarısı
|
|
13
|
+
case 'important-note':
|
|
14
|
+
return '📝'; // Önemli not
|
|
15
|
+
case 'payment-pending':
|
|
16
|
+
return '💳'; // Ödeme bekliyor
|
|
17
|
+
case 'confirmed':
|
|
18
|
+
return '✅'; // Onaylandı
|
|
19
|
+
case 'cancelled':
|
|
20
|
+
return '❌'; // İptal edildi
|
|
21
|
+
case 'pending':
|
|
22
|
+
return '⏳'; // Beklemede
|
|
23
|
+
case 'completed':
|
|
24
|
+
return '✓'; // Tamamlandı
|
|
25
|
+
case 'in-progress':
|
|
26
|
+
return '▶️'; // Devam ediyor
|
|
27
|
+
case 'alert':
|
|
28
|
+
return '🔔'; // Uyarı
|
|
29
|
+
case 'info':
|
|
30
|
+
return 'ℹ️'; // Bilgi
|
|
31
|
+
default:
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const iconContent = getIconContent();
|
|
37
|
+
if (!iconContent) return null;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<span className={`event-icon event-icon-${type} ${className}`}>
|
|
41
|
+
{iconContent}
|
|
42
|
+
</span>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default EventIcon;
|
|
47
|
+
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// src/components/Timeline/EventTooltip.jsx
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
const EventTooltip = ({ event, position = { top: 0, left: 0 }, onClose, onEdit, onDelete }) => {
|
|
5
|
+
if (!event) return null;
|
|
6
|
+
|
|
7
|
+
const { top, left } = position;
|
|
8
|
+
|
|
9
|
+
// Rezervasyon durumuna göre renk belirleme
|
|
10
|
+
const getStatusColor = (status) => {
|
|
11
|
+
switch (status) {
|
|
12
|
+
case "Confirmed":
|
|
13
|
+
return "#4caf50"; // Yeşil
|
|
14
|
+
case "Pending":
|
|
15
|
+
return "#ff9800"; // Turuncu
|
|
16
|
+
case "Cancelled":
|
|
17
|
+
return "#f44336"; // Kırmızı
|
|
18
|
+
case "Completed":
|
|
19
|
+
return "#2196f3"; // Mavi
|
|
20
|
+
default:
|
|
21
|
+
return "#9e9e9e"; // Gri
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const statusColor = getStatusColor(event.status);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
style={{
|
|
30
|
+
position: "absolute",
|
|
31
|
+
top: top - 200, // Tooltip'in biraz yukarıda görünmesi için
|
|
32
|
+
left: left + 70,
|
|
33
|
+
transform: "translateX(-50%)",
|
|
34
|
+
backgroundColor: "#ffffff",
|
|
35
|
+
color: "#333333",
|
|
36
|
+
borderRadius: "10px",
|
|
37
|
+
boxShadow: "0 8px 16px rgba(0, 0, 0, 0.2)",
|
|
38
|
+
fontSize: "16px",
|
|
39
|
+
zIndex: 1000,
|
|
40
|
+
pointerEvents: "auto", // Tooltip'in tıklanabilir olmasını sağlar
|
|
41
|
+
whiteSpace: "normal",
|
|
42
|
+
maxWidth: "400px",
|
|
43
|
+
width: "100%",
|
|
44
|
+
transition: "opacity 0.3s ease, transform 0.3s ease",
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
{/* Kapatma Butonu */}
|
|
48
|
+
{onClose && (
|
|
49
|
+
<button
|
|
50
|
+
onClick={onClose}
|
|
51
|
+
style={{
|
|
52
|
+
position: "absolute",
|
|
53
|
+
top: "10px",
|
|
54
|
+
right: "15px",
|
|
55
|
+
background: "transparent",
|
|
56
|
+
border: "none",
|
|
57
|
+
color: "#aaa",
|
|
58
|
+
fontSize: "24px",
|
|
59
|
+
cursor: "pointer",
|
|
60
|
+
transition: "color 0.2s",
|
|
61
|
+
}}
|
|
62
|
+
aria-label="Kapat"
|
|
63
|
+
onMouseOver={(e) => (e.target.style.color = "#000")}
|
|
64
|
+
onMouseOut={(e) => (e.target.style.color = "#aaa")}
|
|
65
|
+
>
|
|
66
|
+
×
|
|
67
|
+
</button>
|
|
68
|
+
)}
|
|
69
|
+
|
|
70
|
+
{/* Header */}
|
|
71
|
+
<div
|
|
72
|
+
style={{
|
|
73
|
+
backgroundColor: statusColor,
|
|
74
|
+
color: "#ffffff",
|
|
75
|
+
padding: "15px 20px",
|
|
76
|
+
borderTopLeftRadius: "10px",
|
|
77
|
+
borderTopRightRadius: "10px",
|
|
78
|
+
display: "flex",
|
|
79
|
+
flexDirection: "column",
|
|
80
|
+
gap: "5px",
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
<div style={{ fontWeight: "bold", fontSize: "18px" }}>{event.title}</div>
|
|
84
|
+
<div style={{ fontSize: "14px" }}>Rezervasyon ID: {event.reservationId}</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
{/* İçerik */}
|
|
88
|
+
<div style={{ padding: "20px", display: "flex", flexDirection: "column", gap: "15px" }}>
|
|
89
|
+
{/* Misafirler */}
|
|
90
|
+
{Array.isArray(event.guestNames) && (
|
|
91
|
+
<div>
|
|
92
|
+
<strong>Misafirler:</strong> {event.guestNames.join(", ")}
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
95
|
+
|
|
96
|
+
{/* Giriş ve Çıkış Tarihleri */}
|
|
97
|
+
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: "10px" }}>
|
|
98
|
+
<div>
|
|
99
|
+
<strong>Giriş:</strong> {new Date(event.startDate).toLocaleDateString()}
|
|
100
|
+
</div>
|
|
101
|
+
<div>
|
|
102
|
+
<strong>Çıkış:</strong> {new Date(event.endDate).toLocaleDateString()}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
{/* Ödeme Bilgileri */}
|
|
107
|
+
{(event.totalAmount !== undefined || event.amountPaid !== undefined) && (
|
|
108
|
+
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: "10px" }}>
|
|
109
|
+
{event.amountPaid !== undefined && (
|
|
110
|
+
<div>
|
|
111
|
+
<strong>Ödenen Miktar:</strong> ${event.amountPaid.toFixed(2)}
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
{event.totalAmount !== undefined && (
|
|
115
|
+
<div>
|
|
116
|
+
<strong>Toplam Borç:</strong> ${event.totalAmount.toFixed(2)}
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
|
|
122
|
+
{/* Rezervasyon Durumu */}
|
|
123
|
+
{event.status && (
|
|
124
|
+
<div>
|
|
125
|
+
<strong>Durum:</strong> {event.status}
|
|
126
|
+
</div>
|
|
127
|
+
)}
|
|
128
|
+
|
|
129
|
+
{/* Rezervasyon Notu */}
|
|
130
|
+
{event.note && (
|
|
131
|
+
<div>
|
|
132
|
+
<strong>Not:</strong> {event.note}
|
|
133
|
+
</div>
|
|
134
|
+
)}
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
{/* Footer */}
|
|
139
|
+
<div
|
|
140
|
+
style={{
|
|
141
|
+
padding: "15px 20px",
|
|
142
|
+
borderTop: "1px solid #ddd",
|
|
143
|
+
display: "flex",
|
|
144
|
+
justifyContent: "flex-end",
|
|
145
|
+
gap: "10px",
|
|
146
|
+
borderBottomLeftRadius: "10px",
|
|
147
|
+
borderBottomRightRadius: "10px",
|
|
148
|
+
}}
|
|
149
|
+
>
|
|
150
|
+
{/* Düzenle Butonu */}
|
|
151
|
+
{onEdit && (
|
|
152
|
+
<button
|
|
153
|
+
onClick={() => onEdit(event)}
|
|
154
|
+
style={{
|
|
155
|
+
padding: "8px 16px",
|
|
156
|
+
backgroundColor: "#2196f3",
|
|
157
|
+
color: "#ffffff",
|
|
158
|
+
border: "none",
|
|
159
|
+
borderRadius: "4px",
|
|
160
|
+
cursor: "pointer",
|
|
161
|
+
fontSize: "14px",
|
|
162
|
+
display: "flex",
|
|
163
|
+
alignItems: "center",
|
|
164
|
+
gap: "5px",
|
|
165
|
+
transition: "background-color 0.2s",
|
|
166
|
+
}}
|
|
167
|
+
onMouseOver={(e) => (e.target.style.backgroundColor = "#1976d2")}
|
|
168
|
+
onMouseOut={(e) => (e.target.style.backgroundColor = "#2196f3")}
|
|
169
|
+
>
|
|
170
|
+
{/* Düzenle İkonu */}
|
|
171
|
+
<span role="img" aria-label="Düzenle">✏️</span>
|
|
172
|
+
Düzenle
|
|
173
|
+
</button>
|
|
174
|
+
)}
|
|
175
|
+
|
|
176
|
+
{/* Sil Butonu */}
|
|
177
|
+
{onDelete && (
|
|
178
|
+
<button
|
|
179
|
+
onClick={() => onDelete(event.id)}
|
|
180
|
+
style={{
|
|
181
|
+
padding: "8px 16px",
|
|
182
|
+
backgroundColor: "#f44336",
|
|
183
|
+
color: "#ffffff",
|
|
184
|
+
border: "none",
|
|
185
|
+
borderRadius: "4px",
|
|
186
|
+
cursor: "pointer",
|
|
187
|
+
fontSize: "14px",
|
|
188
|
+
display: "flex",
|
|
189
|
+
alignItems: "center",
|
|
190
|
+
gap: "5px",
|
|
191
|
+
transition: "background-color 0.2s",
|
|
192
|
+
}}
|
|
193
|
+
onMouseOver={(e) => (e.target.style.backgroundColor = "#d32f2f")}
|
|
194
|
+
onMouseOut={(e) => (e.target.style.backgroundColor = "#f44336")}
|
|
195
|
+
>
|
|
196
|
+
{/* Sil İkonu */}
|
|
197
|
+
<span role="img" aria-label="Sil">🗑️</span>
|
|
198
|
+
Sil
|
|
199
|
+
</button>
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
);
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
export default EventTooltip;
|
|
207
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
const Indicator = ({ todayIndex, totalDays }) => {
|
|
4
|
+
if (todayIndex < 0 || todayIndex >= totalDays) {
|
|
5
|
+
return null; // Bugün timeline dışında ise çizgiyi gösterme
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
style={{
|
|
11
|
+
position: "absolute",
|
|
12
|
+
top: 0,
|
|
13
|
+
left: `calc(${(todayIndex + 0.5) / totalDays} * 100%)`, // Günün ortasına yerleştirmek için +0.5
|
|
14
|
+
width: "2px",
|
|
15
|
+
height: "100%",
|
|
16
|
+
backgroundColor: "transparent",
|
|
17
|
+
zIndex: 5,
|
|
18
|
+
borderStyle: "dashed", // Kesikli çizgi için
|
|
19
|
+
borderWidth: "0 0 0 2px", // Sadece sol tarafa kesikli çizgi
|
|
20
|
+
borderColor: "red",
|
|
21
|
+
}}
|
|
22
|
+
></div>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default Indicator;
|
|
27
|
+
|