import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import '../../../core/models/offer.dart'; import '../providers/dashboard_providers.dart'; import '../../../widgets/error_view.dart'; class OffersScreen extends ConsumerWidget { const OffersScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final async = ref.watch(offersProvider); return Scaffold( appBar: AppBar(title: const Text('Offers & Promotions')), floatingActionButton: FloatingActionButton.extended( onPressed: () async { final created = await showModalBottomSheet( context: context, isScrollControlled: true, useSafeArea: true, builder: (_) => const _OfferFormSheet(), ); if (created == true) ref.invalidate(offersProvider); }, icon: const Icon(Icons.add), label: const Text('New Offer'), ), body: async.when( loading: () => const Center(child: CircularProgressIndicator()), error: (e, _) => ErrorView( message: 'Failed to load offers', onRetry: () => ref.invalidate(offersProvider), ), data: (offers) { if (offers.isEmpty) { return const Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.local_offer_outlined, size: 64, color: Color(0xFFD1D5DB)), SizedBox(height: 16), Text('No offers yet', style: TextStyle(color: Color(0xFF9CA3AF))), ], ), ); } return RefreshIndicator( onRefresh: () async => ref.invalidate(offersProvider), child: ListView.separated( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), itemCount: offers.length, separatorBuilder: (_, _) => const SizedBox(height: 10), itemBuilder: (_, i) => _OfferCard( offer: offers[i], onToggle: () async { final svc = ref.read(offerServiceProvider); if (offers[i].isActive) { await svc.deactivateOffer(offers[i].id); } else { await svc.activateOffer(offers[i].id); } ref.invalidate(offersProvider); }, onDelete: () async { final confirmed = await showDialog( context: context, builder: (_) => AlertDialog( title: const Text('Delete Offer'), content: Text( 'Delete "${offers[i].title}"? This cannot be undone.'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('Cancel')), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFFE02424)), onPressed: () => Navigator.pop(context, true), child: const Text('Delete'), ), ], ), ); if (confirmed == true) { await ref.read(offerServiceProvider).deleteOffer(offers[i].id); ref.invalidate(offersProvider); } }, ), ), ); }, ), ); } } class _OfferCard extends StatelessWidget { final CompanyOffer offer; final VoidCallback onToggle; final VoidCallback onDelete; const _OfferCard({ required this.offer, required this.onToggle, required this.onDelete, }); @override Widget build(BuildContext context) { final fmt = DateFormat('MMM d, yyyy'); final isExpired = offer.validUntil.isBefore(DateTime.now()); return Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( offer.title, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 15), ), ), _StatusChip( active: offer.isActive && !isExpired, expired: isExpired), const SizedBox(width: 8), PopupMenuButton( onSelected: (v) { if (v == 'toggle') onToggle(); if (v == 'delete') onDelete(); }, itemBuilder: (_) => [ PopupMenuItem( value: 'toggle', child: Text(offer.isActive ? 'Deactivate' : 'Activate'), ), const PopupMenuItem( value: 'delete', child: Text('Delete', style: TextStyle(color: Color(0xFFE02424))), ), ], ), ], ), const SizedBox(height: 8), Row( children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4), decoration: BoxDecoration( color: const Color(0xFFF0F7FF), borderRadius: BorderRadius.circular(20), ), child: Text( offer.discountLabel, style: const TextStyle( color: Color(0xFF1A56DB), fontWeight: FontWeight.w600, fontSize: 13), ), ), if (offer.promoCode != null) ...[ const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4), decoration: BoxDecoration( color: const Color(0xFFF3F4F6), borderRadius: BorderRadius.circular(20), ), child: Text( offer.promoCode!, style: const TextStyle( fontFamily: 'monospace', fontSize: 12, fontWeight: FontWeight.w600), ), ), ], ], ), const SizedBox(height: 8), Text( '${fmt.format(offer.validFrom)} → ${fmt.format(offer.validUntil)}', style: const TextStyle(fontSize: 12, color: Color(0xFF6B7280)), ), if (offer.maxRedemptions != null) ...[ const SizedBox(height: 4), Text( '${offer.redemptionCount} / ${offer.maxRedemptions} uses', style: const TextStyle( fontSize: 12, color: Color(0xFF6B7280)), ), ], ], ), ), ); } } class _StatusChip extends StatelessWidget { final bool active; final bool expired; const _StatusChip({required this.active, required this.expired}); @override Widget build(BuildContext context) { Color bg; Color fg; String label; if (expired) { bg = const Color(0xFFF3F4F6); fg = const Color(0xFF6B7280); label = 'Expired'; } else if (active) { bg = const Color(0xFFDEF7EC); fg = const Color(0xFF03543F); label = 'Active'; } else { bg = const Color(0xFFFDE8E8); fg = const Color(0xFF9B1C1C); label = 'Inactive'; } return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration(color: bg, borderRadius: BorderRadius.circular(20)), child: Text(label, style: TextStyle(fontSize: 11, color: fg, fontWeight: FontWeight.w600)), ); } } class _OfferFormSheet extends ConsumerStatefulWidget { const _OfferFormSheet(); @override ConsumerState<_OfferFormSheet> createState() => _OfferFormSheetState(); } class _OfferFormSheetState extends ConsumerState<_OfferFormSheet> { final _formKey = GlobalKey(); bool _loading = false; final _title = TextEditingController(); final _description = TextEditingController(); final _promoCode = TextEditingController(); final _discountValue = TextEditingController(text: '10'); final _maxRedemptions = TextEditingController(); String _type = 'PERCENTAGE'; bool _isPublic = false; DateTime _validFrom = DateTime.now(); DateTime _validUntil = DateTime.now().add(const Duration(days: 30)); static const _types = ['PERCENTAGE', 'FIXED_AMOUNT', 'FREE_DAY']; @override void dispose() { for (final c in [ _title, _description, _promoCode, _discountValue, _maxRedemptions ]) { c.dispose(); } super.dispose(); } Future _pickDateRange() async { final range = await showDateRangePicker( context: context, firstDate: DateTime.now().subtract(const Duration(days: 1)), lastDate: DateTime.now().add(const Duration(days: 365 * 2)), initialDateRange: DateTimeRange(start: _validFrom, end: _validUntil), ); if (range != null) { setState(() { _validFrom = range.start; _validUntil = range.end; }); } } Future _submit() async { if (!_formKey.currentState!.validate()) return; setState(() => _loading = true); try { int discVal = int.tryParse(_discountValue.text) ?? 0; if (_type == 'FIXED_AMOUNT') discVal = discVal * 100; await ref.read(offerServiceProvider).createOffer({ 'title': _title.text.trim(), if (_description.text.isNotEmpty) 'description': _description.text.trim(), 'type': _type, 'discountValue': discVal, if (_promoCode.text.isNotEmpty) 'promoCode': _promoCode.text.trim(), 'isPublic': _isPublic, 'isActive': true, 'isFeatured': false, 'validFrom': '${_validFrom.toIso8601String().substring(0, 10)}T00:00:00.000Z', 'validUntil': '${_validUntil.toIso8601String().substring(0, 10)}T23:59:59.000Z', if (_maxRedemptions.text.isNotEmpty) 'maxRedemptions': int.parse(_maxRedemptions.text), 'categories': [], }); if (mounted) Navigator.of(context).pop(true); } catch (e) { if (mounted) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('Error: $e'))); } } finally { if (mounted) setState(() => _loading = false); } } @override Widget build(BuildContext context) { final fmt = DateFormat('MMM d, yyyy'); return Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: SingleChildScrollView( padding: const EdgeInsets.all(20), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row( children: [ const Text('New Offer', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold)), const Spacer(), if (_loading) const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2)) else TextButton( onPressed: _submit, child: const Text('Create')), ], ), const SizedBox(height: 16), TextFormField( controller: _title, decoration: const InputDecoration(labelText: 'Title'), validator: (v) => v == null || v.isEmpty ? 'Required' : null, ), const SizedBox(height: 12), TextFormField( controller: _description, decoration: const InputDecoration(labelText: 'Description'), maxLines: 2, ), const SizedBox(height: 12), InputDecorator( decoration: const InputDecoration(labelText: 'Type'), child: DropdownButton( value: _type, isExpanded: true, underline: const SizedBox(), items: _types .map((t) => DropdownMenuItem(value: t, child: Text(t))) .toList(), onChanged: (v) => setState(() => _type = v!), ), ), const SizedBox(height: 12), TextFormField( controller: _discountValue, decoration: InputDecoration( labelText: _type == 'PERCENTAGE' ? 'Discount (%)' : _type == 'FIXED_AMOUNT' ? 'Amount (\$)' : 'Free Days', ), keyboardType: TextInputType.number, validator: (v) => v == null || v.isEmpty ? 'Required' : null, ), const SizedBox(height: 12), TextFormField( controller: _promoCode, decoration: const InputDecoration( labelText: 'Promo Code (optional)'), textCapitalization: TextCapitalization.characters, ), const SizedBox(height: 12), TextFormField( controller: _maxRedemptions, decoration: const InputDecoration( labelText: 'Max Redemptions (optional)'), keyboardType: TextInputType.number, ), const SizedBox(height: 12), InkWell( onTap: _pickDateRange, child: InputDecorator( decoration: const InputDecoration(labelText: 'Valid Period'), child: Text( '${fmt.format(_validFrom)} → ${fmt.format(_validUntil)}'), ), ), const SizedBox(height: 12), SwitchListTile( contentPadding: EdgeInsets.zero, title: const Text('Public offer'), subtitle: const Text('Visible to customers on marketplace'), value: _isPublic, onChanged: (v) => setState(() => _isPublic = v), ), const SizedBox(height: 12), ElevatedButton( onPressed: _loading ? null : _submit, style: ElevatedButton.styleFrom( minimumSize: const Size.fromHeight(48), ), child: const Text('Create Offer'), ), ], ), ), ), ); } }