import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:intl/intl.dart'; class EventResultPage extends StatelessWidget { final String eventId; final String eventName; EventResultPage({required this.eventId, required this.eventName}); final Color primaryBlue = Color(0xFF42A5F5); final Color lightBlue = Color(0xFFBBDEFB); final Color darkBlue = Color(0xFF1976D2); final Color successGreen = Colors.green.shade700; final Color warningOrange = Colors.orange.shade700; final Color dangerRed = Colors.red.shade700; @override Widget build(BuildContext context) { final docRef = FirebaseFirestore.instance.collection('events').doc(eventId); return Scaffold( backgroundColor: Colors.grey[50], appBar: AppBar( title: Text( 'Hasil Event: $eventName', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), ), centerTitle: true, backgroundColor: primaryBlue, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20), ), ), iconTheme: IconThemeData(color: Colors.white), elevation: 5, ), body: StreamBuilder( stream: docRef.snapshots(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Center(child: CircularProgressIndicator(color: primaryBlue)); } if (snapshot.hasError) { return Center(child: Text('Error: ${snapshot.error}', style: TextStyle(color: dangerRed))); } if (!snapshot.hasData || !snapshot.data!.exists) { return Center(child: Text('Event tidak ditemukan.', style: TextStyle(color: Colors.grey[600]))); } final data = snapshot.data!.data() as Map?; if (data == null || data['participants'] == null) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.people_alt_outlined, size: 60, color: Colors.grey[400]), SizedBox(height: 16), Text('Belum ada peserta yang terdaftar untuk event ini.', style: TextStyle(fontSize: 16, color: Colors.grey[600])), ], ), ); } final participants = data['participants'] as Map; if (participants.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.people_alt_outlined, size: 60, color: Colors.grey[400]), SizedBox(height: 16), Text('Belum ada peserta yang terdaftar untuk event ini.', style: TextStyle(fontSize: 16, color: Colors.grey[600])), ], ), ); } List> sortedParticipants = participants.entries.toList(); sortedParticipants.sort((a, b) { final pA = a.value as Map; final pB = b.value as Map; final finishedA = pA['finished'] == true; final finishedB = pB['finished'] == true; if (finishedA && !finishedB) return -1; if (!finishedA && finishedB) return 1; if (finishedA && finishedB) { final timeA = _parseTimeToSeconds(pA['total_time']); final timeB = _parseTimeToSeconds(pB['total_time']); return timeA.compareTo(timeB); } final lapsA = pA['laps'] ?? 0; final lapsB = pB['laps'] ?? 0; return lapsB.compareTo(lapsA); }); return ListView.builder( padding: EdgeInsets.all(16), itemCount: sortedParticipants.length, itemBuilder: (context, index) { final entry = sortedParticipants[index]; final p = entry.value as Map; final name = p['name'] ?? 'Peserta Tidak Dikenal'; final laps = p['laps'] ?? 0; final finished = p['finished'] == true; final totalTime = p['total_time'] ?? '-'; final rfidUid = p['rfid_uid'] ?? '-'; final lapDurations = p['lap_durations'] as Map? ?? {}; String participantStatus = finished ? 'Selesai' : 'Berlangsung'; Color participantStatusColor = finished ? successGreen : warningOrange; String formattedTotalTime = '-'; if (finished && totalTime != '-') { try { final duration = Duration(milliseconds: _parseMilliseconds(totalTime)); formattedTotalTime = _formatDuration(duration); } catch (e) { formattedTotalTime = totalTime; } } List kecepatanWidgets = []; final sortedLapKeys = lapDurations.keys.toList()..sort((a, b) { final intA = int.parse(a.split('_')[1]); final intB = int.parse(b.split('_')[1]); return intA.compareTo(intB); }); for (String lapKey in sortedLapKeys) { final dynamic durasiDetikRaw = lapDurations[lapKey]; double durasiDetik = 0.0; if (durasiDetikRaw is int) { durasiDetik = durasiDetikRaw.toDouble(); } else if (durasiDetikRaw is double) { durasiDetik = durasiDetikRaw; } String formattedDuration = '${durasiDetik.toStringAsFixed(2)} s'; if (durasiDetik > 0) { final kecepatan = 400 / durasiDetik; kecepatanWidgets.add( Row( children: [ SizedBox(width: 24), Text( "Lap ${lapKey.split('_')[1]}: ${kecepatan.toStringAsFixed(2)} m/s ", style: TextStyle(fontSize: 13, color: Colors.grey[800]), ), Text( "($formattedDuration)", style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), ], ), ); } else { kecepatanWidgets.add( Padding( padding: const EdgeInsets.only(left: 24.0), child: Text( "Lap ${lapKey.split('_')[1]}: Durasi tidak valid", style: TextStyle(fontSize: 13, color: Colors.grey[500], fontStyle: FontStyle.italic), ), ), ); } } return Card( margin: EdgeInsets.only(bottom: 16), elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), color: Colors.white, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Flexible( child: Text( name, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: darkBlue, ), overflow: TextOverflow.ellipsis, ), ), Container( padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: participantStatusColor.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Text( participantStatus, style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: participantStatusColor, ), ), ), ], ), SizedBox(height: 8), Divider(color: Colors.grey[200]), SizedBox(height: 8), _buildInfoRow("RFID Tag", rfidUid, darkBlue), _buildInfoRow("Putaran Selesai", "$laps", darkBlue), if (finished) _buildInfoRow("Waktu Total", formattedTotalTime, darkBlue), if (kecepatanWidgets.isNotEmpty) ...[ SizedBox(height: 12), Text( "Detail Kecepatan per Lap:", style: TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: darkBlue, ), ), SizedBox(height: 8), ...kecepatanWidgets, ], // Tombol hapus peserta SizedBox(height: 16), Align( alignment: Alignment.centerRight, child: IconButton( // <-- DIUBAH DARI TextButton.icon MENJADI IconButton icon: Icon(Icons.delete_outline, color: dangerRed, size: 24), // Ukuran ikon diperbesar sedikit tooltip: 'Hapus Peserta', // Tooltip tetap ada saat ditekan lama onPressed: () async { final confirm = await showDialog( context: context, builder: (_) => AlertDialog( title: Text('Konfirmasi Hapus Peserta', style: TextStyle(color: dangerRed)), content: Text('Apakah Anda yakin ingin menghapus peserta "$name" dari event ini?', style: TextStyle(color: Colors.grey[800])), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: Text('Batal', style: TextStyle(color: primaryBlue)), ), TextButton( onPressed: () => Navigator.pop(context, true), child: Text('Hapus', style: TextStyle(color: dangerRed)), ), ], ), ); if (confirm == true) { try { await docRef.update({ 'participants.${entry.key}': FieldValue.delete(), }); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Peserta "$name" berhasil dihapus.'), backgroundColor: Colors.green), ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Gagal menghapus peserta: $e'), backgroundColor: Colors.red), ); print('Error deleting participant: $e'); } } }, ), ), ], ), ), ); }, ); }, ), ); } // Helper widget untuk baris informasi yang clean tanpa ikon Widget _buildInfoRow(String label, String value, Color valueColor) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( flex: 2, child: Text( "$label:", style: TextStyle(fontWeight: FontWeight.w500, fontSize: 14, color: Colors.grey[700]), ), ), Expanded( flex: 3, child: Text( value, style: TextStyle(fontSize: 15, color: valueColor), overflow: TextOverflow.ellipsis, ), ), ], ), ); } // Helper untuk parsing waktu (misal "00:01:23.456" menjadi milidetik) int _parseMilliseconds(String timeString) { if (timeString == '-') return 0; final parts = timeString.split(':'); if (parts.length < 3) return 0; // Invalid format int hours = int.parse(parts[0]); int minutes = int.parse(parts[1]); double secondsWithMs = double.parse(parts[2]); int totalMilliseconds = ((hours * 3600 + minutes * 60 + secondsWithMs) * 1000).toInt(); return totalMilliseconds; } // Helper untuk format total_time dari milidetik String _formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, "0"); String threeDigits(int n) => n.toString().padLeft(3, "0"); String minutes = twoDigits(duration.inMinutes.remainder(60)); String seconds = twoDigits(duration.inSeconds.remainder(60)); String milliseconds = threeDigits(duration.inMilliseconds.remainder(1000)); String hours = twoDigits(duration.inHours); if (duration.inHours > 0) { return "$hours:$minutes:$seconds.$milliseconds"; } else { return "$minutes:$seconds.$milliseconds"; } } // Helper untuk mengonversi string waktu (HH:MM:SS.ms) ke total detik (untuk sorting) double _parseTimeToSeconds(String timeString) { if (timeString == '-') return double.infinity; try { final parts = timeString.split(':'); if (parts.length != 3) return double.infinity; final hours = int.parse(parts[0]); final minutes = int.parse(parts[1]); final secondsParts = parts[2].split('.'); final seconds = int.parse(secondsParts[0]); final milliseconds = secondsParts.length > 1 ? int.parse(secondsParts[1]) : 0; return (hours * 3600 + minutes * 60 + seconds + milliseconds / 1000).toDouble(); } catch (e) { return double.infinity; } } }