import 'package:flutter/material.dart'; import 'api.dart'; import 'screens.dart'; /// Админ-консоль «Феникс 6X» (m08): Flutter Web, Material 3, /// тёмная тема (UI/UX-стандарт). Токен — sessionStorage; 401 → логин. void main() => runApp(const AdminApp()); class AdminApp extends StatelessWidget { const AdminApp({super.key}); @override Widget build(BuildContext context) => MaterialApp( title: 'Феникс 6X — консоль владельца', debugShowCheckedModeBanner: false, theme: ThemeData( brightness: Brightness.dark, colorSchemeSeed: const Color(0xFF4FC3F7), useMaterial3: true, ), home: const RootPage(), ); } class RootPage extends StatefulWidget { const RootPage({super.key}); @override State createState() => _RootPageState(); } class _RootPageState extends State { int _index = 0; @override Widget build(BuildContext context) { if (!Api.authorized) return LoginPage(onDone: _reload); const pages = [ AthletesPage(), WorkoutsPage(), ProgramsPage(), MetricsPage(), PlanPage(), ]; return Scaffold( body: Row( children: [ NavigationRail( selectedIndex: _index, onDestinationSelected: (i) => setState(() => _index = i), labelType: NavigationRailLabelType.all, destinations: const [ NavigationRailDestination( icon: Icon(Icons.people_outline), selectedIcon: Icon(Icons.people), label: Text('Атлеты')), NavigationRailDestination( icon: Icon(Icons.fitness_center_outlined), selectedIcon: Icon(Icons.fitness_center), label: Text('Тренировки')), NavigationRailDestination( icon: Icon(Icons.list_alt_outlined), selectedIcon: Icon(Icons.list_alt), label: Text('Программы')), NavigationRailDestination( icon: Icon(Icons.insights_outlined), selectedIcon: Icon(Icons.insights), label: Text('Метрики')), NavigationRailDestination( icon: Icon(Icons.event_available_outlined), selectedIcon: Icon(Icons.event_available), label: Text('План/факт')), ], ), const VerticalDivider(width: 1), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Align( alignment: Alignment.centerRight, child: Padding( padding: const EdgeInsets.all(8), child: OutlinedButton( onPressed: () { Api.logout(); setState(() {}); }, child: const Text('Выйти'), ), ), ), Expanded(child: pages[_index]), ], ), ), ], ), ); } void _reload() => setState(() {}); } class LoginPage extends StatefulWidget { const LoginPage({super.key, required this.onDone}); final VoidCallback onDone; @override State createState() => _LoginPageState(); } class _LoginPageState extends State { final _login = TextEditingController(); final _password = TextEditingController(); String? _error; @override Widget build(BuildContext context) => Scaffold( body: Center( child: SizedBox( width: 360, child: Card( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('Феникс 6X', style: Theme.of(context).textTheme.headlineMedium), const Text('консоль владельца'), const SizedBox(height: 24), TextField( controller: _login, decoration: const InputDecoration(labelText: 'Логин')), TextField( controller: _password, obscureText: true, onSubmitted: (_) => _submit(), decoration: const InputDecoration(labelText: 'Пароль')), const SizedBox(height: 16), FilledButton( onPressed: _submit, child: const Text('Войти'), ), if (_error != null) Padding( padding: const EdgeInsets.only(top: 12), child: Text(_error!, style: const TextStyle(color: Colors.redAccent)), ), ], ), ), ), ), ), ); Future _submit() async { try { await Api.login(_login.text, _password.text); widget.onDone(); } on UnauthorizedException { setState(() => _error = 'Неверный логин или пароль'); } catch (e) { setState(() => _error = 'Ошибка: $e'); } } }