import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'dart:convert'; class MindMapAdminScreen extends ConsumerStatefulWidget { const MindMapAdminScreen({super.key}); @override ConsumerState createState() => _MindMapAdminScreenState(); } class _MindMapAdminScreenState extends ConsumerState { String _searchQuery = ''; String? _filterProject; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Управление Mind Maps'), actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: () => setState(() {}), tooltip: 'Обновить', ), ], ), body: Column( children: [ _buildSearchBar(), _buildFilterBar(), Expanded(child: _buildMindMapsList()), ], ), ); } Widget _buildSearchBar() { return Padding( padding: const EdgeInsets.all(16), child: TextField( decoration: InputDecoration( hintText: 'Поиск mind-карт...', prefixIcon: const Icon(Icons.search), suffixIcon: _searchQuery.isNotEmpty ? IconButton( icon: const Icon(Icons.clear), onPressed: () => setState(() => _searchQuery = ''), ) : null, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), ), onChanged: (value) => setState(() => _searchQuery = value), ), ); } Widget _buildFilterBar() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ const Icon(Icons.filter_list, color: Colors.grey), const SizedBox(width: 8), Expanded( child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: [ FilterChip( label: const Text('Все проекты'), selected: _filterProject == null, onSelected: (selected) => setState(() => _filterProject = null), ), const SizedBox(width: 8), FilterChip( label: const Text('Проект A'), selected: _filterProject == 'project_a', onSelected: (selected) => setState(() => _filterProject = 'project_a'), ), const SizedBox(width: 8), FilterChip( label: const Text('Проект B'), selected: _filterProject == 'project_b', onSelected: (selected) => setState(() => _filterProject = 'project_b'), ), ], ), ), ), ], ), ); } Widget _buildMindMapsList() { return StreamBuilder( stream: FirebaseFirestore.instance .collection('mind_map_nodes') .orderBy('createdAt', descending: true) .snapshots(), builder: (context, snapshot) { if (snapshot.hasError) { print('Firestore error: ${snapshot.error}'); return _buildErrorState(snapshot.error); } if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } final allDocs = snapshot.data?.docs ?? []; print('Total docs from Firestore: ${allDocs.length}'); // Преобразуем в Map для безопасной работы final mindMaps = >[]; for (final doc in allDocs) { try { final data = doc.data() as Map; data['_id'] = doc.id; mindMaps.add(data); } catch (e) { print('Error converting doc: $e'); } } // Фильтруем по проекту final filtered = mindMaps.where((data) { final projectId = data['projectId'] ?? 'unknown'; final title = data['title']?.toString().toLowerCase() ?? ''; final matchesProject = _filterProject == null || projectId == _filterProject; final matchesSearch = title.contains(_searchQuery.toLowerCase()); return matchesProject && matchesSearch; }).toList(); if (filtered.isEmpty) { return _buildEmptyState(); } return ListView.builder( padding: const EdgeInsets.all(16), itemCount: filtered.length, itemBuilder: (context, index) { final data = filtered[index]; final id = data['_id'] as String; return _buildMindMapCard(id, data); }, ); }, ); } Widget _buildMindMapCard(String id, Map data) { final title = data['title'] ?? 'Без названия'; final projectId = data['projectId'] ?? 'unknown'; final createdAt = data['createdAt'] != null ? (data['createdAt'] as Timestamp).toDate() : DateTime.now(); final nodeCount = data['nodeCount'] ?? 0; final maxLevel = data['maxLevel'] ?? 0; final createdBy = data['createdBy'] ?? 'unknown'; return Card( margin: const EdgeInsets.only(bottom: 12), child: ExpansionTile( leading: const Icon(Icons.account_tree, color: Colors.blue), title: Text( title, style: const TextStyle(fontWeight: FontWeight.bold), ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Проект: $projectId'), Text('Создана: ${_formatDate(createdAt)}'), ], ), trailing: PopupMenuButton( onSelected: (value) => _handleAction(id, value, data), itemBuilder: (context) => [ const PopupMenuItem( value: 'view', child: Row( children: [ Icon(Icons.visibility, size: 20), SizedBox(width: 12), Text('Просмотр'), ], ), ), const PopupMenuItem( value: 'export', child: Row( children: [ Icon(Icons.download, size: 20), SizedBox(width: 12), Text('Экспорт'), ], ), ), const PopupMenuItem( value: 'delete', child: Row( children: [ Icon(Icons.delete, color: Colors.red, size: 20), SizedBox(width: 12), Text('Удалить', style: TextStyle(color: Colors.red)), ], ), ), ], ), children: [ Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildStatRow('Узлов', nodeCount.toString()), _buildStatRow('Уровней', maxLevel.toString()), _buildStatRow('Автор', createdBy), const SizedBox(height: 16), Row( children: [ Expanded( child: OutlinedButton.icon( onPressed: () => _handleAction(id, 'export', data), icon: const Icon(Icons.download, size: 18), label: const Text('Экспорт JSON'), ), ), const SizedBox(width: 16), Expanded( child: ElevatedButton.icon( onPressed: () => _handleAction(id, 'delete', data), icon: const Icon(Icons.delete, size: 18), label: const Text('Удалить'), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, ), ), ), ], ), ], ), ), ], ), ); } Widget _buildStatRow(String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, style: TextStyle(color: Colors.grey[600])), Text(value, style: const TextStyle(fontWeight: FontWeight.bold)), ], ), ); } Widget _buildEmptyState() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.account_tree, size: 80, color: Colors.grey[400]), const SizedBox(height: 16), Text( 'Mind-карты не найдены', style: TextStyle(fontSize: 18, color: Colors.grey[600]), ), const SizedBox(height: 8), Text( 'Создайте первую mind-карту', style: TextStyle(color: Colors.grey[500]), ), ], ), ); } Widget _buildErrorState(dynamic error) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline, size: 64, color: Colors.red), const SizedBox(height: 16), Text('Ошибка: $error'), const SizedBox(height: 16), ElevatedButton( onPressed: () => setState(() {}), child: const Text('Повторить'), ), ], ), ); } Future _handleAction( String id, String action, Map data) async { switch (action) { case 'view': // TODO: Открыть mind map ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Просмотр в разработке')), ); break; case 'export': await _exportMindMap(id, data); break; case 'delete': await _confirmDelete(id, data['title'] ?? 'Без названия'); break; } } Future _exportMindMap(String id, Map data) async { try { final jsonData = { 'id': id, 'title': data['title'], 'projectId': data['projectId'], 'createdAt': data['createdAt']?.toString(), 'data': data, }; final jsonStr = const JsonEncoder.withIndent(' ').convert(jsonData); // TODO: Сохранить в файл if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('📤 Экспорт: скопируйте JSON из консоли'), backgroundColor: Colors.green, ), ); print('=== EXPORT JSON ==='); print(jsonStr); print('=== END EXPORT ==='); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('❌ Ошибка экспорта: $e'), backgroundColor: Colors.red, ), ); } } } Future _confirmDelete(String id, String title) async { final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Удалить mind-карту?'), content: Text( '"$title" будет удалена навсегда. Это действие нельзя отменить.'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('Отмена'), ), TextButton( onPressed: () => Navigator.pop(context, true), style: TextButton.styleFrom(foregroundColor: Colors.red), child: const Text('Удалить'), ), ], ), ); if (confirmed == true) { await _deleteMindMap(id); } } Future _deleteMindMap(String id) async { try { await FirebaseFirestore.instance .collection('mind_map_nodes') .doc(id) .delete(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('✅ Mind-карта удалена'), backgroundColor: Colors.green, ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('❌ Ошибка удаления: $e'), backgroundColor: Colors.red, ), ); } } } String _formatDate(DateTime date) { final now = DateTime.now(); final diff = now.difference(date); if (diff.inMinutes < 1) return 'только что'; if (diff.inMinutes < 60) return '${diff.inMinutes} мин. назад'; if (diff.inHours < 24) return '${diff.inHours} ч. назад'; if (diff.inDays < 7) return '${diff.inDays} дн. назад'; return '${date.day}.${date.month}.${date.year}'; } }