diff --git a/trainerbox/lib/screens/search_tab.dart b/trainerbox/lib/screens/search_tab.dart index 14dd3cd..abd477e 100644 --- a/trainerbox/lib/screens/search_tab.dart +++ b/trainerbox/lib/screens/search_tab.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; class SearchTab extends StatefulWidget { const SearchTab({super.key}); @@ -17,6 +19,38 @@ class _SearchTabState extends State { 'Mobility', 'Rehabilitation', ]; + String? _selectedCategory; + String _searchTerm = ''; + bool _isTrainer = false; + bool _trainerChecked = false; + + @override + void initState() { + super.initState(); + _searchController.addListener(() { + setState(() { + _searchTerm = _searchController.text.trim(); + }); + }); + _checkIfTrainer(); + } + + Future _checkIfTrainer() async { + final user = FirebaseAuth.instance.currentUser; + if (user == null) return; + final doc = await FirebaseFirestore.instance.collection('User').doc(user.uid).get(); + setState(() { + _isTrainer = doc.data()?['role'] == 'trainer'; + _trainerChecked = true; + }); + } + + void _showCreateTrainingDialog() { + showDialog( + context: context, + builder: (context) => _CreateTrainingDialog(categories: _categories), + ).then((_) => setState(() {})); // Refresh nach Hinzufügen + } @override Widget build(BuildContext context) { @@ -37,6 +71,14 @@ class _SearchTabState extends State { ), ), ), + actions: [ + if (_trainerChecked && _isTrainer) + IconButton( + icon: const Icon(Icons.add), + tooltip: 'Neues Training erstellen', + onPressed: _showCreateTrainingDialog, + ), + ], ), SliverPadding( padding: const EdgeInsets.all(16.0), @@ -52,72 +94,130 @@ class _SearchTabState extends State { Wrap( spacing: 8, runSpacing: 8, - children: - _categories.map((category) { - return FilterChip( - label: Text(category), - onSelected: (bool selected) { - // TODO: Implement category filtering - }, - ); - }).toList(), + children: _categories.map((category) { + return FilterChip( + label: Text(category), + selected: _selectedCategory == category, + onSelected: (bool selected) { + setState(() { + _selectedCategory = selected ? category : null; + }); + }, + ); + }).toList(), ), ], ), ), ), - SliverPadding( - padding: const EdgeInsets.all(16.0), - sliver: SliverGrid( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - mainAxisSpacing: 16, - crossAxisSpacing: 16, - childAspectRatio: 0.75, - ), - delegate: SliverChildBuilderDelegate((context, index) { - return Card( - clipBehavior: Clip.antiAlias, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Container( - color: Colors.grey[300], - child: const Center( - child: Icon(Icons.fitness_center, size: 40), - ), - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), + if (_searchTerm.isNotEmpty || _selectedCategory != null) + SliverPadding( + padding: const EdgeInsets.all(16.0), + sliver: FutureBuilder( + future: FirebaseFirestore.instance.collection('Training').get(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const SliverToBoxAdapter( + child: Center(child: CircularProgressIndicator())); + } + if (!snapshot.hasData || snapshot.data!.docs.isEmpty) { + return const SliverToBoxAdapter( + child: Center(child: Text('Keine Trainings gefunden.'))); + } + final docs = snapshot.data!.docs.where((doc) { + final data = doc.data() as Map; + final title = (data['title'] ?? '').toString().toLowerCase(); + final description = (data['description'] ?? '').toString().toLowerCase(); + final category = (data['category'] ?? '').toString(); + final matchesSearch = _searchTerm.isEmpty || + title.contains(_searchTerm.toLowerCase()) || + description.contains(_searchTerm.toLowerCase()); + final matchesCategory = _selectedCategory == null || category == _selectedCategory; + return matchesSearch && matchesCategory; + }).toList(); + if (docs.isEmpty) { + return const SliverToBoxAdapter( + child: Center(child: Text('Keine Trainings gefunden.'))); + } + return SliverGrid( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisSpacing: 16, + crossAxisSpacing: 16, + childAspectRatio: 0.75, + ), + delegate: SliverChildBuilderDelegate((context, index) { + final data = docs[index].data() as Map; + return Card( + clipBehavior: Clip.antiAlias, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - 'Training ${index + 1}', - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - ), + Expanded( + child: (data['picture'] is String && data['picture'] != '') + ? Image.network( + data['picture'], + width: double.infinity, + fit: BoxFit.cover, + ) + : Container( + color: Colors.grey[300], + child: const Center( + child: Icon(Icons.fitness_center, size: 40), + ), + ), ), - const SizedBox(height: 4), - Text( - '${30 + index * 5} Minuten', - style: TextStyle( - color: Colors.grey[600], - fontSize: 14, + Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + data['title'] ?? '-', + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + const SizedBox(height: 4), + Text( + data['description'] ?? '-', + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: Colors.grey[600], + fontSize: 13, + ), + ), + const SizedBox(height: 4), + Text( + '${data['duration'] ?? '-'} Minuten', + style: TextStyle( + color: Colors.grey[600], + fontSize: 13, + ), + ), + const SizedBox(height: 4), + Row( + children: [ + const Icon(Icons.star, size: 16, color: Colors.amber), + const SizedBox(width: 4), + Text('${data['rating overall'] ?? '-'}'), + ], + ), + const SizedBox(height: 4), + Text('Level: ${data['year'] ?? '-'}'), + ], ), ), ], ), - ), - ], - ), - ); - }, childCount: 6), + ); + }, childCount: docs.length), + ); + }, + ), ), - ), ], ), ); @@ -129,3 +229,111 @@ class _SearchTabState extends State { super.dispose(); } } + +class _CreateTrainingDialog extends StatefulWidget { + final List categories; + const _CreateTrainingDialog({required this.categories}); + + @override + State<_CreateTrainingDialog> createState() => _CreateTrainingDialogState(); +} + +class _CreateTrainingDialogState extends State<_CreateTrainingDialog> { + final _formKey = GlobalKey(); + String? _category; + String? _title; + String? _description; + int? _duration; + String? _picture; + double? _rating; + String? _year; + bool _loading = false; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Neues Training erstellen'), + content: SingleChildScrollView( + child: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + DropdownButtonFormField( + value: _category, + items: widget.categories + .map((cat) => DropdownMenuItem(value: cat, child: Text(cat))) + .toList(), + onChanged: (v) => setState(() => _category = v), + decoration: const InputDecoration(labelText: 'Kategorie'), + validator: (v) => v == null ? 'Kategorie wählen' : null, + ), + TextFormField( + decoration: const InputDecoration(labelText: 'Titel'), + onChanged: (v) => _title = v, + validator: (v) => v == null || v.isEmpty ? 'Titel angeben' : null, + ), + TextFormField( + decoration: const InputDecoration(labelText: 'Beschreibung'), + onChanged: (v) => _description = v, + validator: (v) => v == null || v.isEmpty ? 'Beschreibung angeben' : null, + maxLines: 2, + ), + TextFormField( + decoration: const InputDecoration(labelText: 'Dauer (Minuten)'), + keyboardType: TextInputType.number, + onChanged: (v) => _duration = int.tryParse(v), + validator: (v) => v == null || int.tryParse(v) == null ? 'Zahl angeben' : null, + ), + TextFormField( + decoration: const InputDecoration(labelText: 'Bild-URL (optional)'), + onChanged: (v) => _picture = v, + ), + TextFormField( + decoration: const InputDecoration(labelText: 'Bewertung (0-5)'), + keyboardType: TextInputType.number, + onChanged: (v) => _rating = double.tryParse(v), + validator: (v) { + final d = double.tryParse(v ?? ''); + if (d == null || d < 0 || d > 5) return '0-5 angeben'; + return null; + }, + ), + TextFormField( + decoration: const InputDecoration(labelText: 'Schwierigkeitslevel'), + onChanged: (v) => _year = v, + validator: (v) => v == null || v.isEmpty ? 'Level angeben' : null, + ), + ], + ), + ), + ), + actions: [ + TextButton( + onPressed: _loading ? null : () => Navigator.pop(context), + child: const Text('Abbrechen'), + ), + ElevatedButton( + onPressed: _loading + ? null + : () async { + if (_formKey.currentState!.validate()) { + setState(() => _loading = true); + await FirebaseFirestore.instance.collection('Training').add({ + 'category': _category, + 'title': _title, + 'description': _description, + 'duration': _duration, + 'picture': _picture, + 'rating overall': _rating, + 'year': _year, + }); + Navigator.pop(context); + } + }, + child: _loading ? const CircularProgressIndicator() : const Text('Erstellen'), + ), + ], + ); + } +}