import 'package:flutter/material.dart'; import 'package:intl/intl.dart' hide TextDirection; import 'package:provider/provider.dart'; import '../models/user_model.dart'; import '../providers/user_provider.dart'; import '../theme/app_theme.dart'; import '../widgets/app_sidebar.dart'; import '../widgets/responsive_layout.dart'; import '../widgets/secret_dialog.dart'; class UsersScreen extends StatefulWidget { const UsersScreen({super.key}); @override State createState() => _UsersScreenState(); } class _UsersScreenState extends State { final _searchCtrl = TextEditingController(); String _search = ''; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { context.read().loadUsers(); }); } @override void dispose() { _searchCtrl.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return ResponsiveLayout( title: 'کاربران', sidebar: const AppSidebar(), body: _UsersBody( search: _search, searchCtrl: _searchCtrl, onSearch: (v) => setState(() => _search = v.toLowerCase()), ), ); } } class _UsersBody extends StatelessWidget { final String search; final TextEditingController searchCtrl; final ValueChanged onSearch; const _UsersBody({ required this.search, required this.searchCtrl, required this.onSearch, }); @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: () => _showCreateDialog(context), icon: const Icon(Icons.person_add_rounded, size: 18), label: const Text('کاربر جدید'), ), ], ), const SizedBox(height: 20), // ── Search ─────────────────────────────────────────────── TextField( controller: searchCtrl, onChanged: onSearch, 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.name == 'error') { return _ErrorView( message: provider.error ?? 'خطا', onRetry: () => provider.loadUsers()); } final filtered = provider.users .where((u) => search.isEmpty || u.username.toLowerCase().contains(search) || u.role.label.toLowerCase().contains(search)) .toList(); if (filtered.isEmpty) { return const _EmptyView( icon: Icons.people_outline_rounded, message: 'کاربری یافت نشد', ); } return _UsersTable(users: filtered); }, ), ), ], ), ); } void _showCreateDialog(BuildContext context) { showDialog( context: context, builder: (_) => const _CreateUserDialog(), ); } } // ── Create User Dialog ────────────────────────────────────────────────────── class _CreateUserDialog extends StatefulWidget { const _CreateUserDialog(); @override State<_CreateUserDialog> createState() => _CreateUserDialogState(); } class _CreateUserDialogState extends State<_CreateUserDialog> { final _formKey = GlobalKey(); final _usernameCtrl = TextEditingController(); UserRole _role = UserRole.member; bool _loading = false; String? _error; @override void dispose() { _usernameCtrl.dispose(); super.dispose(); } Future _submit() async { if (!_formKey.currentState!.validate()) return; setState(() { _loading = true; _error = null; }); final provider = context.read(); final result = await provider.createUser( _usernameCtrl.text.trim(), _role, ); if (!mounted) return; setState(() => _loading = false); if (result != null) { Navigator.of(context).pop(); await SecretDialog.show( context, username: result.user.username, secret: result.secret, ); } else { setState(() => _error = provider.error); } } @override Widget build(BuildContext context) { return AlertDialog( title: const Row( children: [ Icon(Icons.person_add_rounded, color: AppTheme.primary), SizedBox(width: 10), Text('ایجاد کاربر جدید'), ], ), content: SizedBox( width: 400, child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ TextFormField( controller: _usernameCtrl, textDirection: TextDirection.ltr, decoration: const InputDecoration( labelText: 'نام کاربری', prefixIcon: Icon(Icons.person_outline_rounded), ), validator: (v) => (v == null || v.trim().isEmpty) ? 'نام کاربری الزامی است' : null, ), const SizedBox(height: 16), DropdownButtonFormField( initialValue: _role, decoration: const InputDecoration( labelText: 'نقش', prefixIcon: Icon(Icons.badge_outlined), ), items: UserRole.values .map((r) => DropdownMenuItem( value: r, child: Text(r.label), )) .toList(), onChanged: (v) => setState(() => _role = v!), ), 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( onPressed: _loading ? null : _submit, child: _loading ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 2)) : const Text('ایجاد'), ), ], ); } } // ── Users Table ────────────────────────────────────────────────────────────── class _UsersTable extends StatelessWidget { final List users; const _UsersTable({required this.users}); @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('تاریخ ایجاد')), DataColumn(label: Text('عملیات')), ], rows: users.map((user) => _buildRow(context, user)).toList(), ), ), ), ), ); } DataRow _buildRow(BuildContext context, UserModel user) { return DataRow( cells: [ DataCell( Row( mainAxisSize: MainAxisSize.min, children: [ CircleAvatar( radius: 16, backgroundColor: AppTheme.primary.withValues(alpha: 0.12), child: Text( user.username[0].toUpperCase(), style: const TextStyle( color: AppTheme.primary, fontSize: 13, fontWeight: FontWeight.w700, ), ), ), const SizedBox(width: 10), Text( user.username, style: const TextStyle(fontWeight: FontWeight.w600), ), ], ), ), DataCell(_RoleBadge(role: user.role)), DataCell(_StatusBadge(isActive: user.isActive)), DataCell( Text( user.createdAt != null ? DateFormat('yyyy/MM/dd').format(user.createdAt!) : '—', style: const TextStyle(color: AppTheme.textSecondary), ), ), DataCell( _ResetSecretButton(user: user), ), ], ); } } class _ResetSecretButton extends StatefulWidget { final UserModel user; const _ResetSecretButton({required this.user}); @override State<_ResetSecretButton> createState() => _ResetSecretButtonState(); } class _ResetSecretButtonState extends State<_ResetSecretButton> { bool _loading = false; Future _reset() async { final confirmed = await showDialog( context: context, builder: (_) => AlertDialog( title: const Text('تأیید ریست رمز'), content: Text( 'آیا مطمئن هستید که می‌خواهید رمز «${widget.user.username}» را ریست کنید؟'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('انصراف'), ), ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom(backgroundColor: AppTheme.warning), child: const Text('ریست'), ), ], ), ); if (confirmed != true || !mounted) return; setState(() => _loading = true); final provider = context.read(); final secret = await provider.resetSecret(widget.user.id); if (!mounted) return; setState(() => _loading = false); if (secret != null) { await SecretDialog.show( context, username: widget.user.username, secret: secret, isReset: true, ); } else if (provider.error != null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(provider.error!), backgroundColor: AppTheme.danger, ), ); } } @override Widget build(BuildContext context) { if (_loading) { return const SizedBox( width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2)); } return TextButton.icon( onPressed: _reset, icon: const Icon(Icons.lock_reset_rounded, size: 16), label: const Text('ریست رمز'), style: TextButton.styleFrom(foregroundColor: AppTheme.warning), ); } } // ── Shared small widgets ───────────────────────────────────────────────────── class _RoleBadge extends StatelessWidget { final UserRole role; const _RoleBadge({required this.role}); @override Widget build(BuildContext context) { final color = AppTheme.roleColor(role.name); return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: color.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(20), ), child: Text( role.label, style: TextStyle( color: color, fontSize: 12, fontWeight: FontWeight.w600, ), ), ); } } class _StatusBadge extends StatelessWidget { final bool isActive; const _StatusBadge({required this.isActive}); @override Widget build(BuildContext context) { final color = isActive ? AppTheme.success : AppTheme.danger; return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: color.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 6, height: 6, decoration: BoxDecoration(color: color, shape: BoxShape.circle), ), const SizedBox(width: 5), Text( isActive ? 'فعال' : 'غیرفعال', 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('تلاش مجدد'), ), ], ), ); } }