import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; class DataPelangganPage extends StatefulWidget { final String token; const DataPelangganPage({super.key, required this.token}); @override State createState() => _DataPelangganPageState(); } class _DataPelangganPageState extends State { List bookings = []; bool isLoading = false; final Color primaryColor = const Color(0xFFF06292); final Color backgroundColor = const Color(0xFFFFF6F9); @override void initState() { super.initState(); fetchBookings(); } Future fetchBookings() async { setState(() => isLoading = true); try { final response = await http.get( Uri.parse('http://angeliasalon.my.id/api/bookings'), headers: { 'Authorization': 'Bearer ${widget.token}', 'Accept': 'application/json', }, ); if (response.statusCode == 200) { final data = json.decode(response.body); setState(() { bookings = data; isLoading = false; }); } else { throw Exception('Gagal memuat data booking'); } } catch (e) { setState(() => isLoading = false); _showErrorSnackBar('Error: $e'); } } void _showErrorSnackBar(String message) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message), backgroundColor: Colors.red), ); } Icon getServiceIcon(String serviceName) { final name = serviceName.toLowerCase(); if (name.contains('make')) { return const Icon(Icons.brush, color: Colors.pinkAccent); } else if (name.contains('rambut') || name.contains('hair') || name.contains('cut') || name.contains('blow')) { return const Icon(Icons.content_cut, color: Colors.pinkAccent); } else { return const Icon(Icons.content_cut, color: Colors.pinkAccent); } } String getInitials(String name) { final parts = name.trim().split(' '); if (parts.length == 1) return parts[0][0].toUpperCase(); return '${parts[0][0]}${parts[1][0]}'.toUpperCase(); } Map> getBookingsByPelanggan() { Map> result = {}; for (var booking in bookings) { final pelangganName = booking['user']['name'] ?? 'Tidak diketahui'; result.putIfAbsent(pelangganName, () => []); result[pelangganName]!.add(booking); } return result; } Map> getBookingsByTanggal(List bookings) { Map> result = {}; for (var booking in bookings) { final tanggal = booking['tanggal_booking'] ?? 'Tidak diketahui'; result.putIfAbsent(tanggal, () => []); result[tanggal]!.add(booking); } return result; } int getTotalHarga(List bookingsPerTanggal) { int total = 0; for (var booking in bookingsPerTanggal) { final harga = (booking['service']?['price'] ?? 0) as int; total += harga; } return total; } String formatTanggal(String tanggal) { try { final parts = tanggal.split('-'); if (parts.length == 3) { return '${parts[2]}-${parts[1]}-${parts[0]}'; } return tanggal; } catch (e) { return tanggal; } } String formatRupiah(int angka) { return 'Rp ${angka.toString().replaceAllMapped( RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), (Match m) => '${m[1]}.', )}'; } @override Widget build(BuildContext context) { final pelangganMap = getBookingsByPelanggan(); return Scaffold( backgroundColor: backgroundColor, appBar: AppBar( title: const Text('Riwayat Booking Pelanggan'), backgroundColor: primaryColor, elevation: 0, ), body: isLoading ? const Center(child: CircularProgressIndicator()) : pelangganMap.isEmpty ? const Center(child: Text('Tidak ada data booking')) : RefreshIndicator( onRefresh: fetchBookings, child: ListView( padding: const EdgeInsets.all(12), children: pelangganMap.entries.map((pelangganEntry) { final namaPelanggan = pelangganEntry.key; final bookingsList = pelangganEntry.value; final bookingsByTanggal = getBookingsByTanggal(bookingsList); return Card( color: Colors.white, margin: const EdgeInsets.symmetric(vertical: 8), elevation: 5, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: ExpansionTile( leading: CircleAvatar( backgroundColor: primaryColor, child: Text( getInitials(namaPelanggan), style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, ), ), ), title: Text( namaPelanggan, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 18, color: Colors.black87, ), ), children: bookingsByTanggal.entries.map((tanggalEntry) { final tanggal = tanggalEntry.key; final bookingsPerTanggal = tanggalEntry.value; final totalHarga = getTotalHarga(bookingsPerTanggal); return ExpansionTile( tilePadding: const EdgeInsets.symmetric(horizontal: 16), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Tanggal: ${formatTanggal(tanggal)}', style: TextStyle( color: primaryColor, fontWeight: FontWeight.w600, ), ), Text( formatRupiah(totalHarga), style: const TextStyle( color: Colors.black87, fontWeight: FontWeight.w500, ), ), ], ), children: bookingsPerTanggal.map((booking) { final harga = booking['service']?['price'] ?? 0; return ListTile( leading: CircleAvatar( backgroundColor: Colors.pink.shade100, child: getServiceIcon( booking['service']?['name'] ?? '', ), ), title: Text( booking['service']?['name'] ?? 'Layanan tidak diketahui', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), Row( children: [ const Icon(Icons.access_time, size: 14, color: Colors.grey), const SizedBox(width: 4), Text('Jam: ${booking['jam']}'), ], ), const SizedBox(height: 2), Row( children: [ const Icon(Icons.monetization_on, size: 14, color: Colors.grey), const SizedBox(width: 4), Text('Harga: ${formatRupiah(harga as int)}'), ], ), ], ), trailing: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: booking['status'] == 'diterima' ? Colors.green : booking['status'] == 'ditolak' ? Colors.red : Colors.grey, borderRadius: BorderRadius.circular(20), ), child: Text( booking['status'].toString().toUpperCase(), style: const TextStyle(color: Colors.white, fontSize: 12), ), ), ); }).toList(), ); }).toList(), ), ); }).toList(), ), ), ); } }