Flynf commited on
Commit
05781ed
·
verified ·
1 Parent(s): 6f12f78

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +566 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Facturation
3
- emoji: 🔥
4
- colorFrom: pink
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: facturation
3
+ emoji: 🐳
4
+ colorFrom: blue
5
+ colorTo: red
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,566 @@
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>Facturation Horaires</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ .fade-in {
11
+ animation: fadeIn 0.3s ease-in-out;
12
+ }
13
+ @keyframes fadeIn {
14
+ from { opacity: 0; transform: translateY(10px); }
15
+ to { opacity: 1; transform: translateY(0); }
16
+ }
17
+ .invoice-paper {
18
+ background: linear-gradient(to bottom right, #fff, #f9f9f9);
19
+ box-shadow: 0 10px 30px rgba(0,0,0,0.1);
20
+ border: 1px solid #e5e7eb;
21
+ }
22
+ .watermark {
23
+ position: absolute;
24
+ opacity: 0.05;
25
+ font-size: 120px;
26
+ font-weight: bold;
27
+ color: #3b82f6;
28
+ transform: rotate(-30deg);
29
+ pointer-events: none;
30
+ user-select: none;
31
+ z-index: 0;
32
+ }
33
+ #clientList::-webkit-scrollbar {
34
+ width: 6px;
35
+ }
36
+ #clientList::-webkit-scrollbar-track {
37
+ background: #f1f1f1;
38
+ border-radius: 10px;
39
+ }
40
+ #clientList::-webkit-scrollbar-thumb {
41
+ background: #c1c1c1;
42
+ border-radius: 10px;
43
+ }
44
+ #clientList::-webkit-scrollbar-thumb:hover {
45
+ background: #a8a8a8;
46
+ }
47
+ .print-only {
48
+ display: none;
49
+ }
50
+ @media print {
51
+ body * {
52
+ visibility: hidden;
53
+ }
54
+ #invoice-print, #invoice-print * {
55
+ visibility: visible;
56
+ }
57
+ #invoice-print {
58
+ position: absolute;
59
+ left: 0;
60
+ top: 0;
61
+ width: 100%;
62
+ margin: 0;
63
+ padding: 20px;
64
+ box-shadow: none;
65
+ border: none;
66
+ }
67
+ .print-only {
68
+ display: block;
69
+ }
70
+ .no-print {
71
+ display: none !important;
72
+ }
73
+ .watermark {
74
+ opacity: 0.1;
75
+ }
76
+ }
77
+ </style>
78
+ </head>
79
+ <body class="bg-gray-50 min-h-screen">
80
+ <div class="container mx-auto px-4 py-8 no-print">
81
+ <header class="mb-10 text-center">
82
+ <h1 class="text-4xl font-bold text-blue-600 mb-2">Facturation Horaires</h1>
83
+ <p class="text-gray-600">Générez facilement des factures pour vos clients</p>
84
+ </header>
85
+
86
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
87
+ <!-- Section Clients -->
88
+ <div class="lg:col-span-1 bg-white rounded-xl shadow-md p-6">
89
+ <div class="flex justify-between items-center mb-6">
90
+ <h2 class="text-2xl font-semibold text-gray-800">Clients</h2>
91
+ <button id="addClientBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center transition-colors">
92
+ <i class="fas fa-plus mr-2"></i> Ajouter
93
+ </button>
94
+ </div>
95
+
96
+ <!-- Liste des clients -->
97
+ <div id="clientList" class="space-y-3 max-h-96 overflow-y-auto pr-2">
98
+ <!-- Les clients seront ajoutés ici dynamiquement -->
99
+ <div class="text-center py-4 text-gray-500">
100
+ <i class="fas fa-users text-2xl mb-2"></i>
101
+ <p>Aucun client enregistré</p>
102
+ </div>
103
+ </div>
104
+ </div>
105
+
106
+ <!-- Section Facturation -->
107
+ <div class="lg:col-span-2 space-y-8">
108
+ <!-- Formulaire de facturation -->
109
+ <div class="bg-white rounded-xl shadow-md p-6">
110
+ <h2 class="text-2xl font-semibold text-gray-800 mb-6">Créer une facture</h2>
111
+
112
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
113
+ <div>
114
+ <label for="selectClient" class="block text-sm font-medium text-gray-700 mb-1">Client *</label>
115
+ <select id="selectClient" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500">
116
+ <option value="" selected disabled>Sélectionnez un client</option>
117
+ </select>
118
+ <p id="clientError" class="text-red-500 text-xs mt-1 hidden">Veuillez sélectionner un client</p>
119
+ </div>
120
+
121
+ <div>
122
+ <label for="hoursWorked" class="block text-sm font-medium text-gray-700 mb-1">Heures travaillées *</label>
123
+ <input type="number" id="hoursWorked" min="0" step="0.5" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Ex: 5.5">
124
+ <p id="hoursError" class="text-red-500 text-xs mt-1 hidden">Veuillez entrer un nombre valide</p>
125
+ </div>
126
+
127
+ <div>
128
+ <label for="workDate" class="block text-sm font-medium text-gray-700 mb-1">Date de travail *</label>
129
+ <input type="date" id="workDate" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500">
130
+ </div>
131
+
132
+ <div>
133
+ <label for="invoiceDate" class="block text-sm font-medium text-gray-700 mb-1">Date de facture *</label>
134
+ <input type="date" id="invoiceDate" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500">
135
+ </div>
136
+ </div>
137
+
138
+ <div class="mt-6">
139
+ <label for="workDescription" class="block text-sm font-medium text-gray-700 mb-1">Description du travail</label>
140
+ <textarea id="workDescription" rows="3" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Détaillez les travaux effectués..."></textarea>
141
+ </div>
142
+
143
+ <div class="mt-6 flex justify-end">
144
+ <button id="generateInvoiceBtn" class="bg-green-500 hover:bg-green-600 text-white px-6 py-2 rounded-lg flex items-center transition-colors">
145
+ <i class="fas fa-file-invoice-dollar mr-2"></i> Générer la facture
146
+ </button>
147
+ </div>
148
+ </div>
149
+
150
+ <!-- Aperçu de la facture -->
151
+ <div id="invoicePreviewContainer" class="hidden bg-white rounded-xl shadow-md p-6 fade-in">
152
+ <div class="flex justify-between items-center mb-6">
153
+ <h2 class="text-2xl font-semibold text-gray-800">Aperçu de la facture</h2>
154
+ <div class="flex space-x-2">
155
+ <button id="downloadPdfBtn" class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded-lg flex items-center transition-colors">
156
+ <i class="fas fa-file-pdf mr-2"></i> PDF
157
+ </button>
158
+ <button id="printInvoiceBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center transition-colors">
159
+ <i class="fas fa-print mr-2"></i> Imprimer
160
+ </button>
161
+ </div>
162
+ </div>
163
+
164
+ <div id="invoicePreview" class="invoice-paper p-8 rounded-lg relative overflow-hidden">
165
+ <div class="watermark">FACTURE</div>
166
+ <!-- Le contenu de la facture sera généré ici -->
167
+ </div>
168
+ </div>
169
+ </div>
170
+ </div>
171
+ </div>
172
+
173
+ <!-- Modal Ajout Client -->
174
+ <div id="clientModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
175
+ <div class="bg-white rounded-xl shadow-xl p-6 w-full max-w-md fade-in">
176
+ <div class="flex justify-between items-center mb-4">
177
+ <h3 class="text-xl font-semibold text-gray-800">Ajouter un client</h3>
178
+ <button id="closeModalBtn" class="text-gray-500 hover:text-gray-700 transition-colors">
179
+ <i class="fas fa-times"></i>
180
+ </button>
181
+ </div>
182
+
183
+ <form id="clientForm" class="space-y-4">
184
+ <div>
185
+ <label for="clientName" class="block text-sm font-medium text-gray-700 mb-1">Nom complet *</label>
186
+ <input type="text" id="clientName" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Jean Dupont">
187
+ <p id="nameError" class="text-red-500 text-xs mt-1 hidden">Ce champ est obligatoire</p>
188
+ </div>
189
+
190
+ <div>
191
+ <label for="clientEmail" class="block text-sm font-medium text-gray-700 mb-1">Email</label>
192
+ <input type="email" id="clientEmail" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="client@exemple.com">
193
+ </div>
194
+
195
+ <div>
196
+ <label for="clientPhone" class="block text-sm font-medium text-gray-700 mb-1">Téléphone</label>
197
+ <input type="tel" id="clientPhone" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="06 12 34 56 78">
198
+ </div>
199
+
200
+ <div>
201
+ <label for="clientAddress" class="block text-sm font-medium text-gray-700 mb-1">Adresse</label>
202
+ <textarea id="clientAddress" rows="2" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="123 Rue des Exemples, 75000 Paris"></textarea>
203
+ </div>
204
+
205
+ <div>
206
+ <label for="hourlyRate" class="block text-sm font-medium text-gray-700 mb-1">Taux horaire (€) *</label>
207
+ <input type="number" id="hourlyRate" min="0" step="0.01" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="50.00">
208
+ <p id="rateError" class="text-red-500 text-xs mt-1 hidden">Veuillez entrer un taux valide</p>
209
+ </div>
210
+
211
+ <div class="pt-4 flex justify-end space-x-3">
212
+ <button type="button" id="cancelClientBtn" class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-100 transition-colors">Annuler</button>
213
+ <button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition-colors">Enregistrer</button>
214
+ </div>
215
+ </form>
216
+ </div>
217
+ </div>
218
+
219
+ <!-- Contenu pour l'impression -->
220
+ <div id="invoice-print" class="hidden"></div>
221
+
222
+ <script>
223
+ // Stockage des clients
224
+ let clients = JSON.parse(localStorage.getItem('clients')) || [];
225
+
226
+ // Éléments du DOM
227
+ const clientList = document.getElementById('clientList');
228
+ const selectClient = document.getElementById('selectClient');
229
+ const addClientBtn = document.getElementById('addClientBtn');
230
+ const clientModal = document.getElementById('clientModal');
231
+ const closeModalBtn = document.getElementById('closeModalBtn');
232
+ const cancelClientBtn = document.getElementById('cancelClientBtn');
233
+ const clientForm = document.getElementById('clientForm');
234
+ const generateInvoiceBtn = document.getElementById('generateInvoiceBtn');
235
+ const invoicePreviewContainer = document.getElementById('invoicePreviewContainer');
236
+ const invoicePreview = document.getElementById('invoicePreview');
237
+ const printInvoiceBtn = document.getElementById('printInvoiceBtn');
238
+ const downloadPdfBtn = document.getElementById('downloadPdfBtn');
239
+ const invoicePrint = document.getElementById('invoice-print');
240
+
241
+ // Messages d'erreur
242
+ const clientError = document.getElementById('clientError');
243
+ const hoursError = document.getElementById('hoursError');
244
+ const nameError = document.getElementById('nameError');
245
+ const rateError = document.getElementById('rateError');
246
+
247
+ // Initialisation
248
+ document.addEventListener('DOMContentLoaded', function() {
249
+ // Définir les dates par défaut
250
+ const today = new Date().toISOString().split('T')[0];
251
+ document.getElementById('workDate').value = today;
252
+ document.getElementById('invoiceDate').value = today;
253
+
254
+ // Charger les clients
255
+ loadClients();
256
+
257
+ // Événements
258
+ addClientBtn.addEventListener('click', showClientModal);
259
+ closeModalBtn.addEventListener('click', hideClientModal);
260
+ cancelClientBtn.addEventListener('click', hideClientModal);
261
+ clientForm.addEventListener('submit', saveClient);
262
+ generateInvoiceBtn.addEventListener('click', generateInvoice);
263
+ printInvoiceBtn.addEventListener('click', printInvoice);
264
+ downloadPdfBtn.addEventListener('click', downloadPdf);
265
+
266
+ // Empêcher la fermeture du modal en cliquant à l'extérieur
267
+ clientModal.addEventListener('click', function(e) {
268
+ if (e.target === clientModal) {
269
+ hideClientModal();
270
+ }
271
+ });
272
+ });
273
+
274
+ // Fonctions
275
+ function loadClients() {
276
+ clientList.innerHTML = '';
277
+ selectClient.innerHTML = '<option value="" selected disabled>Sélectionnez un client</option>';
278
+
279
+ if (clients.length === 0) {
280
+ clientList.innerHTML = `
281
+ <div class="text-center py-4 text-gray-500">
282
+ <i class="fas fa-users text-2xl mb-2"></i>
283
+ <p>Aucun client enregistré</p>
284
+ </div>
285
+ `;
286
+ return;
287
+ }
288
+
289
+ clients.forEach((client, index) => {
290
+ // Ajouter à la liste des clients
291
+ const clientElement = document.createElement('div');
292
+ clientElement.className = 'bg-gray-50 hover:bg-gray-100 p-4 rounded-lg cursor-pointer transition-colors';
293
+ clientElement.innerHTML = `
294
+ <div class="flex justify-between items-center">
295
+ <div>
296
+ <h3 class="font-medium text-gray-800">${client.name}</h3>
297
+ <p class="text-sm text-gray-500">${client.address ? client.address.split(',')[0] : ''}</p>
298
+ </div>
299
+ <div class="text-right">
300
+ <p class="font-semibold text-blue-600">${parseFloat(client.hourlyRate).toFixed(2)} €/h</p>
301
+ <button class="delete-client text-red-500 hover:text-red-700 text-sm" data-index="${index}">
302
+ <i class="fas fa-trash"></i>
303
+ </button>
304
+ </div>
305
+ </div>
306
+ `;
307
+ clientList.appendChild(clientElement);
308
+
309
+ // Ajouter au select
310
+ const option = document.createElement('option');
311
+ option.value = index;
312
+ option.textContent = `${client.name} (${parseFloat(client.hourlyRate).toFixed(2)} €/h)`;
313
+ selectClient.appendChild(option);
314
+ });
315
+
316
+ // Gérer la suppression des clients
317
+ document.querySelectorAll('.delete-client').forEach(btn => {
318
+ btn.addEventListener('click', function(e) {
319
+ e.stopPropagation();
320
+ const index = parseInt(this.getAttribute('data-index'));
321
+ deleteClient(index);
322
+ });
323
+ });
324
+
325
+ // Gérer la sélection d'un client dans la liste
326
+ document.querySelectorAll('#clientList > div').forEach((el, index) => {
327
+ if (index < clients.length) {
328
+ el.addEventListener('click', () => {
329
+ selectClient.value = index;
330
+ clientError.classList.add('hidden');
331
+ });
332
+ }
333
+ });
334
+ }
335
+
336
+ function showClientModal() {
337
+ clientModal.classList.remove('hidden');
338
+ document.getElementById('clientName').focus();
339
+ clientForm.reset();
340
+ nameError.classList.add('hidden');
341
+ rateError.classList.add('hidden');
342
+ }
343
+
344
+ function hideClientModal() {
345
+ clientModal.classList.add('hidden');
346
+ }
347
+
348
+ function saveClient(e) {
349
+ e.preventDefault();
350
+
351
+ const name = document.getElementById('clientName').value.trim();
352
+ const hourlyRate = document.getElementById('hourlyRate').value;
353
+
354
+ // Validation
355
+ let isValid = true;
356
+
357
+ if (!name) {
358
+ nameError.classList.remove('hidden');
359
+ isValid = false;
360
+ } else {
361
+ nameError.classList.add('hidden');
362
+ }
363
+
364
+ if (!hourlyRate || isNaN(hourlyRate) || parseFloat(hourlyRate) <= 0) {
365
+ rateError.classList.remove('hidden');
366
+ isValid = false;
367
+ } else {
368
+ rateError.classList.add('hidden');
369
+ }
370
+
371
+ if (!isValid) return;
372
+
373
+ const client = {
374
+ name: name,
375
+ email: document.getElementById('clientEmail').value.trim(),
376
+ phone: document.getElementById('clientPhone').value.trim(),
377
+ address: document.getElementById('clientAddress').value.trim(),
378
+ hourlyRate: parseFloat(hourlyRate).toFixed(2)
379
+ };
380
+
381
+ clients.push(client);
382
+ localStorage.setItem('clients', JSON.stringify(clients));
383
+
384
+ hideClientModal();
385
+ loadClients();
386
+
387
+ // Sélectionner le nouveau client
388
+ selectClient.value = clients.length - 1;
389
+ clientError.classList.add('hidden');
390
+ }
391
+
392
+ function deleteClient(index) {
393
+ if (confirm('Voulez-vous vraiment supprimer ce client ?')) {
394
+ clients.splice(index, 1);
395
+ localStorage.setItem('clients', JSON.stringify(clients));
396
+ loadClients();
397
+
398
+ // Masquer l'aperçu si le client supprimé était sélectionné
399
+ if (selectClient.value === index.toString()) {
400
+ selectClient.value = '';
401
+ invoicePreviewContainer.classList.add('hidden');
402
+ }
403
+ }
404
+ }
405
+
406
+ function generateInvoice() {
407
+ const clientIndex = selectClient.value;
408
+ const hoursWorked = parseFloat(document.getElementById('hoursWorked').value);
409
+ const workDate = document.getElementById('workDate').value;
410
+ const invoiceDate = document.getElementById('invoiceDate').value;
411
+ const workDescription = document.getElementById('workDescription').value.trim();
412
+
413
+ // Validation
414
+ let isValid = true;
415
+
416
+ if (clientIndex === '' || clientIndex === null) {
417
+ clientError.classList.remove('hidden');
418
+ isValid = false;
419
+ } else {
420
+ clientError.classList.add('hidden');
421
+ }
422
+
423
+ if (isNaN(hoursWorked) || hoursWorked <= 0) {
424
+ hoursError.classList.remove('hidden');
425
+ isValid = false;
426
+ } else {
427
+ hoursError.classList.add('hidden');
428
+ }
429
+
430
+ if (!workDate) {
431
+ alert('Veuillez sélectionner une date de travail');
432
+ return;
433
+ }
434
+
435
+ if (!invoiceDate) {
436
+ alert('Veuillez sélectionner une date de facture');
437
+ return;
438
+ }
439
+
440
+ if (!isValid) return;
441
+
442
+ const client = clients[clientIndex];
443
+ const totalAmount = (hoursWorked * parseFloat(client.hourlyRate)).toFixed(2);
444
+
445
+ // Formater les dates
446
+ const formattedWorkDate = formatDate(workDate);
447
+ const formattedInvoiceDate = formatDate(invoiceDate);
448
+
449
+ // Générer la facture
450
+ const invoiceContent = `
451
+ <div class="watermark">FACTURE</div>
452
+ <div class="flex justify-between items-start mb-12">
453
+ <div>
454
+ <h1 class="text-3xl font-bold text-blue-600 mb-1">FACTURE</h1>
455
+ <p class="text-gray-500">N° ${generateInvoiceNumber()}</p>
456
+ </div>
457
+ <div class="text-right">
458
+ <p class="text-gray-700">Date: <span class="font-medium">${formattedInvoiceDate}</span></p>
459
+ <p class="text-gray-700">Échéance: <span class="font-medium">${formattedInvoiceDate}</span></p>
460
+ </div>
461
+ </div>
462
+
463
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12">
464
+ <div>
465
+ <h2 class="text-lg font-semibold text-gray-800 mb-2">De:</h2>
466
+ <p class="font-bold">Votre Entreprise</p>
467
+ <p>123 Rue de l'Entreprise</p>
468
+ <p>75000 Paris, France</p>
469
+ <p class="mt-2">SIRET: 123 456 789 00010</p>
470
+ <p>TVA: FR12 345678901</p>
471
+ </div>
472
+
473
+ <div>
474
+ <h2 class="text-lg font-semibold text-gray-800 mb-2">À:</h2>
475
+ <p class="font-bold">${client.name}</p>
476
+ ${client.address ? `<p>${client.address.replace(/\n/g, '<br>')}</p>` : ''}
477
+ ${client.email ? `<p class="mt-2">Email: ${client.email}</p>` : ''}
478
+ ${client.phone ? `<p>Téléphone: ${client.phone}</p>` : ''}
479
+ </div>
480
+ </div>
481
+
482
+ <div class="mb-8">
483
+ <h2 class="text-lg font-semibold text-gray-800 mb-4">Détails de la facture</h2>
484
+ <div class="overflow-x-auto">
485
+ <table class="w-full border-collapse">
486
+ <thead>
487
+ <tr class="bg-gray-100">
488
+ <th class="text-left py-3 px-4 font-semibold text-gray-700 border-b">Description</th>
489
+ <th class="text-right py-3 px-4 font-semibold text-gray-700 border-b">Taux horaire</th>
490
+ <th class="text-right py-3 px-4 font-semibold text-gray-700 border-b">Heures</th>
491
+ <th class="text-right py-3 px-4 font-semibold text-gray-700 border-b">Montant</th>
492
+ </tr>
493
+ </thead>
494
+ <tbody>
495
+ <tr>
496
+ <td class="py-3 px-4 border-b text-gray-700">
497
+ ${workDescription || 'Prestation de services'}
498
+ <p class="text-sm text-gray-500 mt-1">Date: ${formattedWorkDate}</p>
499
+ </td>
500
+ <td class="py-3 px-4 border-b text-right text-gray-700">${parseFloat(client.hourlyRate).toFixed(2)} €</td>
501
+ <td class="py-3 px-4 border-b text-right text-gray-700">${hoursWorked.toFixed(1)}</td>
502
+ <td class="py-3 px-4 border-b text-right font-medium text-gray-800">${totalAmount} €</td>
503
+ </tr>
504
+ </tbody>
505
+ <tfoot>
506
+ <tr>
507
+ <td colspan="3" class="py-3 px-4 text-right font-semibold text-gray-700">Total HT</td>
508
+ <td class="py-3 px-4 text-right font-bold text-gray-800">${totalAmount} €</td>
509
+ </tr>
510
+ <tr>
511
+ <td colspan="3" class="py-3 px-4 text-right font-semibold text-gray-700">TVA (0%)</td>
512
+ <td class="py-3 px-4 text-right font-bold text-gray-800">0,00 €</td>
513
+ </tr>
514
+ <tr>
515
+ <td colspan="3" class="py-3 px-4 text-right font-semibold text-gray-700 border-t-2 border-gray-200">Total TTC</td>
516
+ <td class="py-3 px-4 text-right font-bold text-gray-800 border-t-2 border-gray-200">${totalAmount} €</td>
517
+ </tr>
518
+ </tfoot>
519
+ </table>
520
+ </div>
521
+ </div>
522
+
523
+ <div class="text-sm text-gray-500 pt-8 border-t border-gray-200">
524
+ <p class="font-semibold mb-2">Conditions de paiement:</p>
525
+ <p>Paiement à réception de la facture par virement bancaire.</p>
526
+ <p class="mt-4">IBAN: FR76 1234 5678 9012 3456 7890 123</p>
527
+ <p>BIC: ABCDEFGH123</p>
528
+ <p class="mt-4">En cas de retard de paiement, des pénalités de 1,5% par mois seront appliquées.</p>
529
+ </div>
530
+
531
+ <div class="print-only mt-12 pt-4 border-t border-gray-200 text-center text-sm text-gray-500">
532
+ <p>Merci pour votre confiance</p>
533
+ </div>
534
+ `;
535
+
536
+ invoicePreview.innerHTML = invoiceContent;
537
+ invoicePrint.innerHTML = invoiceContent;
538
+ invoicePreviewContainer.classList.remove('hidden');
539
+ invoicePreviewContainer.scrollIntoView({ behavior: 'smooth' });
540
+ }
541
+
542
+ function printInvoice() {
543
+ window.print();
544
+ }
545
+
546
+ function downloadPdf() {
547
+ alert("Fonctionnalité PDF à implémenter. Pour l'instant, vous pouvez utiliser l'impression et sélectionner 'Enregistrer au format PDF' comme imprimante.");
548
+ }
549
+
550
+ function formatDate(dateString) {
551
+ if (!dateString) return '';
552
+ const options = { day: '2-digit', month: '2-digit', year: 'numeric' };
553
+ return new Date(dateString).toLocaleDateString('fr-FR', options);
554
+ }
555
+
556
+ function generateInvoiceNumber() {
557
+ const now = new Date();
558
+ const year = now.getFullYear();
559
+ const month = String(now.getMonth() + 1).padStart(2, '0');
560
+ const day = String(now.getDate()).padStart(2, '0');
561
+ const random = Math.floor(Math.random() * 1000);
562
+ return `FACT-${year}${month}${day}-${random}`;
563
+ }
564
+ </script>
565
+ <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=Flynf/facturation" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
566
+ </html>