341 lines
16 KiB
Dart
341 lines
16 KiB
Dart
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:firebase_auth/firebase_auth.dart';
|
|
import 'auth/login_screen.dart'; // Pastikan path ini benar
|
|
import 'create_event_page.dart'; // Pastikan path ini benar
|
|
import 'event_detail_page.dart'; // Pastikan path ini benar
|
|
import 'hasil_event_page.dart'; // Pastikan path ini benar
|
|
|
|
class SuperUserDashboard extends StatefulWidget {
|
|
@override
|
|
State<SuperUserDashboard> createState() => _SuperUserDashboardState();
|
|
}
|
|
|
|
class _SuperUserDashboardState extends State<SuperUserDashboard> {
|
|
// 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
|
|
|
|
Future<void> _refresh() async {
|
|
// Memaksa rebuild StreamBuilder saat refresh
|
|
setState(() {});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: Colors.white, // Background lembut
|
|
body: RefreshIndicator( // RefreshIndicator di sini untuk keseluruhan body
|
|
onRefresh: _refresh,
|
|
color: primaryBlue, // Warna indicator refresh
|
|
child: CustomScrollView(
|
|
// Pastikan physics di CustomScrollView agar bisa di-scroll
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
slivers: [
|
|
SliverAppBar(
|
|
expandedHeight: 200.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, // Set warna dasar AppBar (penting untuk transisi)
|
|
flexibleSpace: FlexibleSpaceBar(
|
|
centerTitle: true,
|
|
titlePadding: EdgeInsets.only(bottom: 16.0, left: 16.0, right: 16.0),
|
|
// Untuk membuat "Dashboard Event" dan logo terlihat melengkung,
|
|
// kita bisa membungkus konten background FlexibleSpaceBar
|
|
// dengan ClipRRect dan Container tambahan.
|
|
background: ClipRRect( // <<< Tambahkan ClipRRect di sini
|
|
borderRadius: BorderRadius.only(
|
|
bottomLeft: Radius.circular(20),
|
|
bottomRight: Radius.circular(20),
|
|
),
|
|
child: Container( // <<< Tambahkan Container di sini
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient( // Tetap gunakan gradient untuk warna
|
|
colors: [primaryBlue, darkBlue],
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
),
|
|
// Hapus borderRadius di sini karena sudah ada di ClipRRect
|
|
),
|
|
child: Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
SizedBox(height: 20), // Spasi untuk logo
|
|
Image.asset(
|
|
'images/run.png', // Pastikan path logo benar
|
|
height: 80,
|
|
),
|
|
// Jika Anda ingin teks 'Dashboard Event' juga terlihat di expanded state,
|
|
// dan melengkung bersama background, Anda bisa menambahkannya di sini.
|
|
// Namun, secara default, title FlexibleSpaceBar akan bergerak.
|
|
// Untuk membuat teks title 'tetap' dengan background yang melengkung,
|
|
// Anda mungkin perlu menempatkan teks di dalam Column ini juga,
|
|
// dan menghapus properti 'title' dari FlexibleSpaceBar.
|
|
// Untuk saat ini, kita biarkan 'title' seperti adanya untuk animasi.
|
|
// Jika Anda ingin title statis di dalam background melengkung, beritahu saya.
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
title: Text( // Ini adalah title yang akan bergerak saat scroll
|
|
'Dashboard Event',
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
),
|
|
actions: [
|
|
IconButton(
|
|
icon: Icon(Icons.logout, color: Colors.white),
|
|
tooltip: 'Logout',
|
|
onPressed: () async {
|
|
final shouldLogout = await showDialog<bool>(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: Text('Konfirmasi Logout'),
|
|
content: Text('Apakah Anda yakin ingin keluar dari akun Super User?'),
|
|
actions: [
|
|
TextButton(onPressed: () => Navigator.pop(context, false), child: Text('Batal')),
|
|
TextButton(
|
|
onPressed: () async {
|
|
await FirebaseAuth.instance.signOut();
|
|
Navigator.pushAndRemoveUntil(
|
|
context,
|
|
MaterialPageRoute(builder: (_) => const LoginScreen()),
|
|
(route) => false,
|
|
);
|
|
},
|
|
child: Text('Logout', style: TextStyle(color: Colors.red))),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
SliverToBoxAdapter(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Card(
|
|
elevation: 4,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
|
color: Colors.white,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
children: [
|
|
// Teks "Aksi Cepat" sudah dihapus dari sini sesuai permintaan sebelumnya
|
|
_buildActionButton(
|
|
context,
|
|
icon: Icons.add_circle_outline,
|
|
label: 'Buat Event Baru',
|
|
onPressed: () {
|
|
Navigator.push(context, MaterialPageRoute(builder: (_) => CreateEventPage()));
|
|
},
|
|
color: primaryBlue,
|
|
),
|
|
SizedBox(height: 12),
|
|
_buildActionButton(
|
|
context,
|
|
icon: Icons.bar_chart,
|
|
label: 'Lihat Hasil Event',
|
|
onPressed: () {
|
|
Navigator.push(context, MaterialPageRoute(builder: (_) => HasilEventPage()));
|
|
},
|
|
color: Colors.green.shade600, // Warna hijau yang bagus
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
SizedBox(height: 24),
|
|
Text(
|
|
'Daftar Event',
|
|
style: TextStyle(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.bold,
|
|
color: darkBlue,
|
|
),
|
|
),
|
|
SizedBox(height: 1),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
StreamBuilder<QuerySnapshot>(
|
|
stream: FirebaseFirestore.instance
|
|
.collection('events')
|
|
.orderBy('created_at', descending: true) // Urutkan terbaru di atas
|
|
.snapshots(),
|
|
builder: (context, snapshot) {
|
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
return SliverFillRemaining(
|
|
child: Center(child: CircularProgressIndicator(color: primaryBlue)));
|
|
}
|
|
if (snapshot.hasError) {
|
|
return SliverFillRemaining(
|
|
child: Center(child: Text('Error: ${snapshot.error}', style: TextStyle(color: Colors.red))));
|
|
}
|
|
if (!snapshot.hasData || snapshot.data!.docs.isEmpty) {
|
|
return SliverFillRemaining(child: Center(child: Text('Belum ada event yang dibuat.')));
|
|
}
|
|
|
|
final events = snapshot.data!.docs;
|
|
|
|
return SliverList(
|
|
delegate: SliverChildBuilderDelegate(
|
|
(context, index) {
|
|
final data = events[index].data() as Map<String, dynamic>;
|
|
final docId = events[index].id;
|
|
final eventName = data['name'] ?? 'Tanpa Nama Event';
|
|
final totalLaps = data['total_laps'] ?? 0;
|
|
final totalDistance = data['total_distance'] ?? 0;
|
|
final createdAt = data['created_at'] is Timestamp
|
|
? (data['created_at'] as Timestamp).toDate()
|
|
: null;
|
|
final isStartedPermanently = data['is_started_permanently'] ?? false;
|
|
final isFinishedPermanently = data['is_finished_permanently'] ?? false;
|
|
|
|
String statusText;
|
|
Color statusColor;
|
|
|
|
if (isFinishedPermanently) {
|
|
statusText = 'Selesai';
|
|
statusColor = Colors.green.shade700;
|
|
} else if (isStartedPermanently) {
|
|
statusText = 'Sedang Berlangsung';
|
|
statusColor = Colors.orange.shade700;
|
|
} else {
|
|
statusText = 'Belum Dimulai';
|
|
statusColor = darkBlue;
|
|
}
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
|
|
child: Card(
|
|
elevation: 5,
|
|
color: Colors.white,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
|
clipBehavior: Clip.antiAlias,
|
|
child: InkWell(
|
|
onTap: () {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => EventDetailPage(
|
|
eventId: docId,
|
|
eventName: eventName,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
eventName,
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: darkBlue,
|
|
),
|
|
),
|
|
SizedBox(height: 8),
|
|
Row(
|
|
children: [
|
|
Icon(Icons.flag_outlined, size: 18, color: Colors.grey[700]),
|
|
SizedBox(width: 8),
|
|
Text(
|
|
'$totalLaps Putaran (${(totalDistance / 1000).toStringAsFixed(2)} KM)', // Format KM dengan 2 desimal
|
|
style: TextStyle(fontSize: 14, color: Colors.grey[700]),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 4),
|
|
Row(
|
|
children: [
|
|
Icon(Icons.event, size: 18, color: Colors.grey[700]),
|
|
SizedBox(width: 8),
|
|
Text(
|
|
createdAt != null ? '${createdAt.day}/${createdAt.month}/${createdAt.year}' : 'Tanggal tidak diketahui',
|
|
style: TextStyle(fontSize: 14, color: Colors.grey[700]),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 8),
|
|
Align(
|
|
alignment: Alignment.bottomRight,
|
|
child: Chip(
|
|
label: Text(
|
|
statusText,
|
|
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
|
|
),
|
|
backgroundColor: statusColor,
|
|
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
|
|
shape: StadiumBorder(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
childCount: events.length,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Helper method untuk membuat tombol aksi yang konsisten
|
|
Widget _buildActionButton(
|
|
BuildContext context, {
|
|
required IconData icon,
|
|
required String label,
|
|
required VoidCallback onPressed,
|
|
required Color color,
|
|
}) {
|
|
return SizedBox(
|
|
width: double.infinity, // Membuat tombol full width
|
|
child: ElevatedButton.icon(
|
|
onPressed: onPressed,
|
|
icon: Icon(icon, color: Colors.white),
|
|
label: Text(
|
|
label,
|
|
style: TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.bold),
|
|
),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: color,
|
|
padding: EdgeInsets.symmetric(vertical: 14),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
elevation: 5,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
} |