import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import '../providers/dashboard_providers.dart'; import '../../../core/models/reservation.dart'; import '../../../widgets/error_view.dart'; import '../../../widgets/status_badge.dart'; const _params = { 'status': 'DRAFT', 'source': 'MARKETPLACE', 'page': 1, 'pageSize': 100, }; class OnlineReservationsScreen extends ConsumerWidget { const OnlineReservationsScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final async = ref.watch(reservationsProvider(_params)); return Scaffold( appBar: AppBar( title: const Text('Online Reservations'), actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: () => ref.invalidate(reservationsProvider(_params)), ), ], ), body: async.when( loading: () => const Center(child: CircularProgressIndicator()), error: (e, _) => ErrorView( message: 'Could not load online reservations', onRetry: () => ref.invalidate(reservationsProvider(_params)), ), data: (result) { final items = result.data; if (items.isEmpty) { return const Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.check_circle_outline, size: 64, color: Color(0xFF6EE7B7)), SizedBox(height: 16), Text( 'All caught up!', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF111928)), ), SizedBox(height: 6), Text( 'No pending marketplace requests.', style: TextStyle(color: Color(0xFF6B7280)), ), ], ), ); } return RefreshIndicator( onRefresh: () async => ref.invalidate(reservationsProvider(_params)), child: ListView.separated( padding: const EdgeInsets.all(16), itemCount: items.length, separatorBuilder: (_, _) => const SizedBox(height: 12), itemBuilder: (_, i) => _ReservationCard( reservation: items[i], onAction: () => ref.invalidate(reservationsProvider(_params)), ), ), ); }, ), ); } } class _ReservationCard extends ConsumerWidget { final Reservation reservation; final VoidCallback onAction; const _ReservationCard({ required this.reservation, required this.onAction, }); @override Widget build(BuildContext context, WidgetRef ref) { final r = reservation; final fmt = DateFormat('MMM d, yyyy'); final fmtMoney = NumberFormat.currency(symbol: '\$', decimalDigits: 2); return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header: vehicle + status Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (r.vehicle != null) Text( r.vehicle!.displayName, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 15, color: Color(0xFF111928), ), ) else const Text('Vehicle unavailable', style: TextStyle(color: Color(0xFF9CA3AF))), if (r.vehicle != null) Text( r.vehicle!.licensePlate, style: const TextStyle( fontSize: 12, color: Color(0xFF6B7280)), ), ], ), ), StatusBadge(status: r.status), ], ), const SizedBox(height: 12), const Divider(height: 1), const SizedBox(height: 12), // Customer _InfoRow( icon: Icons.person_outline, label: r.customer != null ? '${r.customer!.firstName} ${r.customer!.lastName}' : 'Unknown customer', sub: r.customer?.email, ), const SizedBox(height: 8), // Dates _InfoRow( icon: Icons.calendar_today_outlined, label: '${fmt.format(r.startDate)} → ${fmt.format(r.endDate)}', sub: '${r.days} day${r.days == 1 ? '' : 's'}', ), const SizedBox(height: 8), // Amount _InfoRow( icon: Icons.attach_money, label: fmtMoney.format(r.totalAmount / 100), sub: 'Total', ), const SizedBox(height: 14), // Actions Row( children: [ Expanded( child: OutlinedButton.icon( icon: const Icon(Icons.close, size: 16), label: const Text('Decline'), style: OutlinedButton.styleFrom( foregroundColor: const Color(0xFFE02424), side: const BorderSide(color: Color(0xFFE02424)), ), onPressed: () => _showDeclineDialog(context, ref, r.id), ), ), const SizedBox(width: 12), Expanded( child: ElevatedButton.icon( icon: const Icon(Icons.check, size: 16), label: const Text('Confirm'), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF057A55), ), onPressed: () => _confirm(context, ref, r.id), ), ), ], ), ], ), ), ); } Future _confirm( BuildContext context, WidgetRef ref, String id) async { try { await ref.read(reservationServiceProvider).confirmReservation(id); onAction(); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Reservation confirmed'), backgroundColor: Color(0xFF057A55), ), ); } } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Failed to confirm: $e')), ); } } } void _showDeclineDialog( BuildContext context, WidgetRef ref, String id) { final reasonCtrl = TextEditingController(); bool saving = false; showDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (ctx, setState) => AlertDialog( title: const Text('Decline Reservation'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Optionally provide a reason — it will be recorded on the reservation.', style: TextStyle(fontSize: 13, color: Color(0xFF6B7280)), ), const SizedBox(height: 12), TextField( controller: reasonCtrl, maxLines: 3, decoration: const InputDecoration( labelText: 'Reason (optional)', hintText: 'e.g. Vehicle not available for these dates', ), ), ], ), actions: [ TextButton( onPressed: saving ? null : () => Navigator.pop(ctx), child: const Text('Cancel'), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFE02424)), onPressed: saving ? null : () async { setState(() => saving = true); try { await ref .read(reservationServiceProvider) .cancelReservation( id, reasonCtrl.text.trim().isEmpty ? 'Declined by company' : reasonCtrl.text.trim()); onAction(); if (ctx.mounted) Navigator.pop(ctx); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Reservation declined')), ); } } catch (e) { setState(() => saving = false); if (ctx.mounted) { ScaffoldMessenger.of(ctx).showSnackBar( SnackBar( content: Text('Failed to decline: $e')), ); } } }, child: saving ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( Colors.white)), ) : const Text('Decline'), ), ], ), ), ); } } class _InfoRow extends StatelessWidget { final IconData icon; final String label; final String? sub; const _InfoRow({required this.icon, required this.label, this.sub}); @override Widget build(BuildContext context) => Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(icon, size: 16, color: const Color(0xFF9CA3AF)), const SizedBox(width: 8), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w500, color: Color(0xFF374151))), if (sub != null) Text(sub!, style: const TextStyle( fontSize: 11, color: Color(0xFF9CA3AF))), ], ), ), ], ); }