// ... (import tetap sama) import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; class BookingsKaryawanPage extends StatefulWidget { final String token; const BookingsKaryawanPage({Key? key, required this.token}) : super(key: key); @override State createState() => _BookingsKaryawanPageState(); } class _BookingsKaryawanPageState extends State { List bookings = []; bool loading = false; final NumberFormat currencyFormat = NumberFormat.currency(locale: 'id_ID', symbol: 'Rp'); @override void initState() { super.initState(); fetchBookings(); } Future fetchBookings() async { setState(() => loading = 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; }); } else { throw Exception('Gagal memuat data booking'); } } catch (e) { print('Fetch error: $e'); } finally { setState(() => loading = false); } } Future updateStatusByUserAndTanggal(int userId, String tanggal, String status) async { setState(() => loading = true); try { final response = await http.put( Uri.parse('http://angeliasalon.my.id/api/bookings'), headers: { 'Authorization': 'Bearer ${widget.token}', 'Content-Type': 'application/json', 'Accept': 'application/json', }, body: jsonEncode({ 'user_id': userId, 'tanggal_booking': tanggal, 'status': status, }), ); final body = json.decode(response.body); if (response.statusCode == 200) { await fetchBookings(); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(body['message']), backgroundColor: Colors.green), ); } else { throw Exception(body['message'] ?? 'Gagal mengubah status'); } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error: $e'), backgroundColor: Colors.red), ); } finally { setState(() => loading = false); } } Map> groupBookingsByUser(List data) { final Map> grouped = {}; for (var booking in data) { final user = booking['user']; if (user == null) continue; final userId = user['id']; grouped.putIfAbsent(userId, () => []).add(booking); } return grouped; } Icon getServiceIcon(String? namaLayanan) { final name = namaLayanan?.toLowerCase() ?? ''; if (name.contains('makeup') || name.contains('rias') || name.contains('make up')) { return const Icon(Icons.brush, color: Colors.deepPurple); } else { return const Icon(Icons.content_cut, color: Colors.pink); } } @override Widget build(BuildContext context) { final groupedByUser = groupBookingsByUser(bookings); return Scaffold( backgroundColor: const Color(0xFFFDEEF4), appBar: AppBar( backgroundColor: Colors.pink.shade300, title: const Text('Data Booking (Karyawan)'), centerTitle: true, ), body: loading ? const Center(child: CircularProgressIndicator()) : bookings.isEmpty ? const Center( child: Text( 'Belum ada booking', style: TextStyle(fontSize: 18, color: Colors.black54), ), ) : ListView( padding: const EdgeInsets.all(12), children: groupedByUser.entries.map((entry) { final userId = entry.key; final bookingsList = entry.value; final userName = bookingsList.first['user']?['name'] ?? 'Tidak diketahui'; final Map> byTanggal = {}; for (var b in bookingsList) { final tanggal = b['tanggal_booking']; byTanggal.putIfAbsent(tanggal, () => []).add(b); } return Card( elevation: 4, margin: const EdgeInsets.symmetric(vertical: 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: ExpansionTile( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), tilePadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), title: Text( '$userName (ID: $userId)', style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), ), childrenPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), children: byTanggal.entries.map((tanggalEntry) { final tanggal = tanggalEntry.key; final list = tanggalEntry.value; final totalHarga = list.fold( 0, (sum, item) => sum + (item['service']?['price']?.toDouble() ?? 0), ); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Divider(thickness: 1.2, color: Colors.grey), Padding( padding: const EdgeInsets.only(bottom: 8), child: Text( '๐Ÿ“… Tanggal Booking: $tanggal', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 14, color: Colors.pink.shade700), ), ), Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('๐Ÿงพ Total layanan: ${list.length}'), Text('๐Ÿ’ฐ Total harga: ${currencyFormat.format(totalHarga)}'), ], ), ), ...list.map((booking) { final service = booking['service']; final namaLayanan = service?['name'] ?? 'Layanan tidak ditemukan'; final harga = service?['price'] ?? 0; final jam = booking['jam'] ?? '-'; final status = booking['status']; IconData icon; Color chipColor; Color iconColor; switch (status) { case 'diterima': icon = Icons.check_circle; chipColor = Colors.green.shade100; iconColor = Colors.green.shade800; break; case 'ditolak': icon = Icons.cancel; chipColor = Colors.red.shade100; iconColor = Colors.red.shade800; break; default: icon = Icons.hourglass_top; chipColor = Colors.orange.shade100; iconColor = Colors.orange.shade800; } return Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( color: Colors.black12, blurRadius: 2, offset: Offset(0, 1), ) ], ), child: ListTile( contentPadding: EdgeInsets.zero, leading: getServiceIcon(namaLayanan), title: Text(namaLayanan), subtitle: Text('Jam: $jam\nHarga: ${currencyFormat.format(harga)}'), trailing: Chip( avatar: Icon(icon, size: 16, color: iconColor), label: Text(status, style: TextStyle(color: iconColor)), backgroundColor: chipColor, ), ), ); }).toList(), Padding( padding: const EdgeInsets.only(top: 8, bottom: 16), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ ElevatedButton.icon( onPressed: () => updateStatusByUserAndTanggal(userId, tanggal, 'diterima'), icon: const Icon(Icons.check), label: const Text('Terima'), style: ElevatedButton.styleFrom( backgroundColor: Colors.green.shade600, padding: const EdgeInsets.symmetric(horizontal: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8)), ), ), const SizedBox(width: 12), ElevatedButton.icon( onPressed: () => updateStatusByUserAndTanggal(userId, tanggal, 'ditolak'), icon: const Icon(Icons.close), label: const Text('Tolak'), style: ElevatedButton.styleFrom( backgroundColor: Colors.red.shade600, padding: const EdgeInsets.symmetric(horizontal: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8)), ), ), ], ), ), ], ); }).toList(), ), ); }).toList(), ), ); } }