import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import '../../../suggestions/domain/suggestion.dart'; import '../../application/admin_suggestions_notifier.dart'; class AdminSuggestionsScreen extends ConsumerWidget { const AdminSuggestionsScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final async = ref.watch(adminSuggestionsStreamProvider); final theme = Theme.of(context); return Scaffold( body: async.when( loading: () => const Center(child: CircularProgressIndicator()), error: (err, _) => Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.error_outline, size: 48, color: theme.colorScheme.error, ), const SizedBox(height: 12), Text( 'Could not load suggestions', style: theme.textTheme.titleMedium, ), const SizedBox(height: 6), Text( '$err', textAlign: TextAlign.center, style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), ], ), ), ), data: (suggestions) { if (suggestions.isEmpty) { return Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.lightbulb_outline, size: 64, color: theme.colorScheme.onSurfaceVariant, ), const SizedBox(height: 16), Text( 'No suggestions yet', style: theme.textTheme.titleMedium, ), const SizedBox(height: 8), Text( 'Community ideas will show up here as they come in.', style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), ], ), ), ); } return ListView.builder( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), itemCount: suggestions.length, itemBuilder: (context, i) => _AdminSuggestionRow(suggestion: suggestions[i]), ); }, ), ); } } class _AdminSuggestionRow extends ConsumerWidget { const _AdminSuggestionRow({required this.suggestion}); final Suggestion suggestion; String _author() { if (suggestion.isAnonymous) return 'Anonymous'; final name = suggestion.displayName?.trim(); if (name != null && name.isNotEmpty) return name; final userId = suggestion.userId?.trim(); if (userId != null && userId.isNotEmpty) return 'User $userId'; return 'Unknown'; } Future _changeStatus(BuildContext context, WidgetRef ref) async { final picked = await showDialog( context: context, builder: (ctx) => SimpleDialog( title: const Text('Update status'), children: SuggestionStatus.values .map( (s) => SimpleDialogOption( onPressed: () => Navigator.of(ctx).pop(s), child: Row( children: [ Icon( s == suggestion.status ? Icons.radio_button_checked : Icons.radio_button_off, size: 18, ), const SizedBox(width: 12), Text(_statusLabel(s)), ], ), ), ) .toList(), ), ); if (picked == null || picked == suggestion.status) return; if (!context.mounted) return; try { await ref .read(adminSuggestionsNotifierProvider.notifier) .updateStatus(suggestion.id, picked); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Status set to ${_statusLabel(picked)}')), ); } } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Update failed: $e')), ); } } } Future _confirmDelete(BuildContext context, WidgetRef ref) async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Delete suggestion?'), content: const Text( 'This will permanently remove the suggestion. Use for spam or duplicates.', ), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(false), child: const Text('Cancel'), ), FilledButton.tonal( onPressed: () => Navigator.of(ctx).pop(true), child: const Text('Delete'), ), ], ), ); if (confirmed != true) return; if (!context.mounted) return; try { await ref .read(adminSuggestionsNotifierProvider.notifier) .delete(suggestion.id); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Suggestion deleted')), ); } } catch (e) { if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Delete failed: $e')), ); } } } @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); final colors = theme.colorScheme; final dateLabel = DateFormat.yMMMd().add_jm().format(suggestion.submittedAt); return Card( margin: const EdgeInsets.symmetric(vertical: 6), child: Padding( padding: const EdgeInsets.all(14), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Text( suggestion.text, style: theme.textTheme.bodyLarge, ), ), const SizedBox(width: 8), _StatusChip(status: suggestion.status), ], ), const SizedBox(height: 10), Row( children: [ Icon( suggestion.isAnonymous ? Icons.visibility_off_outlined : Icons.person_outline, size: 14, color: colors.onSurfaceVariant, ), const SizedBox(width: 4), Expanded( child: Text( _author(), style: theme.textTheme.bodySmall?.copyWith( color: colors.onSurfaceVariant, ), ), ), Icon(Icons.schedule, size: 14, color: colors.onSurfaceVariant), const SizedBox(width: 4), Text( dateLabel, style: theme.textTheme.bodySmall?.copyWith( color: colors.onSurfaceVariant, ), ), ], ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton.icon( onPressed: () => _confirmDelete(context, ref), icon: const Icon(Icons.delete_outline, size: 18), label: const Text('Delete'), ), const SizedBox(width: 8), OutlinedButton.icon( onPressed: () => _changeStatus(context, ref), icon: const Icon(Icons.flag_outlined, size: 18), label: const Text('Status'), ), ], ), ], ), ), ); } } String _statusLabel(SuggestionStatus s) { switch (s) { case SuggestionStatus.pending: return 'Pending'; case SuggestionStatus.reviewed: return 'Reviewed'; case SuggestionStatus.implemented: return 'Implemented'; } } class _StatusChip extends StatelessWidget { const _StatusChip({required this.status}); final SuggestionStatus status; @override Widget build(BuildContext context) { final theme = Theme.of(context); final colors = theme.colorScheme; final (Color background, Color foreground, String label) = switch (status) { SuggestionStatus.pending => ( colors.surfaceContainerHighest, colors.onSurfaceVariant, 'Pending', ), SuggestionStatus.reviewed => ( Colors.amber.withValues(alpha: 0.18), Colors.amber.shade300, 'Reviewed', ), SuggestionStatus.implemented => ( Colors.green.withValues(alpha: 0.18), Colors.green.shade300, 'Implemented', ), }; return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: background, borderRadius: BorderRadius.circular(12), ), child: Text( label, style: theme.textTheme.labelSmall?.copyWith( color: foreground, fontWeight: FontWeight.w600, ), ), ); } }