418mattburn commited on
Commit
a21abe8
·
verified ·
1 Parent(s): 5c671cd

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +1614 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Wil Be
3
- emoji: 🏆
4
- colorFrom: blue
5
- colorTo: gray
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: wil-be
3
+ emoji: 🐳
4
+ colorFrom: red
5
+ colorTo: green
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,1614 @@
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
+ <script>
5
+ window.huggingface = { variables: { "SPACE_CREATOR_USER_ID": "65c4256169c38d72750aa2d8" } };
6
+ </script>
7
+ <meta charset="UTF-8">
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
9
+ <title>Time Tracking App</title>
10
+ <script src="https://cdn.tailwindcss.com"></script>
11
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
12
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
13
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.28/jspdf.plugin.autotable.min.js"></script>
14
+ <style>
15
+ .task-running {
16
+ background-color: rgba(74, 222, 128, 0.1);
17
+ border-left: 4px solid rgb(74, 222, 128);
18
+ }
19
+ .task-completed {
20
+ background-color: rgba(209, 213, 219, 0.3);
21
+ }
22
+ .modal-overlay {
23
+ background-color: rgba(0, 0, 0, 0.5);
24
+ }
25
+ .slide-in {
26
+ animation: slideIn 0.3s ease-out forwards;
27
+ }
28
+ .slide-out {
29
+ animation: slideOut 0.3s ease-out forwards;
30
+ }
31
+ @keyframes slideIn {
32
+ from { transform: translateY(20px); opacity: 0; }
33
+ to { transform: translateY(0); opacity: 1; }
34
+ }
35
+ @keyframes slideOut {
36
+ from { transform: translateY(0); opacity: 1; }
37
+ to { transform: translateY(20px); opacity: 0; }
38
+ }
39
+ .timer-running {
40
+ animation: pulse 2s infinite;
41
+ }
42
+ @keyframes pulse {
43
+ 0% { color: rgb(74, 222, 128); }
44
+ 50% { color: rgb(34, 197, 94); }
45
+ 100% { color: rgb(74, 222, 128); }
46
+ }
47
+ .employee-select {
48
+ max-height: 300px;
49
+ overflow-y: auto;
50
+ }
51
+ .time-entry {
52
+ font-size: 0.8rem;
53
+ color: #6b7280;
54
+ margin-top: 0.25rem;
55
+ }
56
+ .edit-time-input {
57
+ width: 60px;
58
+ text-align: center;
59
+ padding: 0.25rem;
60
+ border: 1px solid #d1d5db;
61
+ border-radius: 0.25rem;
62
+ }
63
+ .time-picker {
64
+ width: 120px;
65
+ padding: 0.5rem;
66
+ border: 1px solid #d1d5db;
67
+ border-radius: 0.25rem;
68
+ }
69
+ .tab-button {
70
+ padding: 0.5rem 1rem;
71
+ border-bottom: 2px solid transparent;
72
+ cursor: pointer;
73
+ }
74
+ .tab-button.active {
75
+ border-bottom-color: #3b82f6;
76
+ color: #3b82f6;
77
+ font-weight: 500;
78
+ }
79
+ .edit-date-btn {
80
+ cursor: pointer;
81
+ color: #3b82f6;
82
+ margin-left: 0.5rem;
83
+ }
84
+ .edit-date-btn:hover {
85
+ text-decoration: underline;
86
+ }
87
+ .time-range-calculated {
88
+ background-color: rgba(59, 130, 246, 0.1);
89
+ padding: 0.5rem;
90
+ border-radius: 0.25rem;
91
+ margin-top: 0.5rem;
92
+ font-size: 0.875rem;
93
+ }
94
+ </style>
95
+ </head>
96
+ <body class="bg-gray-50 min-h-screen">
97
+ <div class="container mx-auto px-4 py-8">
98
+ <!-- Header -->
99
+ <header class="flex justify-between items-center mb-8">
100
+ <h1 class="text-3xl font-bold text-gray-800">Time Tracker</h1>
101
+ <div class="flex items-center space-x-4">
102
+ <button id="reportBtn" class="flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition">
103
+ <i class="fas fa-file-text mr-2"></i>
104
+ Rapport
105
+ </button>
106
+ <button id="addTaskBtn" class="flex items-center px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition">
107
+ <i class="fas fa-plus mr-2"></i>
108
+ Ajouter Tâche
109
+ </button>
110
+ </div>
111
+ </header>
112
+
113
+ <!-- Stats Cards -->
114
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
115
+ <div class="bg-white p-6 rounded-xl shadow">
116
+ <div class="flex items-center justify-between">
117
+ <div>
118
+ <p class="text-gray-500">Tâches Actives</p>
119
+ <h3 class="text-2xl font-bold" id="activeTasksCount">0</h3>
120
+ </div>
121
+ <div class="p-3 rounded-full bg-green-100 text-green-600">
122
+ <i class="fas fa-play"></i>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ <div class="bg-white p-6 rounded-xl shadow">
127
+ <div class="flex items-center justify-between">
128
+ <div>
129
+ <p class="text-gray-500">Terminées Aujourd'hui</p>
130
+ <h3 class="text-2xl font-bold" id="completedTasksCount">0</h3>
131
+ </div>
132
+ <div class="p-3 rounded-full bg-blue-100 text-blue-600">
133
+ <i class="fas fa-check"></i>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ <div class="bg-white p-6 rounded-xl shadow">
138
+ <div class="flex items-center justify-between">
139
+ <div>
140
+ <p class="text-gray-500">Temps Total Aujourd'hui</p>
141
+ <h3 class="text-2xl font-bold" id="totalTimeToday">0h 0m</h3>
142
+ </div>
143
+ <div class="p-3 rounded-full bg-purple-100 text-purple-600">
144
+ <i class="fas fa-clock"></i>
145
+ </div>
146
+ </div>
147
+ </div>
148
+ </div>
149
+
150
+ <!-- Task List -->
151
+ <div class="bg-white rounded-xl shadow overflow-hidden">
152
+ <div class="px-6 py-4 border-b flex justify-between items-center">
153
+ <h2 class="text-xl font-semibold text-gray-800">Tâches du Jour</h2>
154
+ <div class="relative">
155
+ <button id="employeeFilterBtn" class="flex items-center px-3 py-2 bg-gray-100 rounded-lg hover:bg-gray-200 transition">
156
+ <span id="selectedEmployee">Tous les Employés</span>
157
+ <i class="fas fa-chevron-down ml-2 text-sm"></i>
158
+ </button>
159
+ </div>
160
+ </div>
161
+ <div id="taskList" class="divide-y">
162
+ <!-- Les tâches s'ajouteront ici dynamiquement -->
163
+ <div class="p-6 text-center text-gray-500">
164
+ Aucune tâche ajoutée. Cliquez sur "Ajouter Tâche" pour commencer.
165
+ </div>
166
+ </div>
167
+ </div>
168
+ </div>
169
+
170
+ <!-- Add Task Modal -->
171
+ <div id="addTaskModal" class="fixed inset-0 z-50 flex items-center justify-center hidden">
172
+ <div class="modal-overlay absolute inset-0"></div>
173
+ <div class="bg-white rounded-xl shadow-lg w-full max-w-md z-10 slide-in">
174
+ <div class="p-6 border-b flex justify-between items-center">
175
+ <h3 class="text-xl font-semibold">Nouvelle Tâche</h3>
176
+ <button id="closeAddTaskModal" class="text-gray-500 hover:text-gray-700">
177
+ <i class="fas fa-times"></i>
178
+ </button>
179
+ </div>
180
+ <div class="p-6">
181
+ <form id="taskForm">
182
+ <div class="mb-4">
183
+ <label class="block text-gray-700 mb-2" for="projectNumber">Numéro de Projet</label>
184
+ <input type="text" id="projectNumber" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
185
+ </div>
186
+ <div class="mb-4">
187
+ <label class="block text-gray-700 mb-2" for="taskName">Nom de la Tâche</label>
188
+ <input type="text" id="taskName" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
189
+ </div>
190
+ <div class="mb-4">
191
+ <label class="block text-gray-700 mb-2" for="clientName">Client</label>
192
+ <input type="text" id="clientName" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
193
+ </div>
194
+ <div class="mb-4">
195
+ <label class="block text-gray-700 mb-2" for="taskEmployee">Employé</label>
196
+ <select id="taskEmployee" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 employee-select" required>
197
+ <option value="">Sélectionner un Employé</option>
198
+ <option value="Bastien Bernier">Bastien Bernier</option>
199
+ <option value="Benoit Bernier">Benoit Bernier</option>
200
+ <option value="Francis Bernier">Francis Bernier</option>
201
+ <option value="Gabriel Bilodeau">Gabriel Bilodeau</option>
202
+ <option value="Jacques Bernier">Jacques Bernier</option>
203
+ <option value="Jerome Bernier">Jerome Bernier</option>
204
+ <option value="Matthieu Bernier">Matthieu Bernier</option>
205
+ <option value="Pascal Bernier">Pascal Bernier</option>
206
+ <option value="Richard Houde">Richard Houde</option>
207
+ <option value="Steve Belley">Steve Belley</option>
208
+ <option value="Yves Plante">Yves Plante</option>
209
+ <option value="Jean Philippe Bernier">Jean Philippe Bernier</option>
210
+ <option value="Charles-André Guay">Charles-André Guay</option>
211
+ </select>
212
+ </div>
213
+ <div class="mb-4">
214
+ <label class="block text-gray-700 mb-2" for="taskDate">Date</label>
215
+ <input type="date" id="taskDate" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
216
+ </div>
217
+
218
+ <!-- Section Plage Horaire -->
219
+ <div class="mb-4">
220
+ <label class="block text-gray-700 mb-2">Plage Horaire (Optionnel)</label>
221
+ <div class="flex items-center space-x-2 mb-2">
222
+ <input type="time" id="taskStartTime" class="time-picker">
223
+ <span>à</span>
224
+ <input type="time" id="taskEndTime" class="time-picker">
225
+ </div>
226
+ <div id="taskTimeCalculated" class="time-range-calculated hidden">
227
+ Durée: <span id="taskCalculatedTime">0h 0m 0s</span>
228
+ </div>
229
+ </div>
230
+
231
+ <div class="mb-4">
232
+ <label class="block text-gray-700 mb-2" for="taskDescription">Description (Optionnel)</label>
233
+ <textarea id="taskDescription" rows="3" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
234
+ </div>
235
+ <div class="flex justify-end space-x-3">
236
+ <button type="button" id="cancelAddTask" class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-100 transition">Annuler</button>
237
+ <button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition">Ajouter</button>
238
+ </div>
239
+ </form>
240
+ </div>
241
+ </div>
242
+ </div>
243
+
244
+ <!-- Edit Time Modal -->
245
+ <div id="editTimeModal" class="fixed inset-0 z-50 flex items-center justify-center hidden">
246
+ <div class="modal-overlay absolute inset-0"></div>
247
+ <div class="bg-white rounded-xl shadow-lg w-full max-w-md z-10 slide-in">
248
+ <div class="p-6 border-b flex justify-between items-center">
249
+ <h3 class="text-xl font-semibold">Modifier le Temps</h3>
250
+ <button id="closeEditTimeModal" class="text-gray-500 hover:text-gray-700">
251
+ <i class="fas fa-times"></i>
252
+ </button>
253
+ </div>
254
+ <div class="p-6">
255
+ <div class="flex border-b mb-4">
256
+ <button id="manualTimeTab" class="tab-button active">Temps Manuel</button>
257
+ <button id="timeRangeTab" class="tab-button">Plage Horaire</button>
258
+ <button id="editDatesTab" class="tab-button">Modifier Dates</button>
259
+ </div>
260
+
261
+ <div id="manualTimeSection">
262
+ <div class="mb-4">
263
+ <label class="block text-gray-700 mb-2">Heures</label>
264
+ <input type="number" id="editHours" min="0" class="edit-time-input">
265
+ </div>
266
+ <div class="mb-4">
267
+ <label class="block text-gray-700 mb-2">Minutes</label>
268
+ <input type="number" id="editMinutes" min="0" max="59" class="edit-time-input">
269
+ </div>
270
+ <div class="mb-4">
271
+ <label class="block text-gray-700 mb-2">Secondes</label>
272
+ <input type="number" id="editSeconds" min="0" max="59" class="edit-time-input">
273
+ </div>
274
+ </div>
275
+
276
+ <div id="timeRangeSection" class="hidden">
277
+ <div class="mb-4">
278
+ <label class="block text-gray-700 mb-2">Date</label>
279
+ <input type="date" id="editDate" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
280
+ </div>
281
+ <div class="mb-4">
282
+ <label class="block text-gray-700 mb-2">Heure de Début</label>
283
+ <input type="time" id="editStartTime" class="time-picker">
284
+ </div>
285
+ <div class="mb-4">
286
+ <label class="block text-gray-700 mb-2">Heure de Fin</label>
287
+ <input type="time" id="editEndTime" class="time-picker">
288
+ </div>
289
+ <div class="mb-4 p-3 bg-gray-50 rounded-lg">
290
+ <p class="text-sm text-gray-700">Temps calculé: <span id="calculatedTime">0h 0m 0s</span></p>
291
+ </div>
292
+ </div>
293
+
294
+ <div id="editDatesSection" class="hidden">
295
+ <div class="mb-4">
296
+ <label class="block text-gray-700 mb-2">Date de la Tâche</label>
297
+ <input type="date" id="editTaskDate" class="w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
298
+ </div>
299
+
300
+ <div id="timeEntriesDatesContainer">
301
+ <!-- Les entrées de temps seront ajoutées ici dynamiquement -->
302
+ </div>
303
+ </div>
304
+
305
+ <div class="flex justify-end space-x-3">
306
+ <button type="button" id="cancelEditTime" class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-100 transition">Annuler</button>
307
+ <button type="button" id="saveEditTime" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition">Enregistrer</button>
308
+ </div>
309
+ </div>
310
+ </div>
311
+ </div>
312
+
313
+ <!-- Employee Filter Modal -->
314
+ <div id="employeeFilterModal" class="fixed inset-0 z-50 flex items-center justify-center hidden">
315
+ <div class="modal-overlay absolute inset-0"></div>
316
+ <div class="bg-white rounded-xl shadow-lg w-full max-w-xs z-10 slide-in">
317
+ <div class="p-6 border-b flex justify-between items-center">
318
+ <h3 class="text-xl font-semibold">Filtrer par Employé</h3>
319
+ <button id="closeEmployeeFilterModal" class="text-gray-500 hover:text-gray-700">
320
+ <i class="fas fa-times"></i>
321
+ </button>
322
+ </div>
323
+ <div class="p-4 employee-select">
324
+ <ul class="space-y-2">
325
+ <li>
326
+ <button class="employee-filter-option w-full text-left px-4 py-2 rounded-lg hover:bg-gray-100 transition" data-employee="">
327
+ Tous les Employés
328
+ </button>
329
+ </li>
330
+ <li>
331
+ <button class="employee-filter-option w-full text-left px-4 py-2 rounded-lg hover:bg-gray-100 transition" data-employee="Bastien Bernier">
332
+ Bastien Bernier
333
+ </button>
334
+ </li>
335
+ <li>
336
+ <button class="employee-filter-option w-full text-left px-4 py-2 rounded-lg hover:bg-gray-100 transition" data-employee="Benoit Bernier">
337
+ Benoit Bernier
338
+ </button>
339
+ </li>
340
+ <li>
341
+ <button class="employee-filter-option w-full text-left px-4 py-2 rounded-lg hover:bg-gray-100 transition" data-employee="Francis Bernier">
342
+ Francis Bernier
343
+ </button>
344
+ </li>
345
+ <li>
346
+ <button class="employee-filter-option w-full text-left px-4 py-2 rounded-lg hover:bg-gray-100 transition" data-employee="Gabriel Bilodeau">
347
+ Gabriel Bilodeau
348
+ </button>
349
+ </li>
350
+ <li>
351
+ <button class="employee-filter-option w-full text-left px-4 py-2 rounded-lg hover:bg-gray-100 transition" data-employee="Jacques Bernier">
352
+ Jacques Bernier
353
+ </button>
354
+ </li>
355
+ <li>
356
+ <button class="employee-filter-option w-full text-left px-4 py-2 rounded-lg hover:bg-gray-100 transition" data-employee="Jerome Bernier">
357
+ Jerome Bernier
358
+ </button>
359
+ </li>
360
+ <li>
361
+ <button class="employee-filter-option w-full text-left px-4 py-2 rounded-lg hover:bg-gray-100 transition" data-employee="Matthieu Bernier">
362
+ Matthieu Bernier
363
+ </button>
364
+ </li>
365
+ <li>
366
+ <button class="employee-filter-option w-full text-left px-4 py-2 rounded-lg hover:bg-gray-100 transition" data-employee="Pascal Bernier">
367
+ Pascal Bernier
368
+ </button>
369
+ </li>
370
+ <li>
371
+ <button class="employee-filter-option w-full text-left px-4 py-2 rounded-lg hover:bg-gray-100 transition" data-employee="Richard Houde">
372
+ Richard Houde
373
+ </button>
374
+ </li>
375
+ <li>
376
+ <button class="employee-filter-option w-full text-left px-4 py-2 rounded-lg hover:bg-gray-100 transition" data-employee="Steve Belley">
377
+ Steve Belley
378
+ </button>
379
+ </li>
380
+ <li>
381
+ <button class="employee-filter-option w-full text-left px-4 py-2 rounded-lg hover:bg-gray-100 transition" data-employee="Yves Plante">
382
+ Yves Plante
383
+ </button>
384
+ </li>
385
+ <li>
386
+ <button class="employee-filter-option w-full text-left px-4 py-2 rounded-lg hover:bg-gray-100 transition" data-employee="Jean Philippe Bernier">
387
+ Jean Philippe Bernier
388
+ </button>
389
+ </li>
390
+ <li>
391
+ <button class="employee-filter-option w-full text-left px-4 py-2 rounded-lg hover:bg-gray-100 transition" data-employee="Charles-André Guay">
392
+ Charles-André Guay
393
+ </button>
394
+ </li>
395
+ </ul>
396
+ </div>
397
+ </div>
398
+ </div>
399
+
400
+ <!-- Report Modal -->
401
+ <div id="reportModal" class="fixed inset-0 z-50 flex items-center justify-center hidden">
402
+ <div class="modal-overlay absolute inset-0"></div>
403
+ <div class="bg-white rounded-xl shadow-lg w-full max-w-2xl z-10 slide-in">
404
+ <div class="p-6 border-b flex justify-between items-center">
405
+ <h3 class="text-xl font-semibold">Rapport de Temps</h3>
406
+ <button id="closeReportModal" class="text-gray-500 hover:text-gray-700">
407
+ <i class="fas fa-times"></i>
408
+ </button>
409
+ </div>
410
+ <div class="p-6">
411
+ <div class="flex items-center mb-6">
412
+ <div class="mr-4">
413
+ <label class="block text-gray-700 mb-2">Date de Début</label>
414
+ <input type="date" id="reportStartDate" class="px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
415
+ </div>
416
+ <div class="mr-4">
417
+ <label class="block text-gray-700 mb-2">Date de Fin</label>
418
+ <input type="date" id="reportEndDate" class="px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
419
+ </div>
420
+ <div>
421
+ <label class="block text-gray-700 mb-2">Employé</label>
422
+ <select id="reportEmployee" class="px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 employee-select">
423
+ <option value="">Tous les Employés</option>
424
+ <option value="Bastien Bernier">Bastien Bernier</option>
425
+ <option value="Benoit Bernier">Benoit Bernier</option>
426
+ <option value="Francis Bernier">Francis Bernier</option>
427
+ <option value="Gabriel Bilodeau">Gabriel Bilodeau</option>
428
+ <option value="Jacques Bernier">Jacques Bernier</option>
429
+ <option value="Jerome Bernier">Jerome Bernier</option>
430
+ <option value="Matthieu Bernier">Matthieu Bernier</option>
431
+ <option value="Pascal Bernier">Pascal Bernier</option>
432
+ <option value="Richard Houde">Richard Houde</option>
433
+ <option value="Steve Belley">Steve Belley</option>
434
+ <option value="Yves Plante">Yves Plante</option>
435
+ <option value="Jean Philippe Bernier">Jean Philippe Bernier</option>
436
+ <option value="Charles-André Guay">Charles-André Guay</option>
437
+ </select>
438
+ </div>
439
+ </div>
440
+ <div id="reportContent" class="border rounded-lg p-4 min-h-40 mb-6">
441
+ <p class="text-gray-500">Sélectionnez une période et un employé pour générer un rapport</p>
442
+ </div>
443
+ <div class="flex justify-end space-x-3">
444
+ <button id="cancelReport" class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-100 transition">Annuler</button>
445
+ <button id="generateReport" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition">Générer</button>
446
+ <button id="exportReport" class="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition hidden">Exporter CSV</button>
447
+ <button id="exportPdf" class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition hidden">Exporter PDF</button>
448
+ </div>
449
+ </div>
450
+ </div>
451
+ </div>
452
+
453
+ <script>
454
+ // Gestion de l'état
455
+ let tasks = [];
456
+ let currentFilter = '';
457
+ let intervalIds = {};
458
+ let currentEditingTaskId = null;
459
+ let currentReportData = null;
460
+ let currentEditMode = 'manual'; // 'manual', 'range' ou 'dates'
461
+
462
+ // Éléments du DOM
463
+ const taskList = document.getElementById('taskList');
464
+ const addTaskBtn = document.getElementById('addTaskBtn');
465
+ const addTaskModal = document.getElementById('addTaskModal');
466
+ const closeAddTaskModal = document.getElementById('closeAddTaskModal');
467
+ const cancelAddTask = document.getElementById('cancelAddTask');
468
+ const taskForm = document.getElementById('taskForm');
469
+ const employeeFilterBtn = document.getElementById('employeeFilterBtn');
470
+ const employeeFilterModal = document.getElementById('employeeFilterModal');
471
+ const closeEmployeeFilterModal = document.getElementById('closeEmployeeFilterModal');
472
+ const employeeFilterOptions = document.querySelectorAll('.employee-filter-option');
473
+ const selectedEmployee = document.getElementById('selectedEmployee');
474
+ const reportBtn = document.getElementById('reportBtn');
475
+ const reportModal = document.getElementById('reportModal');
476
+ const closeReportModal = document.getElementById('closeReportModal');
477
+ const generateReport = document.getElementById('generateReport');
478
+ const cancelReport = document.getElementById('cancelReport');
479
+ const exportReport = document.getElementById('exportReport');
480
+ const exportPdf = document.getElementById('exportPdf');
481
+ const reportContent = document.getElementById('reportContent');
482
+ const activeTasksCount = document.getElementById('activeTasksCount');
483
+ const completedTasksCount = document.getElementById('completedTasksCount');
484
+ const totalTimeToday = document.getElementById('totalTimeToday');
485
+ const editTimeModal = document.getElementById('editTimeModal');
486
+ const closeEditTimeModal = document.getElementById('closeEditTimeModal');
487
+ const cancelEditTime = document.getElementById('cancelEditTime');
488
+ const saveEditTime = document.getElementById('saveEditTime');
489
+ const editHours = document.getElementById('editHours');
490
+ const editMinutes = document.getElementById('editMinutes');
491
+ const editSeconds = document.getElementById('editSeconds');
492
+ const manualTimeTab = document.getElementById('manualTimeTab');
493
+ const timeRangeTab = document.getElementById('timeRangeTab');
494
+ const editDatesTab = document.getElementById('editDatesTab');
495
+ const manualTimeSection = document.getElementById('manualTimeSection');
496
+ const timeRangeSection = document.getElementById('timeRangeSection');
497
+ const editDatesSection = document.getElementById('editDatesSection');
498
+ const editDate = document.getElementById('editDate');
499
+ const editStartTime = document.getElementById('editStartTime');
500
+ const editEndTime = document.getElementById('editEndTime');
501
+ const calculatedTime = document.getElementById('calculatedTime');
502
+ const editTaskDate = document.getElementById('editTaskDate');
503
+ const timeEntriesDatesContainer = document.getElementById('timeEntriesDatesContainer');
504
+
505
+ // Nouveaux éléments pour la plage horaire dans le formulaire d'ajout
506
+ const taskStartTime = document.getElementById('taskStartTime');
507
+ const taskEndTime = document.getElementById('taskEndTime');
508
+ const taskTimeCalculated = document.getElementById('taskTimeCalculated');
509
+ const taskCalculatedTime = document.getElementById('taskCalculatedTime');
510
+
511
+ // Initialisation de l'application
512
+ document.addEventListener('DOMContentLoaded', () => {
513
+ // Définit la date d'aujourd'hui par défaut dans le formulaire
514
+ document.getElementById('taskDate').valueAsDate = new Date();
515
+ editDate.valueAsDate = new Date();
516
+ editTaskDate.valueAsDate = new Date();
517
+
518
+ // Charge les tâches sauvegardées depuis localStorage
519
+ const savedTasks = localStorage.getItem('timeTrackerTasks');
520
+ if (savedTasks) {
521
+ tasks = JSON.parse(savedTasks);
522
+ updateTaskList();
523
+ updateStats();
524
+
525
+ // Redémarre les timers en cours
526
+ tasks.forEach(task => {
527
+ if (task.status === 'running') {
528
+ startTimer(task.id);
529
+ }
530
+ });
531
+ }
532
+
533
+ // Écouteurs pour le calcul de la plage horaire dans le formulaire d'ajout
534
+ taskStartTime.addEventListener('change', calculateTaskTimeRange);
535
+ taskEndTime.addEventListener('change', calculateTaskTimeRange);
536
+ });
537
+
538
+ // Écouteurs d'événements
539
+ addTaskBtn.addEventListener('click', () => {
540
+ addTaskModal.classList.remove('hidden');
541
+ });
542
+
543
+ closeAddTaskModal.addEventListener('click', () => {
544
+ addTaskModal.classList.add('hidden');
545
+ });
546
+
547
+ cancelAddTask.addEventListener('click', () => {
548
+ addTaskModal.classList.add('hidden');
549
+ });
550
+
551
+ taskForm.addEventListener('submit', (e) => {
552
+ e.preventDefault();
553
+ addNewTask();
554
+ });
555
+
556
+ employeeFilterBtn.addEventListener('click', () => {
557
+ employeeFilterModal.classList.remove('hidden');
558
+ });
559
+
560
+ closeEmployeeFilterModal.addEventListener('click', () => {
561
+ employeeFilterModal.classList.add('hidden');
562
+ });
563
+
564
+ employeeFilterOptions.forEach(option => {
565
+ option.addEventListener('click', () => {
566
+ currentFilter = option.dataset.employee;
567
+ selectedEmployee.textContent = currentFilter || 'Tous les Employés';
568
+ employeeFilterModal.classList.add('hidden');
569
+ updateTaskList();
570
+ });
571
+ });
572
+
573
+ reportBtn.addEventListener('click', () => {
574
+ // Définit les dates par défaut pour le rapport
575
+ const today = new Date();
576
+ document.getElementById('reportStartDate').valueAsDate = today;
577
+ document.getElementById('reportEndDate').valueAsDate = today;
578
+ reportModal.classList.remove('hidden');
579
+ });
580
+
581
+ closeReportModal.addEventListener('click', () => {
582
+ reportModal.classList.add('hidden');
583
+ });
584
+
585
+ cancelReport.addEventListener('click', () => {
586
+ reportModal.classList.add('hidden');
587
+ });
588
+
589
+ generateReport.addEventListener('click', () => {
590
+ generateTimeReport();
591
+ });
592
+
593
+ exportReport.addEventListener('click', () => {
594
+ exportReportToCSV();
595
+ });
596
+
597
+ exportPdf.addEventListener('click', () => {
598
+ exportReportToPDF();
599
+ });
600
+
601
+ closeEditTimeModal.addEventListener('click', () => {
602
+ editTimeModal.classList.add('hidden');
603
+ });
604
+
605
+ cancelEditTime.addEventListener('click', () => {
606
+ editTimeModal.classList.add('hidden');
607
+ });
608
+
609
+ saveEditTime.addEventListener('click', () => {
610
+ if (currentEditingTaskId) {
611
+ const task = tasks.find(t => t.id === currentEditingTaskId);
612
+ if (task) {
613
+ if (currentEditMode === 'manual') {
614
+ // Entrée manuelle du temps
615
+ const hours = parseInt(editHours.value) || 0;
616
+ const minutes = parseInt(editMinutes.value) || 0;
617
+ const seconds = parseInt(editSeconds.value) || 0;
618
+ task.timeSpent = hours * 3600 + minutes * 60 + seconds;
619
+
620
+ // Réinitialise les entrées de temps existantes
621
+ task.timeEntries = [];
622
+ } else if (currentEditMode === 'range') {
623
+ // Entrée par plage horaire
624
+ const date = editDate.value;
625
+ const startTime = editStartTime.value;
626
+ const endTime = editEndTime.value;
627
+
628
+ if (!date || !startTime || !endTime) {
629
+ alert('Veuillez remplir tous les champs de la plage horaire');
630
+ return;
631
+ }
632
+
633
+ const startDateTime = new Date(`${date}T${startTime}`);
634
+ const endDateTime = new Date(`${date}T${endTime}`);
635
+
636
+ if (endDateTime <= startDateTime) {
637
+ alert('L\'heure de fin doit être après l\'heure de début');
638
+ return;
639
+ }
640
+
641
+ const seconds = Math.floor((endDateTime - startDateTime) / 1000);
642
+ task.timeSpent = seconds;
643
+
644
+ // Mise à jour de la date de la tâche avec la nouvelle date sélectionnée
645
+ task.date = date;
646
+
647
+ // Remplace les entrées de temps par cette nouvelle plage
648
+ task.timeEntries = [{
649
+ startTime: startDateTime.toISOString(),
650
+ endTime: endDateTime.toISOString()
651
+ }];
652
+ } else if (currentEditMode === 'dates') {
653
+ // Modification des dates
654
+ const newTaskDate = editTaskDate.value;
655
+ if (newTaskDate) {
656
+ task.date = newTaskDate;
657
+ }
658
+
659
+ // Met à jour les dates des entrées de temps
660
+ const dateInputs = timeEntriesDatesContainer.querySelectorAll('.time-entry-date');
661
+ dateInputs.forEach((input, index) => {
662
+ if (index < task.timeEntries.length) {
663
+ const entry = task.timeEntries[index];
664
+ const newDate = input.value;
665
+ if (newDate) {
666
+ const startTime = new Date(entry.startTime);
667
+ const endTime = entry.endTime ? new Date(entry.endTime) : null;
668
+
669
+ // Met à jour la date en conservant l'heure
670
+ const newStartTime = new Date(`${newDate}T${formatTimeForInput(startTime)}`);
671
+ entry.startTime = newStartTime.toISOString();
672
+
673
+ if (endTime) {
674
+ const newEndTime = new Date(`${newDate}T${formatTimeForInput(endTime)}`);
675
+ entry.endTime = newEndTime.toISOString();
676
+ }
677
+ }
678
+ }
679
+ });
680
+ }
681
+
682
+ // Met à jour immédiatement l'affichage du timer
683
+ const timerElement = document.getElementById(`timer-${currentEditingTaskId}`);
684
+ if (timerElement) {
685
+ timerElement.textContent = formatTime(task.timeSpent);
686
+ }
687
+
688
+ saveTasks();
689
+ updateStats();
690
+ updateTaskList();
691
+ }
692
+
693
+ editTimeModal.classList.add('hidden');
694
+ currentEditingTaskId = null;
695
+ }
696
+ });
697
+
698
+ // Gestion des onglets dans le modal de modification du temps
699
+ manualTimeTab.addEventListener('click', () => {
700
+ currentEditMode = 'manual';
701
+ manualTimeTab.classList.add('active');
702
+ timeRangeTab.classList.remove('active');
703
+ editDatesTab.classList.remove('active');
704
+ manualTimeSection.classList.remove('hidden');
705
+ timeRangeSection.classList.add('hidden');
706
+ editDatesSection.classList.add('hidden');
707
+ });
708
+
709
+ timeRangeTab.addEventListener('click', () => {
710
+ currentEditMode = 'range';
711
+ timeRangeTab.classList.add('active');
712
+ manualTimeTab.classList.remove('active');
713
+ editDatesTab.classList.remove('active');
714
+ timeRangeSection.classList.remove('hidden');
715
+ manualTimeSection.classList.add('hidden');
716
+ editDatesSection.classList.add('hidden');
717
+ });
718
+
719
+ editDatesTab.addEventListener('click', () => {
720
+ currentEditMode = 'dates';
721
+ editDatesTab.classList.add('active');
722
+ manualTimeTab.classList.remove('active');
723
+ timeRangeTab.classList.remove('active');
724
+ editDatesSection.classList.remove('hidden');
725
+ manualTimeSection.classList.add('hidden');
726
+ timeRangeSection.classList.add('hidden');
727
+
728
+ // Charge les données de date pour la tâche en cours d'édition
729
+ if (currentEditingTaskId) {
730
+ const task = tasks.find(t => t.id === currentEditingTaskId);
731
+ if (task) {
732
+ editTaskDate.value = task.date;
733
+
734
+ // Affiche les entrées de temps avec leurs dates
735
+ timeEntriesDatesContainer.innerHTML = '';
736
+
737
+ if (task.timeEntries.length > 0) {
738
+ const header = document.createElement('h4');
739
+ header.className = 'text-gray-700 mb-2';
740
+ header.textContent = 'Dates des Entrées de Temps';
741
+ timeEntriesDatesContainer.appendChild(header);
742
+
743
+ task.timeEntries.forEach((entry, index) => {
744
+ const entryDiv = document.createElement('div');
745
+ entryDiv.className = 'mb-4 p-3 bg-gray-50 rounded-lg';
746
+
747
+ const startTime = new Date(entry.startTime);
748
+ const endTime = entry.endTime ? new Date(entry.endTime) : null;
749
+
750
+ const dateLabel = document.createElement('label');
751
+ dateLabel.className = 'block text-gray-700 mb-1';
752
+ dateLabel.textContent = `Entrée ${index + 1}`;
753
+
754
+ const dateInput = document.createElement('input');
755
+ dateInput.type = 'date';
756
+ dateInput.className = 'w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 time-entry-date';
757
+ dateInput.value = startTime.toISOString().split('T')[0];
758
+
759
+ const timeInfo = document.createElement('div');
760
+ timeInfo.className = 'text-sm text-gray-600 mt-1';
761
+ timeInfo.innerHTML = `
762
+ <span class="font-medium">${formatDateTime(startTime)}</span>
763
+ ${endTime ? ` - <span class="font-medium">${formatDateTime(endTime)}</span>` : ''}
764
+ `;
765
+
766
+ entryDiv.appendChild(dateLabel);
767
+ entryDiv.appendChild(dateInput);
768
+ entryDiv.appendChild(timeInfo);
769
+ timeEntriesDatesContainer.appendChild(entryDiv);
770
+ });
771
+ } else {
772
+ const noEntries = document.createElement('p');
773
+ noEntries.className = 'text-gray-500 text-sm';
774
+ noEntries.textContent = 'Aucune entrée de temps enregistrée pour cette tâche.';
775
+ timeEntriesDatesContainer.appendChild(noEntries);
776
+ }
777
+ }
778
+ }
779
+ });
780
+
781
+ // Calcul de la différence de temps lors du changement des valeurs
782
+ editStartTime.addEventListener('change', calculateTimeDifference);
783
+ editEndTime.addEventListener('change', calculateTimeDifference);
784
+ editDate.addEventListener('change', calculateTimeDifference);
785
+
786
+ function calculateTimeDifference() {
787
+ const date = editDate.value;
788
+ const startTime = editStartTime.value;
789
+ const endTime = editEndTime.value;
790
+
791
+ if (date && startTime && endTime) {
792
+ const startDateTime = new Date(`${date}T${startTime}`);
793
+ const endDateTime = new Date(`${date}T${endTime}`);
794
+
795
+ if (endDateTime > startDateTime) {
796
+ const seconds = Math.floor((endDateTime - startDateTime) / 1000);
797
+ calculatedTime.textContent = formatTime(seconds);
798
+ } else {
799
+ calculatedTime.textContent = '0h 0m 0s';
800
+ }
801
+ }
802
+ }
803
+
804
+ // Calcul de la plage horaire dans le formulaire d'ajout
805
+ function calculateTaskTimeRange() {
806
+ const date = document.getElementById('taskDate').value;
807
+ const startTime = taskStartTime.value;
808
+ const endTime = taskEndTime.value;
809
+
810
+ if (date && startTime && endTime) {
811
+ const startDateTime = new Date(`${date}T${startTime}`);
812
+ const endDateTime = new Date(`${date}T${endTime}`);
813
+
814
+ if (endDateTime > startDateTime) {
815
+ const seconds = Math.floor((endDateTime - startDateTime) / 1000);
816
+ taskCalculatedTime.textContent = formatTime(seconds);
817
+ taskTimeCalculated.classList.remove('hidden');
818
+ } else {
819
+ taskTimeCalculated.classList.add('hidden');
820
+ }
821
+ } else {
822
+ taskTimeCalculated.classList.add('hidden');
823
+ }
824
+ }
825
+
826
+ // Fonctions
827
+ function addNewTask() {
828
+ const projectNumber = document.getElementById('projectNumber').value;
829
+ const taskName = document.getElementById('taskName').value;
830
+ const clientName = document.getElementById('clientName').value;
831
+ const taskEmployee = document.getElementById('taskEmployee').value;
832
+ const taskDate = document.getElementById('taskDate').value;
833
+ const taskDescription = document.getElementById('taskDescription').value;
834
+
835
+ // Vérifie si une plage horaire a été définie
836
+ const startTime = taskStartTime.value;
837
+ const endTime = taskEndTime.value;
838
+ let timeSpent = 0;
839
+ let timeEntries = [];
840
+
841
+ if (startTime && endTime) {
842
+ const startDateTime = new Date(`${taskDate}T${startTime}`);
843
+ const endDateTime = new Date(`${taskDate}T${endTime}`);
844
+
845
+ if (endDateTime <= startDateTime) {
846
+ alert('L\'heure de fin doit être après l\'heure de début');
847
+ return;
848
+ }
849
+
850
+ timeSpent = Math.floor((endDateTime - startDateTime) / 1000);
851
+ timeEntries = [{
852
+ startTime: startDateTime.toISOString(),
853
+ endTime: endDateTime.toISOString()
854
+ }];
855
+ }
856
+
857
+ const newTask = {
858
+ id: Date.now().toString(),
859
+ projectNumber: projectNumber,
860
+ name: taskName,
861
+ client: clientName,
862
+ employee: taskEmployee,
863
+ date: taskDate,
864
+ description: taskDescription,
865
+ status: 'not_started',
866
+ timeSpent: timeSpent, // en secondes
867
+ startTime: null,
868
+ timeEntries: timeEntries,
869
+ order: tasks.length
870
+ };
871
+
872
+ tasks.push(newTask);
873
+ saveTasks();
874
+ updateTaskList();
875
+ updateStats();
876
+
877
+ // Réinitialise le formulaire et ferme le modal
878
+ taskForm.reset();
879
+ document.getElementById('taskDate').valueAsDate = new Date();
880
+ taskStartTime.value = '';
881
+ taskEndTime.value = '';
882
+ taskTimeCalculated.classList.add('hidden');
883
+ addTaskModal.classList.add('hidden');
884
+ }
885
+
886
+ function updateTaskList() {
887
+ // Filtre les tâches selon le filtre courant
888
+ let filteredTasks = tasks;
889
+ if (currentFilter) {
890
+ filteredTasks = tasks.filter(task => task.employee === currentFilter);
891
+ }
892
+
893
+ // Trie les tâches par leur ordre d'origine
894
+ filteredTasks.sort((a, b) => a.order - b.order);
895
+
896
+ if (filteredTasks.length === 0) {
897
+ taskList.innerHTML = '<div class="p-6 text-center text-gray-500">Aucune tâche trouvée.</div>';
898
+ return;
899
+ }
900
+
901
+ taskList.innerHTML = '';
902
+
903
+ filteredTasks.forEach(task => {
904
+ const taskElement = document.createElement('div');
905
+ taskElement.className = `p-6 flex flex-col md:flex-row md:items-center md:justify-between ${task.status === 'running' ? 'task-running' : ''} ${task.status === 'completed' ? 'task-completed' : ''}`;
906
+
907
+ const timeSpent = formatTime(task.timeSpent);
908
+
909
+ // Format des entrées de temps pour l'affichage
910
+ let timeEntriesHTML = '';
911
+ if (task.timeEntries.length > 0) {
912
+ timeEntriesHTML = '<div class="time-entries mt-2">';
913
+ task.timeEntries.forEach(entry => {
914
+ const startTime = new Date(entry.startTime);
915
+ const endTime = entry.endTime ? new Date(entry.endTime) : null;
916
+
917
+ timeEntriesHTML += `
918
+ <div class="time-entry">
919
+ <span class="font-medium">${formatDateTime(startTime)}</span>
920
+ ${endTime ? ` - <span class="font-medium">${formatDateTime(endTime)}</span>` : ''}
921
+ </div>
922
+ `;
923
+ });
924
+ timeEntriesHTML += '</div>';
925
+ }
926
+
927
+ taskElement.innerHTML = `
928
+ <div class="mb-4 md:mb-0">
929
+ <h3 class="font-medium text-gray-800">${task.projectNumber} - ${task.name}</h3>
930
+ <div class="flex items-center mt-1 text-sm text-gray-600">
931
+ <i class="fas fa-user mr-1"></i>
932
+ <span class="mr-3">${task.employee}</span>
933
+ <i class="fas fa-building mr-1"></i>
934
+ <span class="mr-3">${task.client}</span>
935
+ <i class="fas fa-calendar mr-1"></i>
936
+ <span>${
937
+ task.timeEntries.length > 0
938
+ ? formatDate(new Date(task.timeEntries[0].startTime))
939
+ : formatDate(task.date)
940
+ }</span>
941
+ <span class="edit-date-btn" data-id="${task.id}">
942
+ <i class="fas fa-pencil-alt"></i>
943
+ </span>
944
+ </div>
945
+ ${task.description ? `<p class="mt-2 text-sm text-gray-600">${task.description}</p>` : ''}
946
+ ${timeEntriesHTML}
947
+ </div>
948
+ <div class="flex items-center space-x-3">
949
+ <div class="flex flex-col items-center">
950
+ <div class="text-lg font-medium ${task.status === 'running' ? 'timer-running' : ''}" id="timer-${task.id}">
951
+ ${timeSpent}
952
+ </div>
953
+ <button class="task-action-edit text-xs text-blue-600 hover:text-blue-800 mt-1" data-id="${task.id}">
954
+ <i class="fas fa-pencil-alt mr-1"></i>Modifier
955
+ </button>
956
+ </div>
957
+ <div class="flex space-x-2">
958
+ ${task.status === 'not_started' ? `
959
+ <button class="task-action-start p-2 text-green-600 bg-green-100 rounded-full hover:bg-green-200 transition" data-id="${task.id}">
960
+ <i class="fas fa-play"></i>
961
+ </button>
962
+ ` : ''}
963
+ ${task.status === 'running' ? `
964
+ <button class="task-action-stop p-2 text-red-600 bg-red-100 rounded-full hover:bg-red-200 transition" data-id="${task.id}">
965
+ <i class="fas fa-stop"></i>
966
+ </button>
967
+ ` : ''}
968
+ ${task.status !== 'completed' ? `
969
+ <button class="task-action-complete p-2 text-blue-600 bg-blue-100 rounded-full hover:bg-blue-200 transition" data-id="${task.id}">
970
+ <i class="fas fa-check"></i>
971
+ </button>
972
+ ` : ''}
973
+ <button class="task-action-delete p-2 text-gray-600 bg-gray-100 rounded-full hover:bg-gray-200 transition" data-id="${task.id}">
974
+ <i class="fas fa-trash"></i>
975
+ </button>
976
+ </div>
977
+ </div>
978
+ `;
979
+
980
+ taskList.appendChild(taskElement);
981
+ });
982
+
983
+ // Ajoute les écouteurs d'événements aux nouveaux boutons
984
+ document.querySelectorAll('.task-action-start').forEach(btn => {
985
+ btn.addEventListener('click', (e) => {
986
+ const taskId = e.currentTarget.dataset.id;
987
+ startTask(taskId);
988
+ });
989
+ });
990
+
991
+ document.querySelectorAll('.task-action-stop').forEach(btn => {
992
+ btn.addEventListener('click', (e) => {
993
+ const taskId = e.currentTarget.dataset.id;
994
+ stopTask(taskId);
995
+ });
996
+ });
997
+
998
+ document.querySelectorAll('.task-action-complete').forEach(btn => {
999
+ btn.addEventListener('click', (e) => {
1000
+ const taskId = e.currentTarget.dataset.id;
1001
+ completeTask(taskId);
1002
+ });
1003
+ });
1004
+
1005
+ document.querySelectorAll('.task-action-delete').forEach(btn => {
1006
+ btn.addEventListener('click', (e) => {
1007
+ const taskId = e.currentTarget.dataset.id;
1008
+ deleteTask(taskId);
1009
+ });
1010
+ });
1011
+
1012
+ document.querySelectorAll('.task-action-edit').forEach(btn => {
1013
+ btn.addEventListener('click', (e) => {
1014
+ const taskId = e.currentTarget.dataset.id;
1015
+ openEditTimeModal(taskId);
1016
+ });
1017
+ });
1018
+
1019
+ // Ajoute les écouteurs pour les boutons de modification de date
1020
+ document.querySelectorAll('.edit-date-btn').forEach(btn => {
1021
+ btn.addEventListener('click', (e) => {
1022
+ e.stopPropagation();
1023
+ const taskId = e.currentTarget.dataset.id;
1024
+ openEditTimeModal(taskId, 'dates');
1025
+ });
1026
+ });
1027
+ }
1028
+
1029
+ function openEditTimeModal(taskId, mode = 'manual') {
1030
+ const task = tasks.find(t => t.id === taskId);
1031
+ if (task) {
1032
+ currentEditingTaskId = taskId;
1033
+
1034
+ // Définit le mode d'édition
1035
+ currentEditMode = mode;
1036
+
1037
+ // Active l'onglet approprié
1038
+ if (mode === 'manual') {
1039
+ manualTimeTab.classList.add('active');
1040
+ timeRangeTab.classList.remove('active');
1041
+ editDatesTab.classList.remove('active');
1042
+ manualTimeSection.classList.remove('hidden');
1043
+ timeRangeSection.classList.add('hidden');
1044
+ editDatesSection.classList.add('hidden');
1045
+ } else if (mode === 'range') {
1046
+ timeRangeTab.classList.add('active');
1047
+ manualTimeTab.classList.remove('active');
1048
+ editDatesTab.classList.remove('active');
1049
+ timeRangeSection.classList.remove('hidden');
1050
+ manualTimeSection.classList.add('hidden');
1051
+ editDatesSection.classList.add('hidden');
1052
+ } else if (mode === 'dates') {
1053
+ editDatesTab.classList.add('active');
1054
+ manualTimeTab.classList.remove('active');
1055
+ timeRangeTab.classList.remove('active');
1056
+ editDatesSection.classList.remove('hidden');
1057
+ manualTimeSection.classList.add('hidden');
1058
+ timeRangeSection.classList.add('hidden');
1059
+ }
1060
+
1061
+ // Calcule heures, minutes, secondes à partir du temps total (en secondes)
1062
+ const hours = Math.floor(task.timeSpent / 3600);
1063
+ const minutes = Math.floor((task.timeSpent % 3600) / 60);
1064
+ const seconds = task.timeSpent % 60;
1065
+
1066
+ // Remplit le formulaire de temps manuel
1067
+ editHours.value = hours;
1068
+ editMinutes.value = minutes;
1069
+ editSeconds.value = seconds;
1070
+
1071
+ // Si des entrées de temps existent, remplit le formulaire de plage horaire
1072
+ if (task.timeEntries.length > 0) {
1073
+ const firstEntry = task.timeEntries[0];
1074
+ const startTime = new Date(firstEntry.startTime);
1075
+
1076
+ editDate.valueAsDate = startTime;
1077
+ editStartTime.value = formatTimeForInput(startTime);
1078
+
1079
+ if (firstEntry.endTime) {
1080
+ const endTime = new Date(firstEntry.endTime);
1081
+ editEndTime.value = formatTimeForInput(endTime);
1082
+ calculatedTime.textContent = formatTime(task.timeSpent);
1083
+ }
1084
+ } else {
1085
+ // Valeurs par défaut pour la plage horaire
1086
+ editDate.valueAsDate = new Date();
1087
+ editStartTime.value = '';
1088
+ editEndTime.value = '';
1089
+ calculatedTime.textContent = '0h 0m 0s';
1090
+ }
1091
+
1092
+ // Remplit la date de la tâche
1093
+ editTaskDate.value = task.date;
1094
+
1095
+ // Prépare les entrées de temps pour l'onglet de modification des dates
1096
+ timeEntriesDatesContainer.innerHTML = '';
1097
+
1098
+ if (task.timeEntries.length > 0) {
1099
+ const header = document.createElement('h4');
1100
+ header.className = 'text-gray-700 mb-2';
1101
+ header.textContent = 'Dates des Entrées de Temps';
1102
+ timeEntriesDatesContainer.appendChild(header);
1103
+
1104
+ task.timeEntries.forEach((entry, index) => {
1105
+ const entryDiv = document.createElement('div');
1106
+ entryDiv.className = 'mb-4 p-3 bg-gray-50 rounded-lg';
1107
+
1108
+ const startTime = new Date(entry.startTime);
1109
+ const endTime = entry.endTime ? new Date(entry.endTime) : null;
1110
+
1111
+ const dateLabel = document.createElement('label');
1112
+ dateLabel.className = 'block text-gray-700 mb-1';
1113
+ dateLabel.textContent = `Entrée ${index + 1}`;
1114
+
1115
+ const dateInput = document.createElement('input');
1116
+ dateInput.type = 'date';
1117
+ dateInput.className = 'w-full px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 time-entry-date';
1118
+ dateInput.value = startTime.toISOString().split('T')[0];
1119
+
1120
+ const timeInfo = document.createElement('div');
1121
+ timeInfo.className = 'text-sm text-gray-600 mt-1';
1122
+ timeInfo.innerHTML = `
1123
+ <span class="font-medium">${formatDateTime(startTime)}</span>
1124
+ ${endTime ? ` - <span class="font-medium">${formatDateTime(endTime)}</span>` : ''}
1125
+ `;
1126
+
1127
+ entryDiv.appendChild(dateLabel);
1128
+ entryDiv.appendChild(dateInput);
1129
+ entryDiv.appendChild(timeInfo);
1130
+ timeEntriesDatesContainer.appendChild(entryDiv);
1131
+ });
1132
+ } else {
1133
+ const noEntries = document.createElement('p');
1134
+ noEntries.className = 'text-gray-500 text-sm';
1135
+ noEntries.textContent = 'Aucune entrée de temps enregistrée pour cette tâche.';
1136
+ timeEntriesDatesContainer.appendChild(noEntries);
1137
+ }
1138
+
1139
+ editTimeModal.classList.remove('hidden');
1140
+ }
1141
+ }
1142
+
1143
+ function formatTimeForInput(date) {
1144
+ const hours = date.getHours().toString().padStart(2, '0');
1145
+ const minutes = date.getMinutes().toString().padStart(2, '0');
1146
+ return `${hours}:${minutes}`;
1147
+ }
1148
+
1149
+ function startTask(taskId) {
1150
+ const task = tasks.find(t => t.id === taskId);
1151
+ if (task) {
1152
+ task.status = 'running';
1153
+ const now = new Date();
1154
+ task.startTime = now;
1155
+
1156
+ // Ajoute une nouvelle entrée de temps
1157
+ task.timeEntries.push({
1158
+ startTime: now,
1159
+ endTime: null
1160
+ });
1161
+
1162
+ startTimer(taskId);
1163
+ saveTasks();
1164
+ updateTaskList();
1165
+ updateStats();
1166
+ }
1167
+ }
1168
+
1169
+ function stopTask(taskId) {
1170
+ const task = tasks.find(t => t.id === taskId);
1171
+ if (task) {
1172
+ task.status = 'not_started';
1173
+ const now = new Date();
1174
+
1175
+ if (task.startTime) {
1176
+ const seconds = Math.floor((now - new Date(task.startTime)) / 1000);
1177
+ task.timeSpent += seconds;
1178
+
1179
+ // Met à jour la dernière entrée de temps avec l'heure de fin
1180
+ if (task.timeEntries.length > 0) {
1181
+ const lastEntry = task.timeEntries[task.timeEntries.length - 1];
1182
+ lastEntry.endTime = now;
1183
+ }
1184
+
1185
+ task.startTime = null;
1186
+ }
1187
+
1188
+ stopTimer(taskId);
1189
+ saveTasks();
1190
+ updateTaskList();
1191
+ updateStats();
1192
+ }
1193
+ }
1194
+
1195
+ function completeTask(taskId) {
1196
+ const task = tasks.find(t => t.id === taskId);
1197
+ if (task) {
1198
+ if (task.status === 'running') {
1199
+ const now = new Date();
1200
+ const seconds = Math.floor((now - new Date(task.startTime)) / 1000);
1201
+ task.timeSpent += seconds;
1202
+
1203
+ // Met à jour la dernière entrée de temps avec l'heure de fin
1204
+ if (task.timeEntries.length > 0) {
1205
+ const lastEntry = task.timeEntries[task.timeEntries.length - 1];
1206
+ lastEntry.endTime = now;
1207
+ }
1208
+
1209
+ task.startTime = null;
1210
+ stopTimer(taskId);
1211
+ }
1212
+ task.status = 'completed';
1213
+ saveTasks();
1214
+ updateTaskList();
1215
+ updateStats();
1216
+ }
1217
+ }
1218
+
1219
+ function deleteTask(taskId) {
1220
+ if (confirm('Êtes-vous sûr de vouloir supprimer cette tâche?')) {
1221
+ stopTimer(taskId);
1222
+ tasks = tasks.filter(t => t.id !== taskId);
1223
+
1224
+ // Réaffecte les valeurs d'ordre pour conserver la séquence
1225
+ tasks.forEach((task, index) => {
1226
+ task.order = index;
1227
+ });
1228
+
1229
+ saveTasks();
1230
+ updateTaskList();
1231
+ updateStats();
1232
+ }
1233
+ }
1234
+
1235
+ function startTimer(taskId) {
1236
+ stopTimer(taskId); // Pour éviter les doublons
1237
+
1238
+ const updateTimer = () => {
1239
+ const task = tasks.find(t => t.id === taskId);
1240
+ if (task && task.status === 'running') {
1241
+ const now = new Date();
1242
+ const startTime = new Date(task.startTime);
1243
+ const seconds = Math.floor((now - startTime) / 1000);
1244
+ const totalSeconds = task.timeSpent + seconds;
1245
+
1246
+ const timerElement = document.getElementById(`timer-${taskId}`);
1247
+ if (timerElement) {
1248
+ timerElement.textContent = formatTime(totalSeconds);
1249
+ }
1250
+ }
1251
+ };
1252
+
1253
+ updateTimer();
1254
+ intervalIds[taskId] = setInterval(updateTimer, 1000);
1255
+ }
1256
+
1257
+ function stopTimer(taskId) {
1258
+ if (intervalIds[taskId]) {
1259
+ clearInterval(intervalIds[taskId]);
1260
+ delete intervalIds[taskId];
1261
+ }
1262
+ }
1263
+
1264
+ function saveTasks() {
1265
+ localStorage.setItem('timeTrackerTasks', JSON.stringify(tasks));
1266
+ }
1267
+
1268
+ function updateStats() {
1269
+ const today = new Date().toISOString().split('T')[0];
1270
+
1271
+ // Tâches actives (en cours ou non démarrées)
1272
+ const activeTasks = tasks.filter(task =>
1273
+ task.status !== 'completed' && task.date === today
1274
+ ).length;
1275
+ activeTasksCount.textContent = activeTasks;
1276
+
1277
+ // Tâches terminées aujourd'hui
1278
+ const completedTasks = tasks.filter(task =>
1279
+ task.status === 'completed' && task.date === today
1280
+ ).length;
1281
+ completedTasksCount.textContent = completedTasks;
1282
+
1283
+ // Temps total passé aujourd'hui
1284
+ const totalSeconds = tasks
1285
+ .filter(task => task.date === today)
1286
+ .reduce((total, task) => total + task.timeSpent, 0);
1287
+
1288
+ totalTimeToday.textContent = formatTime(totalSeconds);
1289
+ }
1290
+
1291
+ function formatTime(seconds) {
1292
+ const hours = Math.floor(seconds / 3600);
1293
+ const minutes = Math.floor((seconds % 3600) / 60);
1294
+ const secs = seconds % 60;
1295
+
1296
+ if (hours > 0) {
1297
+ return `${hours}h ${minutes}m ${secs}s`;
1298
+ } else if (minutes > 0) {
1299
+ return `${minutes}m ${secs}s`;
1300
+ } else {
1301
+ return `${secs}s`;
1302
+ }
1303
+ }
1304
+
1305
+ // Pour corriger le décalage, on interprète une chaîne "YYYY-MM-DD" comme date locale
1306
+ function formatDate(dateInput) {
1307
+ if (typeof dateInput === "string" && /^\d{4}-\d{2}-\d{2}$/.test(dateInput)) {
1308
+ const parts = dateInput.split("-");
1309
+ const year = parseInt(parts[0], 10);
1310
+ const month = parseInt(parts[1], 10) - 1;
1311
+ const day = parseInt(parts[2], 10);
1312
+ const options = { year: 'numeric', month: 'short', day: 'numeric' };
1313
+ return new Date(year, month, day).toLocaleDateString('fr-FR', options);
1314
+ } else {
1315
+ const options = { year: 'numeric', month: 'short', day: 'numeric' };
1316
+ return new Date(dateInput).toLocaleDateString('fr-FR', options);
1317
+ }
1318
+ }
1319
+
1320
+ function formatDateTime(date) {
1321
+ const options = {
1322
+ hour: '2-digit',
1323
+ minute: '2-digit',
1324
+ day: '2-digit',
1325
+ month: '2-digit',
1326
+ year: 'numeric'
1327
+ };
1328
+ return date.toLocaleTimeString('fr-FR', options);
1329
+ }
1330
+
1331
+ // Même correction pour le PDF
1332
+ function formatDateForPdf(dateInput) {
1333
+ if (typeof dateInput === "string" && /^\d{4}-\d{2}-\d{2}$/.test(dateInput)) {
1334
+ const parts = dateInput.split("-");
1335
+ const year = parseInt(parts[0], 10);
1336
+ const month = parseInt(parts[1], 10) - 1;
1337
+ const day = parseInt(parts[2], 10);
1338
+ return new Date(year, month, day).toLocaleDateString('fr-FR');
1339
+ } else {
1340
+ return new Date(dateInput).toLocaleDateString('fr-FR');
1341
+ }
1342
+ }
1343
+
1344
+ function formatDateTimeForPdf(date) {
1345
+ return date.toLocaleString('fr-FR');
1346
+ }
1347
+
1348
+ function generateTimeReport() {
1349
+ const startDate = document.getElementById('reportStartDate').value;
1350
+ const endDate = document.getElementById('reportEndDate').value;
1351
+ const employee = document.getElementById('reportEmployee').value;
1352
+
1353
+ if (!startDate || !endDate) {
1354
+ alert('Veuillez sélectionner une date de début et de fin');
1355
+ return;
1356
+ }
1357
+
1358
+ // Filtre les tâches selon les critères du rapport
1359
+ let reportTasks = tasks.filter(task => {
1360
+ const dateMatch = task.date >= startDate && task.date <= endDate;
1361
+ const employeeMatch = !employee || task.employee === employee;
1362
+ return dateMatch && employeeMatch;
1363
+ });
1364
+
1365
+ if (reportTasks.length === 0) {
1366
+ reportContent.innerHTML = '<p class="text-gray-500">Aucune tâche trouvée pour les critères sélectionnés.</p>';
1367
+ exportReport.classList.add('hidden');
1368
+ exportPdf.classList.add('hidden');
1369
+ return;
1370
+ }
1371
+
1372
+ // Regroupe par employé et calcule les totaux
1373
+ const reportData = {};
1374
+ let grandTotalSeconds = 0;
1375
+
1376
+ reportTasks.forEach(task => {
1377
+ if (!reportData[task.employee]) {
1378
+ reportData[task.employee] = {
1379
+ name: task.employee,
1380
+ tasks: [],
1381
+ totalSeconds: 0
1382
+ };
1383
+ }
1384
+
1385
+ reportData[task.employee].tasks.push({
1386
+ projectNumber: task.projectNumber,
1387
+ name: task.name,
1388
+ client: task.client,
1389
+ date: task.date,
1390
+ status: task.status,
1391
+ timeSpent: task.timeSpent,
1392
+ timeEntries: task.timeEntries
1393
+ });
1394
+
1395
+ reportData[task.employee].totalSeconds += task.timeSpent;
1396
+ grandTotalSeconds += task.timeSpent;
1397
+ });
1398
+
1399
+ // Sauvegarde les données du rapport pour l'export PDF
1400
+ currentReportData = {
1401
+ startDate: startDate,
1402
+ endDate: endDate,
1403
+ employee: employee,
1404
+ data: reportData,
1405
+ grandTotalSeconds: grandTotalSeconds
1406
+ };
1407
+
1408
+ // Génère le rapport HTML
1409
+ let reportHTML = '<div class="space-y-6">';
1410
+
1411
+ for (const employee in reportData) {
1412
+ reportHTML += `
1413
+ <div class="border rounded-lg overflow-hidden">
1414
+ <div class="bg-gray-50 px-4 py-3 border-b flex justify-between items-center">
1415
+ <h4 class="font-medium">${employee}</h4>
1416
+ <span class="font-medium">${formatTime(reportData[employee].totalSeconds)}</span>
1417
+ </div>
1418
+ <div class="divide-y">
1419
+ `;
1420
+
1421
+ reportData[employee].tasks.forEach(task => {
1422
+ reportHTML += `
1423
+ <div class="px-4 py-3">
1424
+ <div class="flex justify-between">
1425
+ <div>
1426
+ <div class="font-medium">${task.projectNumber} - ${task.name}</div>
1427
+ <div class="text-sm text-gray-600">${task.client} • ${formatDate(task.date)} • ${task.status}</div>
1428
+ </div>
1429
+ <div class="font-medium">${formatTime(task.timeSpent)}</div>
1430
+ </div>
1431
+ <div class="time-entries mt-2">
1432
+ `;
1433
+
1434
+ task.timeEntries.forEach(entry => {
1435
+ const startTime = new Date(entry.startTime);
1436
+ const endTime = entry.endTime ? new Date(entry.endTime) : null;
1437
+
1438
+ reportHTML += `
1439
+ <div class="time-entry">
1440
+ <span class="font-medium">${formatDateTime(startTime)}</span>
1441
+ ${endTime ? ` - <span class="font-medium">${formatDateTime(endTime)}</span>` : ''}
1442
+ </div>
1443
+ `;
1444
+ });
1445
+
1446
+ reportHTML += `</div></div>`;
1447
+ });
1448
+
1449
+ reportHTML += `</div></div>`;
1450
+ }
1451
+
1452
+ reportHTML += `
1453
+ <div class="bg-gray-50 px-4 py-3 rounded-lg flex justify-between items-center font-medium">
1454
+ <span>Total Général</span>
1455
+ <span>${formatTime(grandTotalSeconds)}</span>
1456
+ </div>
1457
+ </div>
1458
+ `;
1459
+
1460
+ reportContent.innerHTML = reportHTML;
1461
+ exportReport.classList.remove('hidden');
1462
+ exportPdf.classList.remove('hidden');
1463
+ }
1464
+
1465
+ function exportReportToCSV() {
1466
+ const startDate = document.getElementById('reportStartDate').value;
1467
+ const endDate = document.getElementById('reportEndDate').value;
1468
+ const employee = document.getElementById('reportEmployee').value;
1469
+
1470
+ // Filtre les tâches selon les critères du rapport
1471
+ let reportTasks = tasks.filter(task => {
1472
+ const dateMatch = task.date >= startDate && task.date <= endDate;
1473
+ const employeeMatch = !employee || task.employee === employee;
1474
+ return dateMatch && employeeMatch;
1475
+ });
1476
+
1477
+ if (reportTasks.length === 0) return;
1478
+
1479
+ // Crée le contenu CSV
1480
+ let csvContent = "Employé,Numéro Projet,Tâche,Client,Date,Statut,Temps (secondes),Temps (formaté),Début,Fin\n";
1481
+
1482
+ reportTasks.forEach(task => {
1483
+ task.timeEntries.forEach(entry => {
1484
+ const startTime = new Date(entry.startTime);
1485
+ const endTime = entry.endTime ? new Date(entry.endTime) : null;
1486
+ const displayDate = task.timeEntries.length > 0 ? startTime.toISOString().split('T')[0] : task.date;
1487
+ csvContent += `"${task.employee}","${task.projectNumber}","${task.name}","${task.client}","${displayDate}","${task.status}",${task.timeSpent},"${formatTime(task.timeSpent)}","${formatDateTime(startTime)}","${endTime ? formatDateTime(endTime) : ''}"\n`;
1488
+ });
1489
+ });
1490
+
1491
+ // Crée le lien de téléchargement
1492
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
1493
+ const url = URL.createObjectURL(blob);
1494
+ const link = document.createElement('a');
1495
+ link.setAttribute('href', url);
1496
+ link.setAttribute('download', `rapport_temps_${startDate}_à_${endDate}.csv`);
1497
+ link.style.visibility = 'hidden';
1498
+ document.body.appendChild(link);
1499
+ link.click;
1500
+ document.body.removeChild(link);
1501
+ }
1502
+
1503
+ function exportReportToPDF() {
1504
+ if (!currentReportData) return;
1505
+
1506
+ const { jsPDF } = window.jspdf;
1507
+ const doc = new jsPDF();
1508
+
1509
+ // Définit les propriétés du document
1510
+ doc.setProperties({
1511
+ title: `Rapport de Temps ${currentReportData.startDate} à ${currentReportData.endDate}`,
1512
+ subject: 'Rapport de temps des employés',
1513
+ author: 'Time Tracker App',
1514
+ keywords: 'temps, rapport, employés',
1515
+ creator: 'Time Tracker App'
1516
+ });
1517
+
1518
+ // Ajoute le titre
1519
+ doc.setFontSize(18);
1520
+ doc.setTextColor(40);
1521
+ doc.text(`Rapport de Temps`, 105, 20, { align: 'center' });
1522
+
1523
+ // Ajoute la plage de dates
1524
+ doc.setFontSize(12);
1525
+ doc.text(`Période: ${formatDateForPdf(currentReportData.startDate)} à ${formatDateForPdf(currentReportData.endDate)}`, 105, 30, { align: 'center' });
1526
+
1527
+ // Ajoute le filtre employé si applicable
1528
+ if (currentReportData.employee) {
1529
+ doc.text(`Employé: ${currentReportData.employee}`, 105, 37, { align: 'center' });
1530
+ }
1531
+
1532
+ // Ajoute la date de génération
1533
+ const now = new Date();
1534
+ doc.text(`Généré le: ${formatDateTimeForPdf(now)}`, 105, 44, { align: 'center' });
1535
+
1536
+ let yPosition = 60;
1537
+
1538
+ // Ajoute une ligne
1539
+ doc.setDrawColor(200);
1540
+ doc.line(20, yPosition - 10, 190, yPosition - 10);
1541
+
1542
+ // Ajoute les sections par employé
1543
+ for (const employee in currentReportData.data) {
1544
+ const employeeData = currentReportData.data[employee];
1545
+
1546
+ // Vérifie si on a besoin d'une nouvelle page
1547
+ if (yPosition > 250) {
1548
+ doc.addPage();
1549
+ yPosition = 20;
1550
+ }
1551
+
1552
+ // Entête pour l'employé
1553
+ doc.setFontSize(14);
1554
+ doc.setTextColor(40);
1555
+ doc.text(employee, 20, yPosition);
1556
+
1557
+ doc.setFontSize(12);
1558
+ doc.text(`Total: ${formatTime(employeeData.totalSeconds)}`, 180, yPosition, { align: 'right' });
1559
+
1560
+ yPosition += 10;
1561
+
1562
+ // Prépare le tableau des tâches
1563
+ const headers = [["Projet", "Tâche", "Client", "Date", "Temps"]];
1564
+ const rows = [];
1565
+
1566
+ employeeData.tasks.forEach(task => {
1567
+ const execDate = task.timeEntries.length > 0 ? new Date(task.timeEntries[0].startTime) : new Date(task.date);
1568
+ rows.push([
1569
+ task.projectNumber,
1570
+ task.name,
1571
+ task.client,
1572
+ execDate.toLocaleDateString('fr-FR'),
1573
+ formatTime(task.timeSpent)
1574
+ ]);
1575
+ });
1576
+
1577
+ doc.autoTable({
1578
+ startY: yPosition,
1579
+ head: headers,
1580
+ body: rows,
1581
+ margin: { left: 20 },
1582
+ styles: {
1583
+ fontSize: 10,
1584
+ cellPadding: 2,
1585
+ overflow: 'linebreak'
1586
+ },
1587
+ columnStyles: {
1588
+ 0: { cellWidth: 25 },
1589
+ 1: { cellWidth: 45 },
1590
+ 2: { cellWidth: 35 },
1591
+ 3: { cellWidth: 30 },
1592
+ 4: { cellWidth: 20 }
1593
+ }
1594
+ });
1595
+
1596
+ yPosition = doc.lastAutoTable.finalY + 15;
1597
+ }
1598
+
1599
+ // Ajoute le total général
1600
+ doc.setFontSize(14);
1601
+ doc.setTextColor(40);
1602
+ doc.text(`Total Général: ${formatTime(currentReportData.grandTotalSeconds)}`, 105, yPosition, { align: 'center' });
1603
+
1604
+ // Sauvegarde le PDF
1605
+ doc.save(`rapport_temps_${currentReportData.startDate}_à_${currentReportData.endDate}.pdf`);
1606
+ }
1607
+ </script>
1608
+ <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;">
1609
+ 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);">
1610
+ <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank">DeepSite</a> - 🧬
1611
+ <a href="https://enzostvs-deepsite.hf.space?remix=418mattburn/418mattburn" style="color: #fff;text-decoration: underline;" target="_blank">Remix</a>
1612
+ </p>
1613
+ <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=418mattburn/wil-be" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1614
+ </html>