import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../models/notification_model.dart'; import '../providers/notification_provider.dart'; import '../theme/app_theme.dart'; import '../widgets/app_sidebar.dart'; import '../widgets/responsive_layout.dart'; class NotificationsScreen extends StatelessWidget { const NotificationsScreen({super.key}); @override Widget build(BuildContext context) { return const ResponsiveLayout( title: 'اعلان‌ها', sidebar: AppSidebar(), body: _NotificationsBody(), ); } } class _NotificationsBody extends StatefulWidget { const _NotificationsBody(); @override State<_NotificationsBody> createState() => _NotificationsBodyState(); } class _NotificationsBodyState extends State<_NotificationsBody> { final _searchCtrl = TextEditingController(); String _search = ''; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { context.read().loadNotifications(); }); } @override void dispose() { _searchCtrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // ── Header ──────────────────────────────────────────────── Row( children: [ const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'اعلان‌های سیستم', style: TextStyle( fontSize: 24, fontWeight: FontWeight.w800, color: AppTheme.textPrimary, ), ), SizedBox(height: 4), Text( 'مشاهده اعلان‌ها و ارسال اعلان عمومی', style: TextStyle( fontSize: 14, color: AppTheme.textSecondary), ), ], ), ), ElevatedButton.icon( onPressed: () => _showBroadcastDialog(context), icon: const Icon(Icons.campaign_rounded, size: 18), label: const Text('ارسال اعلان عمومی'), ), ], ), const SizedBox(height: 20), // ── Search ─────────────────────────────────────────────── TextField( controller: _searchCtrl, onChanged: (v) => setState(() => _search = v.toLowerCase()), decoration: const InputDecoration( hintText: 'جستجوی اعلان...', prefixIcon: Icon(Icons.search_rounded), ), ), const SizedBox(height: 16), // ── Table ──────────────────────────────────────────────── Expanded( child: Consumer( builder: (_, provider, __) { if (provider.isLoading) { return const Center(child: CircularProgressIndicator()); } if (provider.status == NotificationLoadStatus.error) { return _ErrorView( message: provider.error ?? 'خطا', onRetry: () => provider.loadNotifications(), ); } final filtered = provider.items.where((n) { if (_search.isEmpty) return true; final text = [ n.title, n.description ?? '', n.receiverId, n.senderId ?? '', ].join(' ').toLowerCase(); return text.contains(_search); }).toList(); if (filtered.isEmpty) { return const _EmptyView( icon: Icons.notifications_none_rounded, message: 'اعلانی یافت نشد', ); } return _NotificationsTable(items: filtered); }, ), ), ], ), ); } void _showBroadcastDialog(BuildContext context) { showDialog( context: context, builder: (_) => const _BroadcastDialog(), ); } } // ── Broadcast Dialog ───────────────────────────────────────────────────────── class _BroadcastDialog extends StatefulWidget { const _BroadcastDialog(); @override State<_BroadcastDialog> createState() => _BroadcastDialogState(); } class _BroadcastDialogState extends State<_BroadcastDialog> { final _formKey = GlobalKey(); final _titleCtrl = TextEditingController(); final _descCtrl = TextEditingController(); bool _loading = false; String? _error; bool _sent = false; @override void dispose() { _titleCtrl.dispose(); _descCtrl.dispose(); super.dispose(); } Future _submit() async { if (!_formKey.currentState!.validate()) return; setState(() { _loading = true; _error = null; }); final title = _titleCtrl.text.trim(); final desc = _descCtrl.text.trim(); final provider = context.read(); final success = await provider.sendPublic(title, desc); if (!mounted) return; setState(() => _loading = false); if (success) { setState(() => _sent = true); } else { setState(() => _error = provider.error ?? 'خطا در ارسال اعلان'); } } @override Widget build(BuildContext context) { if (_sent) { return AlertDialog( title: const Row( children: [ Icon(Icons.check_circle_rounded, color: AppTheme.success), SizedBox(width: 10), Text('اعلان ارسال شد'), ], ), content: const Text('اعلان عمومی با موفقیت به همه کاربران ارسال شد.'), actions: [ ElevatedButton( onPressed: () => Navigator.of(context).pop(), child: const Text('بستن'), ), ], ); } return AlertDialog( title: const Row( children: [ Icon(Icons.campaign_rounded, color: AppTheme.primary), SizedBox(width: 10), Text('ارسال اعلان عمومی'), ], ), content: SizedBox( width: 440, child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TextFormField( controller: _titleCtrl, decoration: const InputDecoration( labelText: 'عنوان اعلان', prefixIcon: Icon(Icons.title_rounded), ), validator: (v) => (v == null || v.trim().isEmpty) ? 'عنوان الزامی است' : null, ), const SizedBox(height: 16), TextFormField( controller: _descCtrl, maxLines: 4, decoration: const InputDecoration( labelText: 'متن اعلان (اختیاری)', prefixIcon: Icon(Icons.description_outlined), alignLabelWithHint: true, ), ), if (_error != null) ...[ const SizedBox(height: 12), Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: AppTheme.danger.withValues(alpha: 0.08), borderRadius: BorderRadius.circular(8), border: Border.all( color: AppTheme.danger.withValues(alpha: 0.3)), ), child: Text( _error!, style: const TextStyle( color: AppTheme.danger, fontSize: 13), ), ), ], ], ), ), ), actions: [ TextButton( onPressed: _loading ? null : () => Navigator.of(context).pop(), child: const Text('انصراف'), ), ElevatedButton.icon( onPressed: _loading ? null : _submit, icon: _loading ? const SizedBox( width: 14, height: 14, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 2)) : const Icon(Icons.send_rounded, size: 16), label: const Text('ارسال به همه'), ), ], ); } } // ── Notifications Table ───────────────────────────────────────────────────── class _NotificationsTable extends StatelessWidget { final List items; const _NotificationsTable({required this.items}); @override Widget build(BuildContext context) { return Card( child: ClipRRect( borderRadius: BorderRadius.circular(12), child: SingleChildScrollView( child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: DataTable( columns: const [ DataColumn(label: Text('عنوان')), DataColumn(label: Text('نوع')), DataColumn(label: Text('گیرنده')), DataColumn(label: Text('وضعیت')), ], rows: items.map((n) => _buildRow(n)).toList(), ), ), ), ), ); } DataRow _buildRow(NotificationModel n) { return DataRow( cells: [ DataCell( SizedBox( width: 280, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( n.title, style: const TextStyle(fontWeight: FontWeight.w600), ), if ((n.description ?? '').isNotEmpty) ...[ const SizedBox(height: 4), Text( n.description!, style: const TextStyle( color: AppTheme.textSecondary, fontSize: 12, ), ), ], if (n.groupId != null) ...[ const SizedBox(height: 4), Text( 'گروه: ${n.groupId}', style: const TextStyle( color: AppTheme.textSecondary, fontSize: 11, ), ), ], ], ), ), ), DataCell(_TypeBadge(type: n.type)), DataCell( Text( n.receiverId, style: const TextStyle(color: AppTheme.textSecondary), ), ), DataCell(_StatusBadge(type: n.type, isAccepted: n.isAccepted)), ], ); } } class _TypeBadge extends StatelessWidget { final NotificationType type; const _TypeBadge({required this.type}); @override Widget build(BuildContext context) { final color = type == NotificationType.public ? AppTheme.primary : const Color(0xFF0891B2); return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: color.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(20), ), child: Text( type.label, style: TextStyle( color: color, fontSize: 12, fontWeight: FontWeight.w600, ), ), ); } } class _StatusBadge extends StatelessWidget { final NotificationType type; final bool? isAccepted; const _StatusBadge({required this.type, required this.isAccepted}); @override Widget build(BuildContext context) { String label; Color color; if (type == NotificationType.public) { label = 'ارسال شد'; color = AppTheme.success; } else { if (isAccepted == true) { label = 'تأیید شد'; color = AppTheme.success; } else if (isAccepted == false) { label = 'رد شد'; color = AppTheme.danger; } else { label = 'در انتظار'; color = AppTheme.warning; } } return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: color.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(20), ), child: Text( label, style: TextStyle( color: color, fontSize: 12, fontWeight: FontWeight.w600, ), ), ); } } class _EmptyView extends StatelessWidget { final IconData icon; final String message; const _EmptyView({required this.icon, required this.message}); @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 64, color: AppTheme.border), const SizedBox(height: 16), Text( message, style: const TextStyle(color: AppTheme.textSecondary, fontSize: 16), ), ], ), ); } } class _ErrorView extends StatelessWidget { final String message; final VoidCallback onRetry; const _ErrorView({required this.message, required this.onRetry}); @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline_rounded, size: 64, color: AppTheme.danger), const SizedBox(height: 16), Text(message, style: const TextStyle(color: AppTheme.textSecondary)), const SizedBox(height: 16), ElevatedButton.icon( onPressed: onRetry, icon: const Icon(Icons.refresh_rounded), label: const Text('تلاش مجدد'), ), ], ), ); } }