Neda/admin_panel/lib/screens/group_detail_screen.dart

564 lines
18 KiB
Dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import '../models/group_member_model.dart';
import '../models/user_model.dart';
import '../providers/group_provider.dart';
import '../providers/user_provider.dart';
import '../theme/app_theme.dart';
import '../widgets/app_sidebar.dart';
import '../widgets/responsive_layout.dart';
class GroupDetailScreen extends StatefulWidget {
final String groupId;
const GroupDetailScreen({super.key, required this.groupId});
@override
State<GroupDetailScreen> createState() => _GroupDetailScreenState();
}
class _GroupDetailScreenState extends State<GroupDetailScreen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final groups = context.read<GroupProvider>();
groups.loadGroupMembers(widget.groupId);
if (groups.groups.isEmpty) groups.loadGroups();
context.read<UserProvider>().loadUsers();
});
}
@override
Widget build(BuildContext context) {
return Consumer<GroupProvider>(builder: (_, groupProvider, __) {
final group =
groupProvider.groups.where((g) => g.id == widget.groupId).firstOrNull;
return ResponsiveLayout(
title: group?.name ?? 'جزئیات گروه',
sidebar: const AppSidebar(),
body: _GroupDetailBody(
groupId: widget.groupId,
groupName: group?.name ?? '...',
isActive: group?.isActive ?? true,
),
);
});
}
}
class _GroupDetailBody extends StatelessWidget {
final String groupId;
final String groupName;
final bool isActive;
const _GroupDetailBody({
required this.groupId,
required this.groupName,
required this.isActive,
});
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ── Back button + Header ──────────────────────────────────
Row(
children: [
IconButton(
onPressed: () => context.go('/groups'),
icon: const Icon(Icons.arrow_back_rounded),
tooltip: 'بازگشت',
),
const SizedBox(width: 8),
Expanded(
child: Text(
groupName,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.w800,
color: AppTheme.textPrimary,
),
),
),
ElevatedButton.icon(
onPressed: () => _showAddMemberDialog(context),
icon: const Icon(Icons.person_add_rounded, size: 18),
label: const Text('افزودن عضو'),
),
],
),
const SizedBox(height: 20),
// ── Group info card ───────────────────────────────────────
Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Row(
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: const Color(0xFF7C3AED).withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(14),
),
child: const Icon(
Icons.groups_rounded,
color: Color(0xFF7C3AED),
size: 28,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
groupName,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: AppTheme.textPrimary,
),
),
const SizedBox(width: 10),
_StatusBadge(isActive: isActive),
],
),
],
),
),
],
),
),
),
const SizedBox(height: 24),
// ── Members section ───────────────────────────────────────
const Text(
'اعضای گروه',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: AppTheme.textPrimary,
),
),
const SizedBox(height: 12),
_MembersTable(groupId: groupId),
],
),
);
}
void _showAddMemberDialog(BuildContext context) {
showDialog(
context: context,
builder: (_) => _AddMemberDialog(groupId: groupId),
);
}
}
// ── Add Member Dialog ────────────────────────────────────────────────────────
class _AddMemberDialog extends StatefulWidget {
final String groupId;
const _AddMemberDialog({required this.groupId});
@override
State<_AddMemberDialog> createState() => _AddMemberDialogState();
}
class _AddMemberDialogState extends State<_AddMemberDialog> {
UserModel? _selectedUser;
bool _loading = false;
String? _error;
Future<void> _submit() async {
if (_selectedUser == null) {
setState(() => _error = 'لطفاً یک کاربر انتخاب کنید');
return;
}
setState(() {
_loading = true;
_error = null;
});
final provider = context.read<GroupProvider>();
final currentMembers = provider.membersOf(widget.groupId);
if (currentMembers.any((m) => m.username == _selectedUser!.username)) {
setState(() {
_loading = false;
_error = 'این کاربر قبلاً عضو این گروه است';
});
return;
}
final success = await provider.inviteMember(
widget.groupId,
_selectedUser!.username,
);
if (!mounted) return;
setState(() => _loading = false);
if (success) {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('«${_selectedUser!.username}» به گروه اضافه شد'),
backgroundColor: AppTheme.success,
),
);
} else {
setState(() => _error = provider.error);
}
}
@override
Widget build(BuildContext context) {
return Consumer<UserProvider>(builder: (_, userProvider, __) {
final users = userProvider.users;
return AlertDialog(
title: const Row(
children: [
Icon(Icons.person_add_rounded, color: AppTheme.primary),
SizedBox(width: 10),
Text('افزودن عضو به گروه'),
],
),
content: SizedBox(
width: 400,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// User selector
DropdownButtonFormField<UserModel>(
initialValue: _selectedUser,
decoration: const InputDecoration(
labelText: 'انتخاب کاربر',
prefixIcon: Icon(Icons.person_outline_rounded),
),
hint: const Text('کاربر را انتخاب کنید'),
items: users
.map((u) => DropdownMenuItem(
value: u,
child: Text(
'${u.username} (${u.role.label})',
),
))
.toList(),
onChanged: (v) => setState(() => _selectedUser = 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('افزودن'),
),
],
);
});
}
}
// ── Members Table ────────────────────────────────────────────────────────────
class _MembersTable extends StatelessWidget {
final String groupId;
const _MembersTable({required this.groupId});
@override
Widget build(BuildContext context) {
return Consumer<GroupProvider>(builder: (_, provider, __) {
final members = provider.membersOf(groupId);
if (members.isEmpty) {
return Card(
child: Padding(
padding: const EdgeInsets.all(40),
child: Center(
child: Column(
children: [
const Icon(Icons.people_outline_rounded,
size: 56, color: AppTheme.border),
const SizedBox(height: 12),
const Text(
'هنوز عضوی به این گروه اضافه نشده است',
style: TextStyle(color: AppTheme.textSecondary),
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: () => showDialog(
context: context,
builder: (_) => _AddMemberDialog(groupId: groupId),
),
icon: const Icon(Icons.person_add_rounded, size: 16),
label: const Text('افزودن اولین عضو'),
),
],
),
),
),
);
}
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: members.map((m) => _buildRow(context, m)).toList(),
),
),
),
),
);
});
}
DataRow _buildRow(BuildContext context, GroupMemberModel member) {
final color = member.role == GroupRole.manager
? const Color(0xFF0891B2)
: AppTheme.textSecondary;
return DataRow(
cells: [
DataCell(
Row(
mainAxisSize: MainAxisSize.min,
children: [
CircleAvatar(
radius: 16,
backgroundColor: AppTheme.primary.withValues(alpha: 0.12),
child: Text(
(member.username ?? member.userId)[0].toUpperCase(),
style: const TextStyle(
color: AppTheme.primary,
fontSize: 13,
fontWeight: FontWeight.w700,
),
),
),
const SizedBox(width: 10),
Text(
member.username ?? member.userId,
style: const TextStyle(fontWeight: FontWeight.w600),
),
],
),
),
DataCell(
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(20),
),
child: Text(
member.role.label,
style: TextStyle(
color: color,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
),
DataCell(
_OnlineBadge(isOnline: member.isOnline),
),
DataCell(
_RemoveMemberButton(groupId: groupId, member: member),
),
],
);
}
}
class _RemoveMemberButton extends StatefulWidget {
final String groupId;
final GroupMemberModel member;
const _RemoveMemberButton({required this.groupId, required this.member});
@override
State<_RemoveMemberButton> createState() => _RemoveMemberButtonState();
}
class _RemoveMemberButtonState extends State<_RemoveMemberButton> {
bool _loading = false;
Future<void> _remove() async {
final confirmed = await showDialog<bool>(
context: context,
builder: (_) => AlertDialog(
title: const Text('تأیید حذف عضو'),
content: Text(
'آیا مطمئن هستید که می‌خواهید «${widget.member.username ?? widget.member.userId}» را از این گروه حذف کنید؟'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('انصراف'),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
style: ElevatedButton.styleFrom(backgroundColor: AppTheme.danger),
child: const Text('حذف'),
),
],
),
);
if (confirmed != true || !mounted) return;
setState(() => _loading = true);
final provider = context.read<GroupProvider>();
final success =
await provider.removeMember(widget.groupId, widget.member.userId);
if (!mounted) return;
setState(() => _loading = false);
if (success) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('عضو از گروه حذف شد'),
backgroundColor: AppTheme.success,
),
);
} 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 IconButton(
onPressed: _remove,
icon: const Icon(Icons.person_remove_rounded, size: 18),
color: AppTheme.danger,
tooltip: 'حذف از گروه',
);
}
}
// ── Status Badge ─────────────────────────────────────────────────────────────
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: Text(
isActive ? 'فعال' : 'غیرفعال',
style: TextStyle(
color: color,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
);
}
}
class _OnlineBadge extends StatelessWidget {
final bool isOnline;
const _OnlineBadge({required this.isOnline});
@override
Widget build(BuildContext context) {
final color = isOnline ? AppTheme.success : AppTheme.textSecondary;
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(
isOnline ? 'آنلاین' : 'آفلاین',
style: TextStyle(
color: color,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
],
),
);
}
}