diff --git a/Code.gs b/Code.gs index 797423cb633d3c563e91feeac4821ceed8249e50..0704d7061b95728282dd2b11b7a94420652693bc 100644 --- a/Code.gs +++ b/Code.gs @@ -31,6 +31,13 @@ function doPost(e) { // Timestamp from the scanner app var scanTime = new Date(data.timestamp); + // --- NEW LOGIC: DETECT PASSAGE TYPE --- + // If leoId contains "MANUEL", it means manual entry (card forgotten) + var passageType = "Carte (NFC)"; + if (String(leoId).toUpperCase().indexOf("MANUEL") !== -1) { + passageType = "Oubli (Saisie Manuelle)"; + } + // 1. ROBUST SEARCH: Find if student exists var rowIndex = findRowIndex(sheet, studentId); var status = ""; @@ -45,7 +52,8 @@ function doPost(e) { prenom, // D: First Name nom, // E: Last Name formatTime(scanTime), // F: Entry Time (Fixed Column 6) - "" // G: Exit Time (Empty initially) + "", // G: Exit Time (Empty initially) + passageType // H: Type (New Column 8) ]; sheet.appendRow(newRow); status = "Entrée (Start)"; @@ -64,10 +72,15 @@ function doPost(e) { results.push({ "id": studentId, "name": prenom + " " + nom, - "status": status + "status": status, + "type": passageType }); }); + // --- VISUAL IMPROVEMENT --- + // Auto-resize columns A to H (1 to 8) to fit content perfectly + sheet.autoResizeColumns(1, 8); + return ContentService.createTextOutput(JSON.stringify({ "status": "success", "processed": results diff --git a/lib/components/sync_status_indicator.dart b/lib/components/sync_status_indicator.dart index 3be0e0fd3ddf25cac054d94058a7536beac8d8d0..0f924b20954d505b5ecfad6f0795c226e323ff0a 100644 --- a/lib/components/sync_status_indicator.dart +++ b/lib/components/sync_status_indicator.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; +import 'package:intl/intl.dart'; import '../utils/locator.dart'; import '../services/attendance_service.dart'; import '../models/student.dart'; @@ -9,7 +10,6 @@ import '../models/student.dart'; class SyncStatusIndicator extends StatelessWidget { const SyncStatusIndicator({super.key}); - /// Generates a CSV string from the queue and triggers the system share sheet Future _exportQueueToCsv(BuildContext context) async { final service = getIt(); final List students = service.queueSnapshot; @@ -19,19 +19,24 @@ class SyncStatusIndicator extends StatelessWidget { try { // Build CSV Content using StringBuffer for performance final StringBuffer csvBuffer = StringBuffer(); - // Header - csvBuffer.writeln("Prenom,Nom,No Etudiant,No Leo,Timestamp"); + + // Header: Matches Google Sheet columns exactly + csvBuffer.writeln("Date,N° Etudiant,N° Leo (UID),Prénom,Nom,Heure Entrée,Heure Sortie,Type"); - // Rows for (final s in students) { // Data Sanitization: Prevent CSV injection/breakage by removing commas final p = s.firstName.replaceAll(',', ''); final n = s.lastName.replaceAll(',', ''); final id = s.studentId; final leo = s.leoId; - final time = s.entryTime?.toIso8601String() ?? ""; + + final DateTime ts = s.entryTime ?? DateTime.now(); + final String dateStr = DateFormat('dd/MM/yyyy').format(ts); + final String timeStr = DateFormat('HH:mm:ss').format(ts); + + final String type = s.scanType ?? "Inconnu"; - csvBuffer.writeln("$p,$n,$id,$leo,$time"); + csvBuffer.writeln("$dateStr,$id,$leo,$p,$n,$timeStr,,$type"); } // IO Operation: Write to temp storage @@ -43,11 +48,12 @@ class SyncStatusIndicator extends StatelessWidget { // Trigger Native Share Sheet (Works on Mobile & Desktop) await Share.shareXFiles([ XFile(path), - ], text: 'Backup Émargement - ${students.length} étudiants'); + ], text: 'Backup Attendance - ${students.length} students'); + } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Erreur d'export : $e")), + SnackBar(content: Text("Export Error: $e")) ); } } @@ -73,7 +79,6 @@ class SyncStatusIndicator extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Status Row Row( children: [ Icon( @@ -85,8 +90,8 @@ class SyncStatusIndicator extends StatelessWidget { Expanded( child: Text( isSynced - ? "Toutes les données sont synchronisées." - : "Attention: $count étudiant(s) en attente.", + ? "All data synced." + : "Warning: $count student(s) pending.", style: TextStyle( color: isSynced ? Colors.green[900] @@ -97,11 +102,6 @@ class SyncStatusIndicator extends StatelessWidget { ), ], ), - - // Export Button (Only visible if not synced) - // If !isSynced, scatter the widgets in the given - // array onto the scope of the parent widget, that - // is what the ...[] syntax means if (!isSynced) ...[ const SizedBox(height: 10), // SizedBox(width: double.infinity) makes the button stretch @@ -111,7 +111,7 @@ class SyncStatusIndicator extends StatelessWidget { onPressed: () => _exportQueueToCsv(context), icon: const Icon(Icons.save_alt, color: Colors.black87), label: const Text( - "SAUVEGARDER LE CSV DE SECOURS", + "SAVE EMERGENCY CSV", style: TextStyle(color: Colors.black87), ), style: OutlinedButton.styleFrom( @@ -123,7 +123,7 @@ class SyncStatusIndicator extends StatelessWidget { const Padding( padding: EdgeInsets.only(top: 4.0), child: Text( - "Utilisez ce bouton si la connexion échoue pour ne pas perdre les scans.", + "Use this if connection fails to save scans manually.", style: TextStyle( fontSize: 11, fontStyle: FontStyle.italic,