Chepuhagram/lib/presentation/screens/admin_panel_screen.dart

379 lines
13 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'package:chepuhagram/core/constants.dart';
import 'package:chepuhagram/domain/services/api_service.dart';
class AdminPanelScreen extends StatefulWidget {
const AdminPanelScreen({super.key});
@override
State<AdminPanelScreen> createState() => _AdminPanelScreenState();
}
class _AdminPanelScreenState extends State<AdminPanelScreen>
with SingleTickerProviderStateMixin {
late TabController _tabController;
final ApiService _apiService = ApiService();
List<dynamic> _users = [];
bool _isLoadingUsers = true;
// Контроллеры формы создания пользователя
final _idController = TextEditingController();
final _createFormKey = GlobalKey<FormState>();
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();
final _firstNameController = TextEditingController();
final _lastNameController = TextEditingController();
bool _isCreating = false;
@override
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
_loadUsers();
}
Future<void> _loadUsers() async {
setState(() => _isLoadingUsers = true);
try {
final token = await _apiService.getAccessToken();
final response = await Dio().get(
'${AppConstants.baseUrl}/admin/users',
options: Options(headers: {'Authorization': 'Bearer $token'}),
);
setState(() {
_users = response.data;
_isLoadingUsers = false;
});
} catch (e) {
setState(() => _isLoadingUsers = false);
_showSnackBar('Ошибка загрузки пользователей: $e');
}
}
Future<void> _toggleBlock(int userId, bool currentBlockStatus) async {
final action = currentBlockStatus ? 'unblock' : 'block';
try {
final token = await _apiService.getAccessToken();
await Dio().post(
'${AppConstants.baseUrl}/admin/users/$userId/$action',
options: Options(headers: {'Authorization': 'Bearer $token'}),
);
_showSnackBar(
currentBlockStatus
? 'Пользователь разблокирован'
: 'Пользователь заблокирован',
);
_loadUsers();
} catch (e) {
_showSnackBar('Ошибка изменения статуса: $e');
}
}
Future<void> _createUser() async {
if (!_createFormKey.currentState!.validate()) return;
setState(() => _isCreating = true);
try {
final token = await _apiService.getAccessToken();
final Map<String, dynamic> requestData = {
'username': _usernameController.text.trim(),
'password': _passwordController.text.trim(),
'first_name': _firstNameController.text.trim(),
'last_name': _lastNameController.text.trim(),
};
final idText = _idController.text.trim();
if (idText.isNotEmpty) {
requestData['id'] = int.tryParse(idText);
}
await Dio().post(
'${AppConstants.baseUrl}/admin/users',
data: requestData,
options: Options(headers: {'Authorization': 'Bearer $token'}),
);
_showSnackBar('Пользователь успешно создан!');
_idController.clear();
_usernameController.clear();
_passwordController.clear();
_firstNameController.clear();
_lastNameController.clear();
_loadUsers();
_tabController.animateTo(0);
} catch (e) {
_showSnackBar('Ошибка создания: $e');
} finally {
setState(() => _isCreating = false);
}
}
void _showSnackBar(String text) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(text), behavior: SnackBarBehavior.floating),
);
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: colorScheme.background,
appBar: AppBar(
title: const Text(
'Панель администратора',
style: TextStyle(fontWeight: FontWeight.bold),
),
elevation: 0,
backgroundColor: Colors.transparent,
bottom: TabBar(
controller: _tabController,
tabs: const [
Tab(icon: Icon(Icons.people_alt_rounded), text: 'Пользователи'),
Tab(
icon: Icon(Icons.person_add_alt_1_rounded),
text: 'Создать аккаунт',
),
],
),
),
body: TabBarView(
controller: _tabController,
children: [_buildUsersTab(colorScheme), _buildCreateTab(colorScheme)],
),
);
}
Widget _buildUsersTab(ColorScheme colorScheme) {
if (_isLoadingUsers)
return const Center(child: CircularProgressIndicator());
return RefreshIndicator(
onRefresh: _loadUsers,
child: ListView.builder(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.all(16),
itemCount: _users.length,
itemBuilder: (context, index) {
final user = _users[index];
final bool isBlocked = user['is_blocked'] == 1;
final int userId = user['id'];
if (userId == 1)
return const SizedBox.shrink(); // Скрываем супер-админа из списка менеджмента
return Card(
margin: const EdgeInsets.only(bottom: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
color: colorScheme.surfaceVariant.withOpacity(0.2),
elevation: 0,
child: ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
title: Text(
'${user['first_name']} ${user['last_name'] ?? ''}'.trim(),
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(
'@${user['username']}\nID: $userId',
style: TextStyle(color: colorScheme.outline, fontSize: 13),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.edit_note_rounded),
onPressed: () => _showEditUserDialog(user),
),
IconButton(
icon: Icon(
isBlocked
? Icons.lock_open_rounded
: Icons.lock_person_rounded,
),
color: isBlocked ? Colors.green : colorScheme.error,
onPressed: () => _toggleBlock(userId, isBlocked),
),
],
),
),
);
},
),
);
}
Widget _buildCreateTab(ColorScheme colorScheme) {
return Form(
key: _createFormKey,
child: ListView(
padding: const EdgeInsets.all(24),
children: [
_buildAdminInputField(
_idController,
'ID пользователя (оставьте пустым для автогенерации)',
Icons.fingerprint_rounded,
(v) {
if (v != null && v.isNotEmpty && int.tryParse(v) == null) {
return 'ID должен быть числом';
}
return null;
},
keyboardType: TextInputType.number,
),
_buildAdminInputField(
_usernameController,
'Имя пользователя (username)',
Icons.alternate_email_rounded,
(v) => v!.isEmpty ? 'Заполните юзернейм' : null,
),
_buildAdminInputField(
_passwordController,
'Временный пароль аккаунта',
Icons.password_rounded,
(v) => v!.length < 6 ? 'Минимум 6 символов' : null,
obscure: true,
),
_buildAdminInputField(
_firstNameController,
'Имя',
Icons.person_rounded,
(v) => v!.isEmpty ? 'Введите имя' : null,
),
_buildAdminInputField(
_lastNameController,
'Фамилия',
Icons.people_rounded,
null,
),
const SizedBox(height: 20),
SizedBox(
height: 50,
child: ElevatedButton(
onPressed: _isCreating ? null : _createUser,
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
child: _isCreating
? const CircularProgressIndicator()
: const Text(
'Зарегистрировать пользователя',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
),
],
),
);
}
Widget _buildAdminInputField(
TextEditingController controller,
String label,
IconData icon,
String? Function(String?)? validator, {
bool obscure = false,
TextInputType keyboardType = TextInputType.text,
}) {
final colorScheme = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: Container(
decoration: BoxDecoration(
color: colorScheme.surfaceVariant.withOpacity(0.15),
borderRadius: BorderRadius.circular(16),
),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
child: TextFormField(
controller: controller,
obscureText: obscure,
validator: validator,
keyboardType: keyboardType,
decoration: InputDecoration(
icon: Icon(icon, color: colorScheme.primary),
labelText: label,
border: InputBorder.none,
),
),
),
);
}
void _showEditUserDialog(Map<String, dynamic> user) {
final fNameController = TextEditingController(text: user['first_name']);
final lNameController = TextEditingController(text: user['last_name']);
final aboutController = TextEditingController(text: user['about']);
final phoneController = TextEditingController(text: user['phone']);
final emailController = TextEditingController(text: user['email']);
showDialog(
context: context,
builder: (ctx) => AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
title: Text('Редактирование ID: ${user['id']}'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: fNameController,
decoration: const InputDecoration(labelText: 'Имя'),
),
TextField(
controller: lNameController,
decoration: const InputDecoration(labelText: 'Фамилия'),
),
TextField(
controller: aboutController,
decoration: const InputDecoration(labelText: 'О себе'),
),
TextField(
controller: phoneController,
decoration: const InputDecoration(labelText: 'Телефон'),
),
TextField(
controller: emailController,
decoration: const InputDecoration(labelText: 'Почта'),
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('Отмена'),
),
ElevatedButton(
onPressed: () async {
try {
final token = await _apiService.getAccessToken();
await Dio().put(
'${AppConstants.baseUrl}/admin/users/${user['id']}/profile',
data: {
'first_name': fNameController.text.trim(),
'last_name': lNameController.text.trim(),
'about': aboutController.text.trim(),
'phone': phoneController.text.trim(),
'email': emailController.text.trim(),
},
options: Options(headers: {'Authorization': 'Bearer $token'}),
);
Navigator.pop(ctx);
_showSnackBar('Профиль успешно обновлен!');
_loadUsers();
} catch (e) {
_showSnackBar('Ошибка обновления: $e');
}
},
child: const Text('Сохранить'),
),
],
),
);
}
}