import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_database/firebase_database.dart'; import 'package:flutter/material.dart'; import 'dart:async'; class EventDetailPage extends StatefulWidget { final String eventId; final String eventName; EventDetailPage({required this.eventId, required this.eventName}); @override _EventDetailPageState createState() => _EventDetailPageState(); } class _EventDetailPageState extends State { final FirebaseFirestore _firestore = FirebaseFirestore.instance; late FirebaseDatabase _rtdb; late DatabaseReference _eventStatusRef; late DatabaseReference _scanResultRef; late DatabaseReference _eventActivityDataRef; // Map untuk menyimpan status finished dan current laps DARI RTDB (Hanya relevan saat event aktif) Map _liveParticipantFinishedStatus = {}; Map _liveParticipantCurrentLaps = {}; // Map untuk mapping UID RFID ke Firestore userId Map _rfidUidToUserIdMap = {}; // List untuk menyimpan subscriptions agar bisa di-cancel saat dispose final List _rtdbSubscriptions = []; // Variabel untuk menyimpan status event global dari RTDB (hanya untuk kontrol internal sementara) bool _rtdbEventStarted = false; bool _rtdbEventFinished = false; // StreamSubscription untuk Realtime Database listeners StreamSubscription? _activityDataSubscription; StreamSubscription? _finishedSubscription; StreamSubscription? _lapsSubscription; StreamSubscription? _scanResultSubscription; // Warna utama yang terinspirasi dari gambar Anda final Color primaryBlue = Color(0xFF42A5F5); // Biru muda yang cerah final Color lightBlue = Color(0xFFBBDEFB); // Biru yang lebih terang untuk background final Color darkBlue = Color(0xFF1976D2); // Biru yang lebih gelap untuk teks/ikon @override void initState() { super.initState(); _rtdb = FirebaseDatabase.instanceFor( app: Firebase.app(), databaseURL: "https://ta-running-default-rtdb.asia-southeast1.firebasedatabase.app", ); _eventStatusRef = _rtdb.ref('event_status/${widget.eventId}'); _scanResultRef = _rtdb.ref('scan_result/${widget.eventId}'); _eventActivityDataRef = _rtdb.ref('event_activity_data/${widget.eventId}'); _preloadRfidToUserIdsMap().then((_) { _listenToRealtimeEventStatus(); // Mendengarkan status global dari RTDB (untuk internal saja) }); } @override void dispose() { for (var subscription in _rtdbSubscriptions) { subscription.cancel(); } super.dispose(); } Future _preloadRfidToUserIdsMap() async { try { final eventDoc = await _firestore.collection('events').doc(widget.eventId).get(); if (eventDoc.exists) { final participantsData = eventDoc.data()?['participants'] as Map?; if (participantsData != null) { participantsData.forEach((userId, data) { final rfidUid = data['rfid_uid'] as String?; if (rfidUid != null) { _rfidUidToUserIdMap[rfidUid] = userId; } }); print("Preloaded RFID UID to User ID Map: $_rfidUidToUserIdMap"); } } } catch (e) { print("Error preloading RFID UID to User ID map: $e"); } } // Listener Realtime Database untuk status event global (hanya untuk tujuan internal) void _listenToRealtimeEventStatus() { _rtdbSubscriptions.add(_eventStatusRef.onValue.listen((event) async { if (event.snapshot.exists && event.snapshot.value != null) { final eventStatusData = event.snapshot.value as Map; setState(() { _rtdbEventStarted = eventStatusData['started'] ?? false; _rtdbEventFinished = eventStatusData['finished'] ?? false; }); // Ambil status permanen dari Firestore untuk perbandingan final firestoreDoc = await _firestore.collection('events').doc(widget.eventId).get(); final firestoreData = firestoreDoc.data(); bool isEventFinishedPermanently = firestoreData?['is_finished_permanently'] ?? false; // Jika event baru saja selesai di RTDB oleh ESP32, dan belum di Firestore if (_rtdbEventFinished && !isEventFinishedPermanently) { _endEventPermanentlyFromRTDB(); } } else { setState(() { _rtdbEventStarted = false; _rtdbEventFinished = false; }); } print("Internal RTDB Event Status: Started=$_rtdbEventStarted, Finished=$_rtdbEventFinished"); })); } // Fungsi baru: jika RTDB menandakan selesai, dan Firestore belum, akhiri permanen Future _endEventPermanentlyFromRTDB() async { print("RTDB menandakan event selesai, mengakhiri secara permanen di Firestore."); try { await _firestore.collection('events').doc(widget.eventId).update({ 'is_started_permanently': false, 'is_finished_permanently': true, }); // Bersihkan RTDB setelah status permanen di Firestore diupdate await _rtdb.ref('event_activity_data/${widget.eventId}').remove(); await _rtdb.ref('event_status/${widget.eventId}').remove(); await _rtdb.ref('scan_result/${widget.eventId}').remove(); print("✅ Event diakhiri secara permanen di Firestore dan RTDB dibersihkan."); } catch (e) { print("❌ Gagal mengakhiri event secara permanen dari RTDB: $e"); } } // Fungsi untuk mengelola listener Realtime Database void _manageRealtimeListeners(bool activate) { if (activate) { // Aktifkan listener aktivitas peserta jika belum ada if (_activityDataSubscription == null) { print("Mengaktifkan Realtime Database activity listeners..."); // Gunakan onChildAdded untuk inisialisasi awal dan onChildChanged untuk update _activityDataSubscription = _eventActivityDataRef.onChildAdded.listen((event) { final uidTag = event.snapshot.key; if (uidTag != null) { // Subscribe ke 'finished' _finishedSubscription = _eventActivityDataRef.child(uidTag).child('finished').onValue.listen((finishedEvent) async { if (finishedEvent.snapshot.exists && finishedEvent.snapshot.value != null) { final finished = finishedEvent.snapshot.value as bool; setState(() { _liveParticipantFinishedStatus[uidTag] = finished; }); final userId = _rfidUidToUserIdMap[uidTag]; if (userId != null) { try { await _firestore.collection('events').doc(widget.eventId).update({ 'participants.$userId.finished': finished, }); if (finished) { final totalTimeSnapshot = await _eventActivityDataRef.child(uidTag).child('duration').get(); if (totalTimeSnapshot.exists && totalTimeSnapshot.value != null) { final totalTimeMs = totalTimeSnapshot.value as int; String formattedTime = _formatDuration(Duration(milliseconds: totalTimeMs)); await _firestore.collection('events').doc(widget.eventId).update({ 'participants.$userId.total_time': formattedTime, }); } } } catch (e) { print("❌ Gagal update Firestore participants.$userId.finished/total_time: $e"); } } } }); _rtdbSubscriptions.add(_finishedSubscription!); // Subscribe ke 'laps' _lapsSubscription = _eventActivityDataRef.child(uidTag).child('laps').onValue.listen((lapsEvent) async { if (lapsEvent.snapshot.exists && lapsEvent.snapshot.value != null) { final lapsData = lapsEvent.snapshot.value as Map; final lapsCount = lapsData.length; setState(() { _liveParticipantCurrentLaps[uidTag] = lapsCount; }); final userId = _rfidUidToUserIdMap[uidTag]; if (userId != null) { try { await _firestore.collection('events').doc(widget.eventId).update({ 'participants.$userId.laps': lapsCount > 0 ? lapsCount - 1 : 0, }); Map currentTimestamps = {}; lapsData.forEach((key, value) { if (value is Map && value.containsKey('timestamp')) { currentTimestamps[key.toString()] = DateTime.parse(value['timestamp']); } }); Map lapDurations = {}; final sortedKeys = currentTimestamps.keys.toList()..sort((a, b) => a.compareTo(b)); for (int i = 1; i < sortedKeys.length; i++) { final prevTimestamp = currentTimestamps[sortedKeys[i - 1]]; final currentTimestamp = currentTimestamps[sortedKeys[i]]; if (prevTimestamp != null && currentTimestamp != null) { final durationSeconds = currentTimestamp.difference(prevTimestamp).inMilliseconds / 1000.0; lapDurations['lap_${i}'] = durationSeconds; } } if (lapDurations.isNotEmpty) { await _firestore.collection('events').doc(widget.eventId).update({ 'participants.$userId.lap_durations': lapDurations, }); } } catch (e) { print("❌ Gagal update Firestore participants.$userId.laps atau lap_durations: $e"); } } } }); _rtdbSubscriptions.add(_lapsSubscription!); } }); _rtdbSubscriptions.add(_activityDataSubscription!); } // Aktifkan listener scan RFID jika belum aktif if (_scanResultSubscription == null) { print("Mengaktifkan scan_result listener..."); final scanRef = _rtdb.ref('scan_result/${widget.eventId}'); _scanResultSubscription = scanRef.onChildAdded.listen((event) { final scannedUID = event.snapshot.key; final value = event.snapshot.value; if (scannedUID != null && value == true) { processUID(scannedUID, widget.eventId); } }); _rtdbSubscriptions.add(_scanResultSubscription!); // Panggil processUID untuk data yang sudah ada saat ini juga (saat listener diaktifkan) scanRef.get().then((snapshot) { if (snapshot.exists) { final data = snapshot.value as Map; data.forEach((key, value) { if (value == true) { processUID(key, widget.eventId); } }); } }); } } else { // Nonaktifkan semua listener jika event tidak dimulai secara permanen atau sudah selesai print("Menonaktifkan Realtime Database listeners..."); _activityDataSubscription?.cancel(); _finishedSubscription?.cancel(); _lapsSubscription?.cancel(); _scanResultSubscription?.cancel(); // Set semua subscription ke null agar bisa diinisialisasi ulang _activityDataSubscription = null; _finishedSubscription = null; _lapsSubscription = null; _scanResultSubscription = null; // Juga kosongkan data live RTDB jika listener dimatikan _liveParticipantFinishedStatus.clear(); _liveParticipantCurrentLaps.clear(); } } String _formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, "0"); String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); String threeDigitMilliseconds = (duration.inMilliseconds.remainder(1000)).toString().padLeft(3, "0"); return "${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds.$threeDigitMilliseconds"; } void processUID(String scannedUID, String eventId) async { print("🔍 Mencari user dengan idGelang = '$scannedUID'"); final querySnapshot = await _firestore .collection('users') .where('idGelang', isEqualTo: scannedUID.trim()) .limit(1) .get(); if (querySnapshot.docs.isEmpty) { print('❌ UID $scannedUID tidak ditemukan di Firestore.'); return; } final userDoc = querySnapshot.docs.first; final userId = userDoc.id; final userData = userDoc.data(); _rfidUidToUserIdMap[scannedUID] = userId; print("Mapped RFID UID $scannedUID to User ID $userId"); final eventRef = _firestore.collection('events').doc(eventId); final eventSnapshot = await eventRef.get(); Map participants = eventSnapshot.data()?['participants'] ?? {}; Map existing = participants[userId] ?? {}; final participantData = { 'name': userData['username'] ?? existing['name'] ?? '-', 'user_id': userId, 'rfid_uid': scannedUID, 'laps': existing['laps'] ?? 0, 'finished': existing['finished'] ?? false, 'total_time': existing['total_time'] ?? '-', 'lap_durations': existing['lap_durations'] ?? {}, }; await eventRef.update({ 'participants.$userId': participantData, }); print("🎉 Peserta ${participantData['name']} berhasil dimasukkan/diupdate di event $eventId di Firestore."); } @override Widget build(BuildContext context) { final docRef = _firestore.collection('events').doc(widget.eventId); return Scaffold( backgroundColor: Colors.grey[50], // Background lembut body: StreamBuilder( stream: docRef.snapshots(), builder: (context, firestoreSnapshot) { if (!firestoreSnapshot.hasData) { return Center(child: CircularProgressIndicator(color: primaryBlue)); } if (firestoreSnapshot.hasError) { return Center(child: Text('Error: ${firestoreSnapshot.error}', style: TextStyle(color: Colors.red))); } final firestoreData = firestoreSnapshot.data!.data() as Map?; if (firestoreData == null) { return Center(child: Text('Data event tidak ditemukan di Firestore.')); } // Ambil status permanen dari Firestore bool isEventStartedPermanently = firestoreData['is_started_permanently'] ?? false; bool isEventFinishedPermanently = firestoreData['is_finished_permanently'] ?? false; // Kelola listener RTDB berdasarkan status permanen event WidgetsBinding.instance.addPostFrameCallback((_) { _manageRealtimeListeners(isEventStartedPermanently && !isEventFinishedPermanently); }); // Logic untuk menentukan status yang ditampilkan di UI String displayEventStatusText; Color displayEventStatusColor; if (isEventFinishedPermanently) { displayEventStatusText = "Sudah Selesai Permanen"; displayEventStatusColor = Colors.green.shade700; } else if (isEventStartedPermanently) { displayEventStatusText = "Sedang Berlangsung"; displayEventStatusColor = Colors.orange.shade700; } else { displayEventStatusText = "Belum Dimulai"; displayEventStatusColor = Colors.red.shade700; } final totalLaps = firestoreData['total_laps'] ?? 0; final totalDistance = firestoreData['total_distance'] ?? 0; final createdBy = firestoreData['created_by'] ?? '-'; final createdAt = firestoreData['created_at'] != null ? (firestoreData['created_at'] as Timestamp).toDate() : null; return CustomScrollView( slivers: [ SliverAppBar( expandedHeight: 180.0, // Tinggi AppBar saat expanded floating: false, pinned: true, // AppBar tetap terlihat saat scroll shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(20), // Sudut melengkung di kiri bawah bottomRight: Radius.circular(20), // Sudut melengkung di kanan bawah ), ), backgroundColor: primaryBlue, // Warna dasar AppBar flexibleSpace: FlexibleSpaceBar( centerTitle: true, titlePadding: EdgeInsets.only(bottom: 16.0, left: 16.0, right: 16.0), // Pastikan background juga melengkung background: ClipRRect( // Tambahkan ClipRRect di sini borderRadius: BorderRadius.only( bottomLeft: Radius.circular(20), bottomRight: Radius.circular(20), ), child: Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [primaryBlue, darkBlue], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ), child: Center( child: Padding( padding: const EdgeInsets.only(top: 24.0), // Beri sedikit padding atas agar tidak terlalu mepet child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.event, color: Colors.white, size: 48, ), SizedBox(height: 8), Text( widget.eventName, style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: Colors.white, shadows: [ Shadow( blurRadius: 4.0, color: Colors.black.withOpacity(0.3), offset: Offset(1.0, 1.0), ), ], ), textAlign: TextAlign.center, ), SizedBox(height: 4), Text( "${totalLaps} Putaran | ${(totalDistance / 1000).toStringAsFixed(2)} KM", style: TextStyle( fontSize: 14, color: Colors.white.withOpacity(0.8), ), textAlign: TextAlign.center, ), ], ), ), ), ), ), ), actions: [ IconButton( icon: Icon(Icons.delete_forever, color: Colors.white), tooltip: 'Hapus Event', onPressed: () async { final confirm = await showDialog( context: context, builder: (_) => AlertDialog( title: Text('Konfirmasi Hapus', style: TextStyle(color: darkBlue)), content: Text('Apakah Anda yakin ingin menghapus event "${widget.eventName}" dan semua datanya?', 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: Colors.red)), ), ], ), ); if (confirm == true) { await docRef.delete(); await _rtdb.ref('event_activity_data/${widget.eventId}').remove(); await _rtdb.ref('event_status/${widget.eventId}').remove(); await _rtdb.ref('scan_result/${widget.eventId}').remove(); Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Event "${widget.eventName}" telah dihapus.')), ); } }, ), ], ), SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Informasi Detail Event Card( elevation: 4, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), color: Colors.white, // CARD PUTIH child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildInfoRow(Icons.person, "Dibuat oleh", createdBy), SizedBox(height: 8), if (createdAt != null) _buildInfoRow( Icons.calendar_today, "Tanggal dibuat", "${createdAt.day}/${createdAt.month}/${createdAt.year} ${createdAt.hour.toString().padLeft(2, '0')}:${createdAt.minute.toString().padLeft(2, '0')}", ), SizedBox(height: 16), Divider(color: Colors.grey[300]), SizedBox(height: 16), Text("Status Event", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: darkBlue)), SizedBox(height: 8), Row( children: [ Icon(Icons.play_circle_outline, size: 20, color: Colors.grey[700]), SizedBox(width: 8), Text( displayEventStatusText, // Teks status baru style: TextStyle( fontSize: 15, color: displayEventStatusColor, // Warna status baru fontWeight: FontWeight.w600, ), ), ], ), SizedBox(height: 4), Row( children: [ Icon(Icons.check_circle_outline, size: 20, color: Colors.grey[700]), SizedBox(width: 8), Text( "Selesai Permanen: ${isEventFinishedPermanently ? 'Ya ✅' : 'Tidak ❌'}", style: TextStyle( fontSize: 15, color: isEventFinishedPermanently ? Colors.green.shade700 : Colors.red.shade700, fontWeight: FontWeight.w600, ), ), ], ), SizedBox(height: 24), // Tombol Mulai Event SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: (isEventStartedPermanently || isEventFinishedPermanently) ? null : () async { final confirm = await showDialog( context: context, builder: (_) => AlertDialog( title: Text('Mulai Event', style: TextStyle(color: darkBlue)), content: Text('Peserta akan mulai scan RFID untuk 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('Mulai', style: TextStyle(color: Colors.green))), ], ), ); if (confirm == true) { final currentEventData = await docRef.get(); final currentTotalLaps = (currentEventData.data() as Map?)?['total_laps'] ?? 0; await docRef.update({ 'is_started_permanently': true, 'is_finished_permanently': false, 'start_time': Timestamp.now(), }).catchError((e) { print("❌ Gagal update Firestore: $e"); }); await _eventStatusRef.set({ 'started': true, 'scan_mode': true, 'timestamp': ServerValue.timestamp, 'total_laps_target': currentTotalLaps, 'finished': false, }); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Event dimulai. Peserta dapat scan RFID sekarang.')), ); } }, icon: Icon(Icons.play_arrow, color: Colors.white), label: Text( 'Mulai Event (Scan RFID)', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), ), style: ElevatedButton.styleFrom( backgroundColor: (isEventStartedPermanently || isEventFinishedPermanently) ? Colors.grey : Colors.green.shade600, padding: EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), elevation: 3, ), ), ), // Tombol Akhiri Event Manual if (isEventStartedPermanently && !isEventFinishedPermanently) Padding( padding: const EdgeInsets.only(top: 12.0), child: SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: () async { final confirm = await showDialog( context: context, builder: (_) => AlertDialog( title: Text('Akhiri Event', style: TextStyle(color: darkBlue)), content: Text('Apakah Anda yakin ingin mengakhiri event ini secara manual? Status event akan permanen selesai dan data Realtime akan dihapus.', 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('Akhiri', style: TextStyle(color: Colors.red))), ], ), ); if (confirm == true) { await docRef.update({ 'is_started_permanently': false, 'is_finished_permanently': true, }); await _rtdb.ref('event_activity_data/${widget.eventId}').remove(); await _rtdb.ref('event_status/${widget.eventId}').remove(); await _rtdb.ref('scan_result/${widget.eventId}').remove(); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Event telah diakhiri secara manual dan disimpan secara permanen.')), ); } }, icon: Icon(Icons.stop, color: Colors.white), label: Text( 'Akhiri Event Manual', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), ), style: ElevatedButton.styleFrom( backgroundColor: Colors.red.shade600, padding: EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), elevation: 3, ), ), ), ), ], ), ), ), SizedBox(height: 24), Text("Peserta Event:", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18, color: darkBlue)), SizedBox(height: 1), ], ), ), ), // Daftar Peserta SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 16.0), sliver: ((firestoreData['participants'] as Map?)?.entries.isNotEmpty ?? false) ? SliverList( delegate: SliverChildBuilderDelegate( (context, index) { final participantEntry = (firestoreData['participants'] as Map).entries.toList()[index]; final participant = participantEntry.value; final name = participant['name'] ?? 'Nama tidak ditemukan'; final rfidUid = participant['rfid_uid'] as String?; bool participantFinishedDisplay; int participantLapsDisplay; String participantTotalTimeDisplay = participant['total_time'] ?? '-'; // Prioritaskan data live (RTDB) jika event sedang berjalan (permanen) if (isEventStartedPermanently && !isEventFinishedPermanently && _liveParticipantFinishedStatus.containsKey(rfidUid)) { participantFinishedDisplay = _liveParticipantFinishedStatus[rfidUid]!; } else { participantFinishedDisplay = participant['finished'] ?? false; // Ambil dari Firestore } if (isEventStartedPermanently && !isEventFinishedPermanently && _liveParticipantCurrentLaps.containsKey(rfidUid)) { participantLapsDisplay = _liveParticipantCurrentLaps[rfidUid]! > 0 ? _liveParticipantCurrentLaps[rfidUid]! - 1 : 0; } else { participantLapsDisplay = participant['laps'] ?? 0; // Ambil dari Firestore } return Card( margin: EdgeInsets.symmetric(vertical: 8), elevation: 4, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), color: Colors.white, // CARD PUTIH child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( name, style: TextStyle( fontSize: 17, fontWeight: FontWeight.bold, color: darkBlue, ), ), SizedBox(height: 8), _buildParticipantInfoRow(Icons.credit_card, "UID RFID", rfidUid ?? 'N/A'), _buildParticipantInfoRow(Icons.sports_score, "Putaran", "$participantLapsDisplay"), _buildParticipantInfoRow( Icons.flag, "Selesai", participantFinishedDisplay ? 'Ya ✅' : 'Tidak ❌', valueColor: participantFinishedDisplay ? Colors.green.shade700 : Colors.red.shade700, ), if (participantFinishedDisplay && participantTotalTimeDisplay != '-') _buildParticipantInfoRow( Icons.timer, "Waktu Total", participantTotalTimeDisplay, valueColor: darkBlue, ), ], ), ), ); }, childCount: (firestoreData['participants'] as Map).length, ), ) : SliverToBoxAdapter( child: Center( child: Padding( padding: const EdgeInsets.all(16.0), child: Text( "Belum ada peserta yang terdaftar untuk event ini.", style: TextStyle(fontSize: 16, color: Colors.grey[600]), textAlign: TextAlign.center, ), ), ), ), ), SliverToBoxAdapter( child: SizedBox(height: 24), // Tambahan spasi di bagian bawah ), ], ); }, ), ); } // Helper widget untuk baris informasi event Widget _buildInfoRow(IconData icon, String label, String value) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(icon, size: 20, color: primaryBlue), SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: Colors.grey[700]), ), Text( value, style: TextStyle(fontSize: 15, color: Colors.grey[900]), ), ], ), ), ], ); } // Helper widget untuk baris informasi peserta Widget _buildParticipantInfoRow(IconData icon, String label, String value, {Color? valueColor}) { return Padding( padding: const EdgeInsets.only(bottom: 4.0), child: Row( children: [ Icon(icon, size: 16, color: Colors.grey[600]), SizedBox(width: 8), Text( "$label: ", style: TextStyle(fontSize: 14, color: Colors.grey[700]), ), Expanded( child: Text( value, style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: valueColor ?? darkBlue), overflow: TextOverflow.ellipsis, ), ), ], ), ); } }