PAUTREL Johan commited on
Commit
035a859
·
verified ·
1 Parent(s): a8a99b7

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +681 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Calendrier Interactif
3
- emoji: 🏢
4
- colorFrom: purple
5
- colorTo: green
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: calendrier-interactif
3
+ emoji: 🐳
4
+ colorFrom: gray
5
+ colorTo: yellow
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,681 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Calendrier Interactif 2025-2030</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://accounts.google.com/gsi/client" async defer></script>
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
+ <style>
11
+ .calendar-day {
12
+ min-height: 120px;
13
+ transition: all 0.2s ease;
14
+ }
15
+ .calendar-day:hover {
16
+ transform: translateY(-2px);
17
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
18
+ }
19
+ .task-item {
20
+ transition: all 0.2s ease;
21
+ }
22
+ .task-item:hover {
23
+ transform: translateX(2px);
24
+ }
25
+ .multi-day-selected {
26
+ background-color: #e0f2fe !important;
27
+ }
28
+ .multi-day-start {
29
+ border-top-left-radius: 0.5rem;
30
+ border-bottom-left-radius: 0.5rem;
31
+ }
32
+ .multi-day-end {
33
+ border-top-right-radius: 0.5rem;
34
+ border-bottom-right-radius: 0.5rem;
35
+ }
36
+ .multi-day-middle {
37
+ border-radius: 0;
38
+ }
39
+ @media (max-width: 768px) {
40
+ .calendar-day {
41
+ min-height: 100px;
42
+ }
43
+ }
44
+ </style>
45
+ </head>
46
+ <body class="bg-blue-50 min-h-screen">
47
+ <div class="container mx-auto px-4 py-8">
48
+ <!-- Header -->
49
+ <header class="flex flex-col md:flex-row justify-between items-center mb-8">
50
+ <h1 class="text-3xl font-bold text-blue-800 mb-4 md:mb-0">Calendrier Interactif</h1>
51
+ <div id="auth-section" class="flex items-center space-x-4">
52
+ <div id="user-info" class="hidden items-center">
53
+ <img id="user-pic" class="w-10 h-10 rounded-full mr-2" src="" alt="Profile">
54
+ <span id="user-name" class="text-blue-800 font-medium"></span>
55
+ </div>
56
+ <div id="g_id_onload"
57
+ data-client_id="YOUR_GOOGLE_CLIENT_ID"
58
+ data-context="signin"
59
+ data-ux_mode="popup"
60
+ data-callback="handleGoogleSignIn"
61
+ data-auto_prompt="false">
62
+ </div>
63
+ <div class="g_id_signin"
64
+ data-type="standard"
65
+ data-shape="pill"
66
+ data-theme="filled_blue"
67
+ data-text="signin_with"
68
+ data-size="medium"
69
+ data-logo_alignment="left">
70
+ </div>
71
+ <button id="sign-out" class="hidden bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-full text-sm font-medium transition">
72
+ Déconnexion
73
+ </button>
74
+ </div>
75
+ </header>
76
+
77
+ <!-- Controls -->
78
+ <div class="bg-white rounded-xl shadow-md p-6 mb-8">
79
+ <div class="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0">
80
+ <div class="flex items-center space-x-4">
81
+ <button id="prev-year" class="bg-blue-100 hover:bg-blue-200 text-blue-800 p-2 rounded-full transition">
82
+ <i class="fas fa-chevron-left"></i>
83
+ </button>
84
+ <h2 id="current-year" class="text-2xl font-bold text-blue-800">2025</h2>
85
+ <button id="next-year" class="bg-blue-100 hover:bg-blue-200 text-blue-800 p-2 rounded-full transition">
86
+ <i class="fas fa-chevron-right"></i>
87
+ </button>
88
+ </div>
89
+ <div class="flex items-center space-x-2">
90
+ <button id="export-btn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg text-sm font-medium transition flex items-center">
91
+ <i class="fas fa-file-export mr-2"></i> Exporter
92
+ </button>
93
+ <label for="import-file" class="bg-blue-100 hover:bg-blue-200 text-blue-800 px-4 py-2 rounded-lg text-sm font-medium transition flex items-center cursor-pointer">
94
+ <i class="fas fa-file-import mr-2"></i> Importer
95
+ <input id="import-file" type="file" accept=".json" class="hidden">
96
+ </label>
97
+ <button id="save-drive-btn" class="hidden bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg text-sm font-medium transition flex items-center">
98
+ <i class="fab fa-google-drive mr-2"></i> Sauvegarder
99
+ </button>
100
+ </div>
101
+ </div>
102
+ </div>
103
+
104
+ <!-- Calendar -->
105
+ <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4" id="calendar-container">
106
+ <!-- Months will be inserted here by JavaScript -->
107
+ </div>
108
+
109
+ <!-- Task Modal -->
110
+ <div id="task-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
111
+ <div class="bg-white rounded-xl shadow-xl p-6 w-full max-w-md">
112
+ <div class="flex justify-between items-center mb-4">
113
+ <h3 class="text-xl font-bold text-blue-800" id="modal-date">Ajouter une tâche</h3>
114
+ <button id="close-modal" class="text-gray-500 hover:text-gray-700">
115
+ <i class="fas fa-times"></i>
116
+ </button>
117
+ </div>
118
+ <div class="space-y-4">
119
+ <div>
120
+ <label for="task-text" class="block text-sm font-medium text-gray-700 mb-1">Tâche</label>
121
+ <input type="text" id="task-text" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
122
+ </div>
123
+ <div>
124
+ <label for="task-time" class="block text-sm font-medium text-gray-700 mb-1">Heure (optionnel)</label>
125
+ <input type="time" id="task-time" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
126
+ </div>
127
+ <div class="flex justify-end space-x-3 pt-2">
128
+ <button id="cancel-task" class="px-4 py-2 text-gray-600 hover:text-gray-800 font-medium rounded-lg transition">
129
+ Annuler
130
+ </button>
131
+ <button id="save-task" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition">
132
+ Ajouter
133
+ </button>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </div>
138
+
139
+ <!-- Multi-day selection info -->
140
+ <div id="multi-day-info" class="fixed bottom-4 left-1/2 transform -translate-x-1/2 bg-white shadow-lg rounded-lg px-4 py-2 hidden">
141
+ <span class="text-blue-800 font-medium">Sélection multiple activée - Sélectionnez plusieurs jours</span>
142
+ <button id="cancel-multi-day" class="ml-4 text-blue-600 hover:text-blue-800">
143
+ <i class="fas fa-times"></i> Annuler
144
+ </button>
145
+ </div>
146
+ </div>
147
+
148
+ <script>
149
+ // Global variables
150
+ let currentYear = 2025;
151
+ let calendarData = {};
152
+ let selectedDate = null;
153
+ let multiDaySelection = false;
154
+ let multiDayStart = null;
155
+ let multiDayEnd = null;
156
+ let currentUser = null;
157
+ let isDragging = false;
158
+
159
+ // Initialize the calendar
160
+ document.addEventListener('DOMContentLoaded', function() {
161
+ initializeCalendar();
162
+ setupEventListeners();
163
+ loadFromLocalStorage();
164
+ });
165
+
166
+ function initializeCalendar() {
167
+ // Initialize empty calendar data structure
168
+ for (let year = 2025; year <= 2030; year++) {
169
+ calendarData[year] = {};
170
+
171
+ for (let month = 0; month < 12; month++) {
172
+ calendarData[year][month] = {};
173
+
174
+ const daysInMonth = new Date(year, month + 1, 0).getDate();
175
+ for (let day = 1; day <= daysInMonth; day++) {
176
+ calendarData[year][month][day] = [];
177
+ }
178
+ }
179
+ }
180
+
181
+ renderCalendar();
182
+ }
183
+
184
+ function renderCalendar() {
185
+ const container = document.getElementById('calendar-container');
186
+ container.innerHTML = '';
187
+
188
+ document.getElementById('current-year').textContent = currentYear;
189
+
190
+ for (let month = 0; month < 12; month++) {
191
+ const monthElement = document.createElement('div');
192
+ monthElement.className = 'bg-white rounded-xl shadow-md overflow-hidden';
193
+
194
+ const monthName = new Date(currentYear, month, 1).toLocaleString('fr-FR', { month: 'long' });
195
+ const capitalizedMonthName = monthName.charAt(0).toUpperCase() + monthName.slice(1);
196
+
197
+ monthElement.innerHTML = `
198
+ <div class="bg-blue-600 text-white px-4 py-3 font-bold">
199
+ ${capitalizedMonthName} ${currentYear}
200
+ </div>
201
+ <div class="grid grid-cols-7 gap-px bg-gray-200 p-px">
202
+ ${['Lu', 'Ma', 'Me', 'Je', 'Ve', 'Sa', 'Di'].map(day => `
203
+ <div class="bg-blue-100 text-blue-800 text-center text-sm font-medium py-1">
204
+ ${day}
205
+ </div>
206
+ `).join('')}
207
+ </div>
208
+ <div class="grid grid-cols-7 gap-px bg-gray-200 p-px" id="month-${month}">
209
+ <!-- Days will be inserted here -->
210
+ </div>
211
+ `;
212
+
213
+ container.appendChild(monthElement);
214
+ renderMonthDays(month);
215
+ }
216
+ }
217
+
218
+ function renderMonthDays(month) {
219
+ const monthContainer = document.getElementById(`month-${month}`);
220
+ const firstDay = new Date(currentYear, month, 1).getDay();
221
+ const daysInMonth = new Date(currentYear, month + 1, 0).getDate();
222
+
223
+ // Adjust for Monday as first day (French calendar)
224
+ const adjustedFirstDay = firstDay === 0 ? 6 : firstDay - 1;
225
+
226
+ // Add empty cells for days before the first day of the month
227
+ for (let i = 0; i < adjustedFirstDay; i++) {
228
+ const emptyCell = document.createElement('div');
229
+ emptyCell.className = 'bg-gray-100 h-12';
230
+ monthContainer.appendChild(emptyCell);
231
+ }
232
+
233
+ // Add cells for each day of the month
234
+ for (let day = 1; day <= daysInMonth; day++) {
235
+ const date = new Date(currentYear, month, day);
236
+ const dayOfWeek = date.getDay();
237
+ const isWeekend = dayOfWeek === 0 || dayOfWeek === 6;
238
+
239
+ const dayElement = document.createElement('div');
240
+ dayElement.className = `calendar-day bg-white ${isWeekend ? 'bg-blue-50' : ''} p-2 relative`;
241
+ dayElement.dataset.year = currentYear;
242
+ dayElement.dataset.month = month;
243
+ dayElement.dataset.day = day;
244
+
245
+ // Check if this day is part of a multi-day selection
246
+ const isMultiDay = checkMultiDaySelection(currentYear, month, day);
247
+ if (isMultiDay) {
248
+ dayElement.classList.add('multi-day-selected', isMultiDay.position);
249
+ }
250
+
251
+ dayElement.innerHTML = `
252
+ <div class="flex justify-between items-start mb-1">
253
+ <span class="text-lg font-medium ${isWeekend ? 'text-blue-600' : 'text-gray-800'}">${day}</span>
254
+ <button class="add-task-btn text-blue-500 hover:text-blue-700 text-sm p-1 rounded-full transition">
255
+ <i class="fas fa-plus"></i>
256
+ </button>
257
+ </div>
258
+ <div class="tasks-container space-y-1 overflow-y-auto max-h-20">
259
+ ${renderTasks(currentYear, month, day)}
260
+ </div>
261
+ `;
262
+
263
+ monthContainer.appendChild(dayElement);
264
+ }
265
+ }
266
+
267
+ function renderTasks(year, month, day) {
268
+ if (!calendarData[year] || !calendarData[year][month] || !calendarData[year][month][day]) {
269
+ return '';
270
+ }
271
+
272
+ return calendarData[year][month][day].map((task, index) => `
273
+ <div class="task-item bg-blue-100 rounded px-2 py-1 text-sm flex justify-between items-center" data-index="${index}">
274
+ <div>
275
+ ${task.time ? `<span class="text-blue-600 font-medium">${task.time}</span> - ` : ''}
276
+ <span>${task.text}</span>
277
+ ${task.user ? `<span class="text-xs text-gray-500 ml-1">(${task.user})</span>` : ''}
278
+ </div>
279
+ <button class="delete-task text-red-500 hover:text-red-700 text-xs p-1">
280
+ <i class="fas fa-trash-alt"></i>
281
+ </button>
282
+ </div>
283
+ `).join('');
284
+ }
285
+
286
+ function setupEventListeners() {
287
+ // Year navigation
288
+ document.getElementById('prev-year').addEventListener('click', () => {
289
+ if (currentYear > 2025) {
290
+ currentYear--;
291
+ renderCalendar();
292
+ }
293
+ });
294
+
295
+ document.getElementById('next-year').addEventListener('click', () => {
296
+ if (currentYear < 2030) {
297
+ currentYear++;
298
+ renderCalendar();
299
+ }
300
+ });
301
+
302
+ // Task modal
303
+ document.addEventListener('click', function(e) {
304
+ if (e.target.closest('.add-task-btn')) {
305
+ const dayElement = e.target.closest('.calendar-day');
306
+ selectedDate = {
307
+ year: parseInt(dayElement.dataset.year),
308
+ month: parseInt(dayElement.dataset.month),
309
+ day: parseInt(dayElement.dataset.day)
310
+ };
311
+
312
+ const dateStr = new Date(selectedDate.year, selectedDate.month, selectedDate.day)
313
+ .toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long' });
314
+
315
+ document.getElementById('modal-date').textContent = `Tâche pour le ${dateStr}`;
316
+ document.getElementById('task-text').value = '';
317
+ document.getElementById('task-time').value = '';
318
+ document.getElementById('task-modal').classList.remove('hidden');
319
+ }
320
+
321
+ if (e.target.closest('.delete-task')) {
322
+ const taskItem = e.target.closest('.task-item');
323
+ const dayElement = e.target.closest('.calendar-day');
324
+ const taskIndex = parseInt(taskItem.dataset.index);
325
+
326
+ const year = parseInt(dayElement.dataset.year);
327
+ const month = parseInt(dayElement.dataset.month);
328
+ const day = parseInt(dayElement.dataset.day);
329
+
330
+ calendarData[year][month][day].splice(taskIndex, 1);
331
+ saveToLocalStorage();
332
+ renderCalendar();
333
+ }
334
+ });
335
+
336
+ // Modal buttons
337
+ document.getElementById('close-modal').addEventListener('click', () => {
338
+ document.getElementById('task-modal').classList.add('hidden');
339
+ });
340
+
341
+ document.getElementById('cancel-task').addEventListener('click', () => {
342
+ document.getElementById('task-modal').classList.add('hidden');
343
+ });
344
+
345
+ document.getElementById('save-task').addEventListener('click', () => {
346
+ const taskText = document.getElementById('task-text').value.trim();
347
+ const taskTime = document.getElementById('task-time').value;
348
+
349
+ if (taskText) {
350
+ const newTask = {
351
+ text: taskText,
352
+ time: taskTime || null,
353
+ user: currentUser ? currentUser.name : null
354
+ };
355
+
356
+ calendarData[selectedDate.year][selectedDate.month][selectedDate.day].push(newTask);
357
+ saveToLocalStorage();
358
+ renderCalendar();
359
+ document.getElementById('task-modal').classList.add('hidden');
360
+ }
361
+ });
362
+
363
+ // Export/Import
364
+ document.getElementById('export-btn').addEventListener('click', exportCalendar);
365
+
366
+ document.getElementById('import-file').addEventListener('change', function(e) {
367
+ const file = e.target.files[0];
368
+ if (!file) return;
369
+
370
+ const reader = new FileReader();
371
+ reader.onload = function(e) {
372
+ try {
373
+ const importedData = JSON.parse(e.target.result);
374
+ mergeCalendarData(importedData);
375
+ saveToLocalStorage();
376
+ renderCalendar();
377
+ alert('Données importées avec succès!');
378
+ } catch (error) {
379
+ alert('Erreur lors de l\'importation: ' + error.message);
380
+ }
381
+ };
382
+ reader.readAsText(file);
383
+ e.target.value = ''; // Reset file input
384
+ });
385
+
386
+ // Multi-day selection
387
+ document.addEventListener('mousedown', handleDayMouseDown);
388
+ document.addEventListener('mousemove', handleDayMouseMove);
389
+ document.addEventListener('mouseup', handleDayMouseUp);
390
+
391
+ document.addEventListener('touchstart', handleDayTouchStart, { passive: false });
392
+ document.addEventListener('touchmove', handleDayTouchMove, { passive: false });
393
+ document.addEventListener('touchend', handleDayTouchEnd);
394
+
395
+ document.getElementById('cancel-multi-day').addEventListener('click', cancelMultiDaySelection);
396
+
397
+ // Enable multi-day selection mode
398
+ document.addEventListener('keydown', function(e) {
399
+ if (e.key === 'Shift' && !multiDaySelection) {
400
+ startMultiDaySelection();
401
+ }
402
+ });
403
+
404
+ document.addEventListener('keyup', function(e) {
405
+ if (e.key === 'Shift' && multiDaySelection) {
406
+ endMultiDaySelection();
407
+ }
408
+ });
409
+ }
410
+
411
+ // Google Sign-In
412
+ function handleGoogleSignIn(response) {
413
+ const user = response.credential ? parseJWT(response.credential) : response;
414
+ currentUser = {
415
+ name: user.name,
416
+ email: user.email,
417
+ picture: user.picture
418
+ };
419
+
420
+ document.getElementById('user-info').classList.remove('hidden');
421
+ document.getElementById('user-pic').src = currentUser.picture;
422
+ document.getElementById('user-name').textContent = currentUser.name;
423
+
424
+ document.querySelector('.g_id_signin').classList.add('hidden');
425
+ document.getElementById('sign-out').classList.remove('hidden');
426
+ document.getElementById('save-drive-btn').classList.remove('hidden');
427
+
428
+ // Load data from Google Drive if available
429
+ // This would require additional Google Drive API implementation
430
+ }
431
+
432
+ function parseJWT(token) {
433
+ const base64Url = token.split('.')[1];
434
+ const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
435
+ const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
436
+ return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
437
+ }).join(''));
438
+
439
+ return JSON.parse(jsonPayload);
440
+ }
441
+
442
+ document.getElementById('sign-out').addEventListener('click', function() {
443
+ google.accounts.id.disableAutoSelect();
444
+ currentUser = null;
445
+
446
+ document.getElementById('user-info').classList.add('hidden');
447
+ document.querySelector('.g_id_signin').classList.remove('hidden');
448
+ document.getElementById('sign-out').classList.add('hidden');
449
+ document.getElementById('save-drive-btn').classList.add('hidden');
450
+ });
451
+
452
+ // Save to Google Drive (placeholder - would need actual implementation)
453
+ document.getElementById('save-drive-btn').addEventListener('click', function() {
454
+ if (!currentUser) return;
455
+
456
+ // In a real implementation, this would use the Google Drive API
457
+ alert('Fonctionnalité de sauvegarde sur Google Drive à implémenter');
458
+ });
459
+
460
+ // Local storage
461
+ function saveToLocalStorage() {
462
+ localStorage.setItem('calendarData', JSON.stringify(calendarData));
463
+ }
464
+
465
+ function loadFromLocalStorage() {
466
+ const savedData = localStorage.getItem('calendarData');
467
+ if (savedData) {
468
+ try {
469
+ const parsedData = JSON.parse(savedData);
470
+
471
+ // Merge with existing structure to ensure all years are present
472
+ for (let year in parsedData) {
473
+ if (parsedData.hasOwnProperty(year)) {
474
+ if (!calendarData[year]) {
475
+ calendarData[year] = {};
476
+ }
477
+
478
+ for (let month in parsedData[year]) {
479
+ if (parsedData[year].hasOwnProperty(month)) {
480
+ if (!calendarData[year][month]) {
481
+ calendarData[year][month] = {};
482
+ }
483
+
484
+ for (let day in parsedData[year][month]) {
485
+ if (parsedData[year][month].hasOwnProperty(day)) {
486
+ calendarData[year][month][day] = parsedData[year][month][day];
487
+ }
488
+ }
489
+ }
490
+ }
491
+ }
492
+ }
493
+
494
+ renderCalendar();
495
+ } catch (error) {
496
+ console.error('Error loading from localStorage:', error);
497
+ }
498
+ }
499
+ }
500
+
501
+ // Export/Import functions
502
+ function exportCalendar() {
503
+ const dataStr = JSON.stringify(calendarData, null, 2);
504
+ const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr);
505
+
506
+ const exportName = `calendrier_${currentYear}.json`;
507
+
508
+ const linkElement = document.createElement('a');
509
+ linkElement.setAttribute('href', dataUri);
510
+ linkElement.setAttribute('download', exportName);
511
+ linkElement.click();
512
+ }
513
+
514
+ function mergeCalendarData(importedData) {
515
+ for (let year in importedData) {
516
+ if (importedData.hasOwnProperty(year)) {
517
+ if (!calendarData[year]) {
518
+ calendarData[year] = {};
519
+ }
520
+
521
+ for (let month in importedData[year]) {
522
+ if (importedData[year].hasOwnProperty(month)) {
523
+ if (!calendarData[year][month]) {
524
+ calendarData[year][month] = {};
525
+ }
526
+
527
+ for (let day in importedData[year][month]) {
528
+ if (importedData[year][month].hasOwnProperty(day)) {
529
+ if (!calendarData[year][month][day]) {
530
+ calendarData[year][month][day] = [];
531
+ }
532
+
533
+ // Add imported tasks with user prefix if available
534
+ const importedTasks = importedData[year][month][day];
535
+ for (let task of importedTasks) {
536
+ if (currentUser) {
537
+ task.text = `[${currentUser.name}] ${task.text}`;
538
+ task.user = currentUser.name;
539
+ }
540
+ calendarData[year][month][day].push(task);
541
+ }
542
+ }
543
+ }
544
+ }
545
+ }
546
+ }
547
+ }
548
+ }
549
+
550
+ // Multi-day selection functions
551
+ function startMultiDaySelection() {
552
+ multiDaySelection = true;
553
+ document.getElementById('multi-day-info').classList.remove('hidden');
554
+ }
555
+
556
+ function endMultiDaySelection() {
557
+ multiDaySelection = false;
558
+ document.getElementById('multi-day-info').classList.add('hidden');
559
+
560
+ if (multiDayStart && multiDayEnd) {
561
+ // Here you would implement what happens when a multi-day selection is made
562
+ // For example, create a task that spans multiple days
563
+ alert(`Sélection multiple du ${formatDate(multiDayStart)} au ${formatDate(multiDayEnd)}`);
564
+ }
565
+
566
+ multiDayStart = null;
567
+ multiDayEnd = null;
568
+ renderCalendar();
569
+ }
570
+
571
+ function cancelMultiDaySelection() {
572
+ multiDaySelection = false;
573
+ multiDayStart = null;
574
+ multiDayEnd = null;
575
+ document.getElementById('multi-day-info').classList.add('hidden');
576
+ renderCalendar();
577
+ }
578
+
579
+ function formatDate(date) {
580
+ return new Date(date.year, date.month, date.day).toLocaleDateString('fr-FR');
581
+ }
582
+
583
+ function checkMultiDaySelection(year, month, day) {
584
+ if (!multiDayStart || !multiDayEnd) return false;
585
+
586
+ const currentDate = new Date(year, month, day).getTime();
587
+ const startDate = new Date(multiDayStart.year, multiDayStart.month, multiDayStart.day).getTime();
588
+ const endDate = new Date(multiDayEnd.year, multiDayEnd.month, multiDayEnd.day).getTime();
589
+
590
+ const minDate = Math.min(startDate, endDate);
591
+ const maxDate = Math.max(startDate, endDate);
592
+
593
+ if (currentDate >= minDate && currentDate <= maxDate) {
594
+ if (currentDate === minDate) return { selected: true, position: 'multi-day-start' };
595
+ if (currentDate === maxDate) return { selected: true, position: 'multi-day-end' };
596
+ return { selected: true, position: 'multi-day-middle' };
597
+ }
598
+
599
+ return false;
600
+ }
601
+
602
+ // Mouse event handlers for multi-day selection
603
+ function handleDayMouseDown(e) {
604
+ if (!multiDaySelection) return;
605
+
606
+ const dayElement = e.target.closest('.calendar-day');
607
+ if (!dayElement) return;
608
+
609
+ isDragging = true;
610
+
611
+ const year = parseInt(dayElement.dataset.year);
612
+ const month = parseInt(dayElement.dataset.month);
613
+ const day = parseInt(dayElement.dataset.day);
614
+
615
+ multiDayStart = { year, month, day };
616
+ multiDayEnd = { year, month, day };
617
+
618
+ renderCalendar();
619
+ }
620
+
621
+ function handleDayMouseMove(e) {
622
+ if (!isDragging || !multiDaySelection) return;
623
+
624
+ const dayElement = e.target.closest('.calendar-day');
625
+ if (!dayElement) return;
626
+
627
+ const year = parseInt(dayElement.dataset.year);
628
+ const month = parseInt(dayElement.dataset.month);
629
+ const day = parseInt(dayElement.dataset.day);
630
+
631
+ multiDayEnd = { year, month, day };
632
+ renderCalendar();
633
+ }
634
+
635
+ function handleDayMouseUp() {
636
+ isDragging = false;
637
+ }
638
+
639
+ // Touch event handlers for multi-day selection
640
+ function handleDayTouchStart(e) {
641
+ if (!multiDaySelection) return;
642
+
643
+ e.preventDefault();
644
+ const touch = e.touches[0];
645
+ const dayElement = document.elementFromPoint(touch.clientX, touch.clientY).closest('.calendar-day');
646
+ if (!dayElement) return;
647
+
648
+ isDragging = true;
649
+
650
+ const year = parseInt(dayElement.dataset.year);
651
+ const month = parseInt(dayElement.dataset.month);
652
+ const day = parseInt(dayElement.dataset.day);
653
+
654
+ multiDayStart = { year, month, day };
655
+ multiDayEnd = { year, month, day };
656
+
657
+ renderCalendar();
658
+ }
659
+
660
+ function handleDayTouchMove(e) {
661
+ if (!isDragging || !multiDaySelection) return;
662
+
663
+ e.preventDefault();
664
+ const touch = e.touches[0];
665
+ const dayElement = document.elementFromPoint(touch.clientX, touch.clientY).closest('.calendar-day');
666
+ if (!dayElement) return;
667
+
668
+ const year = parseInt(dayElement.dataset.year);
669
+ const month = parseInt(dayElement.dataset.month);
670
+ const day = parseInt(dayElement.dataset.day);
671
+
672
+ multiDayEnd = { year, month, day };
673
+ renderCalendar();
674
+ }
675
+
676
+ function handleDayTouchEnd() {
677
+ isDragging = false;
678
+ }
679
+ </script>
680
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=johanpautrel/calendrier-interactif" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
681
+ </html>