diff --git a/main/forms.py b/main/forms.py index 359f995..e599240 100644 --- a/main/forms.py +++ b/main/forms.py @@ -25,7 +25,6 @@ class CourseForm(forms.ModelForm): return cleaned_data class DossardForm(forms.Form): - csv_file = forms.FileField(label="Fichier CSV (nom;classe)") rows = forms.IntegerField(label="Étiquettes par colonne", min_value=1, initial=2) cols = forms.IntegerField(label="Étiquettes par ligne", min_value=1, initial=2) diff --git a/main/forms_coureur_import.py b/main/forms_coureur_import.py deleted file mode 100644 index e69de29..0000000 diff --git a/main/templates/arrivees_tbody.html b/main/templates/arrivees_tbody.html deleted file mode 100644 index e085c77..0000000 Binary files a/main/templates/arrivees_tbody.html and /dev/null differ diff --git a/main/templates/course_detail.html b/main/templates/course_detail.html index ee696ae..b9314ee 100644 --- a/main/templates/course_detail.html +++ b/main/templates/course_detail.html @@ -74,8 +74,9 @@ - + + @@ -85,6 +86,7 @@ + @@ -94,6 +96,7 @@ + {% endfor %} @@ -127,11 +130,43 @@ socket.onmessage = function(e) { } // Vérifie le format des données reçues let rowData; + function formatHMS(seconds) { + // Copie la logique du templatetag temps_format + if (seconds === undefined || seconds === null || seconds === "") return "--:--:--"; + let total_seconds = 0; + if (typeof seconds === 'string' && seconds.includes(':')) { + // déjà formaté + return seconds; + } + if (!isNaN(Number(seconds))) { + total_seconds = Math.floor(Number(seconds)); + } else { + return "--:--:--"; + } + let h = Math.floor(total_seconds / 3600); + let m = Math.floor((total_seconds % 3600) / 60); + let s = total_seconds % 60; + return (h < 10 ? '0' : '') + h + 'h' + (m < 10 ? '0' : '') + m + 'm' + (s < 10 ? '0' : '') + s + 's'; + } if (Array.isArray(data)) { - rowData = data; + // Si la ligne reçue n'a pas 5 colonnes, on complète avec des vides + rowData = data.slice(0, 5); + while (rowData.length < 5) rowData.push(''); + // Si le temps est en secondes, on le formate + if (rowData.length > 4 && /^\d+$/.test(rowData[4])) { + rowData[4] = formatHMS(rowData[4]); + } } else if (typeof data === 'object' && data !== null) { // Transforme l'objet en tableau dans l'ordre attendu - rowData = [data.rang, data.nom || (data.coureur && data.coureur.nom), data.classe || (data.coureur && data.coureur.classe), data.temps]; + let temps = data.temps; + if (/^\d+$/.test(temps)) temps = formatHMS(temps); + rowData = [ + data.rang, + data.nom || (data.coureur && data.coureur.nom), + data.prenom || (data.coureur && data.coureur.prenom), + data.classe || (data.coureur && data.coureur.classe), + temps + ]; } else { // Format inconnu, ignore return; diff --git a/main/templates/dossards.html b/main/templates/dossards.html index a9511fc..ab36abd 100644 --- a/main/templates/dossards.html +++ b/main/templates/dossards.html @@ -1,32 +1,140 @@ {% extends 'base.html' %} +{% load static %} {% block content %}
-
-
Importer le fichier CSV
+
+
Liste des coureurs
+
+ + +
-
- {% csrf_token %} - {{ form.as_p }} - - - {% if error %} -
{{ error }}
- {% endif %} - {% if progress %} -
{{ progress }}
- {% endif %} - {% if pdf_url %} - T\00e9l\00e9charger le PDF - {% endif %} +
+
RangRang NomPrénom Classe Temps
{{ a.rang }} {{ a.coureur.nom }}{{ a.coureur.prenom }} {{ a.coureur.classe }} {% if a.temps %}{{ a.temps|seconds_to_hms }}{% endif %}
+ + + + + + + + + {% for c in coureurs %} + + + + + + {% endfor %} + +
NomPrénomClasse
{{ c.nom }}{{ c.prenom }}{{ c.classe }}
+ + + + + + + + +{% block extra_js %} + + + + +{% endblock %} {% endblock %} \ No newline at end of file diff --git a/main/templates/scan.html b/main/templates/scan.html index 35b21c5..285f97d 100644 --- a/main/templates/scan.html +++ b/main/templates/scan.html @@ -156,7 +156,9 @@ function onScanSuccess(decodedText, decodedResult) { .then(response => response.text()) .then(html => { document.getElementById('scanResult').innerHTML = html; - window.scanDebounce = false; + setTimeout(function() { + window.scanDebounce = false; + }, 100); // 100ms de délai avant d'autoriser un nouveau scan }) .catch(() => { window.scanDebounce = false; diff --git a/main/templates/scan_result.html b/main/templates/scan_result.html index 93712d8..cc4cfbe 100644 --- a/main/templates/scan_result.html +++ b/main/templates/scan_result.html @@ -1,7 +1,7 @@ {% if result %}
Arrivée enregistrée :
- Nom : {{ result.nom }}
+ Coureur : {{ result.nom }} {{ result.prenom }}
Classe : {{ result.classe }}
Rang : {{ result.rang }}
Temps : {{ result.temps }} diff --git a/main/views.py b/main/views.py index 654b799..8fe5323 100644 --- a/main/views.py +++ b/main/views.py @@ -222,43 +222,43 @@ def dossards_view(request): progress = None pdf_url = None if request.method == 'POST': - form = DossardForm(request.POST, request.FILES) - if form.is_valid(): - csv_file = form.cleaned_data['csv_file'] - rows = form.cleaned_data['rows'] - cols = form.cleaned_data['cols'] - try: - # Build or find Coureur objects for each line. Accept either 'nom;classe' or 'nom;prenom;classe'. - data = [] - for line in csv_file.read().decode('utf-8').splitlines(): - parts = [p.strip() for p in line.split(';') if p.strip() != ''] - if len(parts) == 2: - nom, classe = parts - prenom = '' - elif len(parts) == 3: - nom, prenom, classe = parts - else: - continue - coureur, _ = Coureur.objects.get_or_create(nom=nom, prenom=prenom, classe=classe) - data.append(coureur) - total = len(data) - progress = f"G\u00e9n\u00e9ration des dossards : 0/{total}..." - buffer = generate_dossards_pdf(data, rows, cols) - response = HttpResponse(buffer, content_type='application/pdf') - response['Content-Disposition'] = f'attachment; filename="dossards_{rows}x{cols}.pdf"' - return response - except Exception as e: - error = str(e) - else: - error = "Formulaire invalide." - else: - form = DossardForm() + # Get the list of Coureur IDs from the POST data + coureur_ids_str = request.POST.get('coureur_ids', '') + rows = int(request.POST.get('rows', 2)) + cols = int(request.POST.get('cols', 2)) + try: + coureur_ids = [cid for cid in coureur_ids_str.split(',') if cid.strip()] + if not coureur_ids: + error = "Aucun coureur sélectionné." + else: + # Accept both PKs and names for fallback (legacy/fallback) + coureurs = list(Coureur.objects.filter(id__in=coureur_ids)) + # If fallback: try by name if not found by id + if len(coureurs) < len(coureur_ids): + missing = set(coureur_ids) - set(str(c.id) for c in coureurs) + for nom in missing: + c = Coureur.objects.filter(nom=nom).first() + if c: + coureurs.append(c) + if not coureurs: + error = "Aucun coureur trouvé pour les IDs fournis." + else: + buffer = generate_dossards_pdf(coureurs, rows, cols) + response = HttpResponse(buffer, content_type='application/pdf') + response['Content-Disposition'] = f'attachment; filename="dossards_{rows}x{cols}.pdf"' + return response + except Exception as e: + error = str(e) + # Liste des coureurs et des classes distinctes pour la DataTable + coureurs = Coureur.objects.all().order_by('nom', 'prenom', 'classe') + classes = Coureur.objects.values_list('classe', flat=True).distinct().order_by('classe') return render(request, 'dossards.html', { 'title': 'G\u00e9n\u00e9ration des dossards PDF', - 'form': form, 'error': error, 'progress': progress, - 'pdf_url': pdf_url + 'pdf_url': pdf_url, + 'coureurs': coureurs, + 'classes': classes, }) @@ -274,9 +274,7 @@ def generate_dossards_pdf(data, rows, cols): label_h = (height - 2 * margin) / rows x0, y0 = margin, height - margin - label_h qr_scale = 0.8 # 80% of the label area for the QR - for idx, (nom, classe) in enumerate(data): - # data is now a list of Coureur objects - coureur = data[idx] + for idx, coureur in enumerate(data): col = idx % cols row = (idx // cols) % rows page = idx // (rows * cols)