Neda/admin_panel/lib/screens/group_detail_screen.dart
2026-03-07 19:18:52 +03:30

502 lines
16 KiB
Dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart' hide TextDirection;
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 ?? '...',
groupDescription: group?.description,
isActive: group?.isActive ?? true,
createdAt: group?.createdAt,
),
);
});
}
}
class _GroupDetailBody extends StatelessWidget {
final String groupId;
final String groupName;
final String? groupDescription;
final bool isActive;
final DateTime? createdAt;
const _GroupDetailBody({
required this.groupId,
required this.groupName,
required this.groupDescription,
required this.isActive,
required this.createdAt,
});
@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),
],
),
if (groupDescription != null) ...[
const SizedBox(height: 4),
Text(
groupDescription!,
style: const TextStyle(
color: AppTheme.textSecondary,
fontSize: 14,
),
),
],
const SizedBox(height: 4),
Text(
createdAt != null
? 'ایجاد شده در ${DateFormat('yyyy/MM/dd').format(createdAt!)}'
: '',
style: const TextStyle(
color: AppTheme.textSecondary,
fontSize: 12,
),
),
],
),
),
],
),
),
),
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;
GroupRole _role = GroupRole.member;
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.userId == _selectedUser!.id)) {
setState(() {
_loading = false;
_error = 'این کاربر قبلاً عضو این گروه است';
});
return;
}
final result = await provider.addMember(
widget.groupId,
_selectedUser!.id,
_role,
_selectedUser!.username,
);
if (!mounted) return;
setState(() => _loading = false);
if (result != null) {
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),
),
const SizedBox(height: 16),
// Role selector
DropdownButtonFormField<GroupRole>(
initialValue: _role,
decoration: const InputDecoration(
labelText: 'نقش در گروه',
prefixIcon: Icon(Icons.badge_outlined),
),
items: GroupRole.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('افزودن'),
),
],
);
});
}
}
// ── 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('تاریخ عضویت')),
],
rows: members.map((m) => _buildRow(m)).toList(),
),
),
),
),
);
});
}
DataRow _buildRow(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(
Text(
member.joinedAt != null
? DateFormat('yyyy/MM/dd').format(member.joinedAt!)
: '',
style: const TextStyle(color: AppTheme.textSecondary),
),
),
],
);
}
}
// ── 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,
),
),
);
}
}