Kategorien horizontal

main
joschy2002 2025-07-02 20:58:58 +02:00
parent 4e966656d3
commit e01a0203d1
3 changed files with 143 additions and 226 deletions

View File

@ -499,103 +499,36 @@ class _CalendarTabState extends State<CalendarTab> {
Future<void> _deleteTraining(Map<String, dynamic> event) async { Future<void> _deleteTraining(Map<String, dynamic> event) async {
if (_userRole != 'trainer' || !event['isCurrentUser']) return; if (_userRole != 'trainer' || !event['isCurrentUser']) return;
print('=== DEBUG: Starting _deleteTraining ===');
print('DEBUG: Event to delete: $event');
print('DEBUG: Training ID: ${event['id']}');
print('DEBUG: Date: ${event['date']}');
print('DEBUG: Is weekly training: ${event['id']?.toString().startsWith('weekly_')}');
try { try {
final userDoc = await FirebaseFirestore.instance.collection('User').doc(_currentUserId).get(); final userDoc = await FirebaseFirestore.instance.collection('User').doc(_currentUserId).get();
if (!userDoc.exists) { if (!userDoc.exists) return;
print('DEBUG: User document does not exist');
return;
}
final data = userDoc.data() as Map<String, dynamic>; final data = userDoc.data() as Map<String, dynamic>;
final trainings = Map<String, dynamic>.from(data['trainings'] ?? {}); final trainings = Map<String, dynamic>.from(data['trainings'] ?? {});
final dateString = event['date'] as String; final dateString = event['date'] as String;
final trainingId = event['id'] as String?; final trainingId = event['id'] as String?;
final cancelledTrainings = List<Map<String, dynamic>>.from(data['cancelledTrainings'] ?? []); final cancelledTrainings = List<Map<String, dynamic>>.from(data['cancelledTrainings'] ?? []);
print('DEBUG: Current trainings in DB: $trainings');
print('DEBUG: Current cancelledTrainings in DB: $cancelledTrainings');
// Wenn es sich um ein regelmäßiges Training handelt (ID beginnt mit 'weekly_') // Wenn es sich um ein regelmäßiges Training handelt (ID beginnt mit 'weekly_')
if (trainingId != null && trainingId.startsWith('weekly_')) { if (trainingId != null && trainingId.startsWith('weekly_')) {
print('DEBUG: Processing weekly training deletion'); // Füge das Datum zu cancelledTrainings hinzu, wenn es noch nicht existiert
if (!cancelledTrainings.any((cancelled) =>
// Für regelmäßige Trainings: Lösche alle Trainings dieser Serie //cancelled is Map<String, dynamic> &&
final weekdays = { cancelled['date'] == dateString
'Montag': 1, )) {
'Dienstag': 2, cancelledTrainings.add({
'Mittwoch': 3, 'date': dateString,
'Donnerstag': 4, });
'Freitag': 5, }
'Samstag': 6,
'Sonntag': 7,
};
final date = DateTime.parse(dateString);
final weekday = date.weekday;
print('DEBUG: Weekday to delete: $weekday');
// Lösche alle Trainings an diesem Wochentag
trainings.forEach((dateStr, trainingsList) {
final trainingDate = DateTime.tryParse(dateStr);
if (trainingDate != null && trainingDate.weekday == weekday) {
print('DEBUG: Checking date $dateStr (weekday: ${trainingDate.weekday})');
final list = List<Map<String, dynamic>>.from(trainingsList);
print('DEBUG: Trainings on this date before deletion: $list');
final beforeCount = list.length;
list.removeWhere((t) => t['id'] == trainingId);
final afterCount = list.length;
print('DEBUG: Removed ${beforeCount - afterCount} trainings with ID $trainingId');
if (list.isEmpty) {
trainings.remove(dateStr);
print('DEBUG: Removed empty date $dateStr from trainings');
} else {
trainings[dateStr] = list;
print('DEBUG: Updated trainings for date $dateStr: $list');
}
}
});
// Entferne alle cancelledTrainings für diesen Wochentag
final beforeCancelledCount = cancelledTrainings.length;
cancelledTrainings.removeWhere((cancelled) =>
cancelled.containsKey('date') &&
DateTime.parse(cancelled['date'] as String).weekday == weekday
);
final afterCancelledCount = cancelledTrainings.length;
print('DEBUG: Removed ${beforeCancelledCount - afterCancelledCount} cancelled trainings for weekday $weekday');
} else { } else {
print('DEBUG: Processing specific training deletion'); // Für spezifische Trainings: Entferne das Training aus der Liste
// Für spezifische Trainings: Entferne nur das Training an diesem Tag
if (trainings.containsKey(dateString)) { if (trainings.containsKey(dateString)) {
final trainingsList = List<Map<String, dynamic>>.from(trainings[dateString]); final trainingsList = List<Map<String, dynamic>>.from(trainings[dateString]);
print('DEBUG: Trainings on date $dateString before deletion: $trainingsList');
final beforeCount = trainingsList.length;
trainingsList.removeWhere((t) => t['id'] == trainingId); trainingsList.removeWhere((t) => t['id'] == trainingId);
final afterCount = trainingsList.length;
print('DEBUG: Removed ${beforeCount - afterCount} trainings with ID $trainingId');
if (trainingsList.isEmpty) { if (trainingsList.isEmpty) {
trainings.remove(dateString); trainings.remove(dateString);
print('DEBUG: Removed empty date $dateString from trainings');
} else { } else {
trainings[dateString] = trainingsList; trainings[dateString] = trainingsList;
print('DEBUG: Updated trainings for date $dateString: $trainingsList');
} }
} }
@ -616,107 +549,45 @@ class _CalendarTabState extends State<CalendarTab> {
final weekdayName = weekdays.entries.firstWhere((entry) => entry.value == weekday).key; final weekdayName = weekdays.entries.firstWhere((entry) => entry.value == weekday).key;
final trainingTimes = data['trainingTimes'] as Map<String, dynamic>? ?? {}; final trainingTimes = data['trainingTimes'] as Map<String, dynamic>? ?? {};
print('DEBUG: Weekday name: $weekdayName');
print('DEBUG: Training times: $trainingTimes');
print('DEBUG: Has regular training on this weekday: ${trainingTimes.containsKey(weekdayName)}');
// Wenn an diesem Tag kein regelmäßiges Training stattfindet, entferne den Eintrag aus cancelledTrainings // Wenn an diesem Tag kein regelmäßiges Training stattfindet, entferne den Eintrag aus cancelledTrainings
if (!trainingTimes.containsKey(weekdayName)) { if (!trainingTimes.containsKey(weekdayName)) {
final beforeCancelledCount = cancelledTrainings.length;
cancelledTrainings.removeWhere((cancelled) => cancelledTrainings.removeWhere((cancelled) =>
//cancelled is Map<String, dynamic> &&
cancelled.containsKey('date') && cancelled.containsKey('date') &&
cancelled['date'] == dateString cancelled['date'] == dateString
); );
final afterCancelledCount = cancelledTrainings.length;
print('DEBUG: Removed ${beforeCancelledCount - afterCancelledCount} cancelled trainings for date $dateString');
} }
} }
print('DEBUG: Final trainings after deletion: $trainings');
print('DEBUG: Final cancelledTrainings after deletion: $cancelledTrainings');
// Aktualisiere die Datenbank // Aktualisiere die Datenbank
final updates = <String, dynamic>{}; final updates = <String, dynamic>{};
// Aktualisiere trainings nur, wenn es nicht leer ist // Aktualisiere trainings nur, wenn es nicht leer ist
if (trainings.isNotEmpty) { if (trainings.isNotEmpty) {
updates['trainings'] = trainings; updates['trainings'] = trainings;
print('DEBUG: Will update trainings in DB');
} else { } else {
updates['trainings'] = null; updates['trainings'] = null;
print('DEBUG: Will set trainings to null in DB');
} }
// Aktualisiere cancelledTrainings nur, wenn es nicht leer ist // Aktualisiere cancelledTrainings nur, wenn es nicht leer ist
if (cancelledTrainings.isNotEmpty) { if (cancelledTrainings.isNotEmpty) {
updates['cancelledTrainings'] = cancelledTrainings; updates['cancelledTrainings'] = cancelledTrainings;
print('DEBUG: Will update cancelledTrainings in DB');
} else { } else {
updates['cancelledTrainings'] = null; updates['cancelledTrainings'] = null;
print('DEBUG: Will set cancelledTrainings to null in DB');
} }
print('DEBUG: Final updates to DB: $updates');
// Führe die Aktualisierung durch // Führe die Aktualisierung durch
await FirebaseFirestore.instance.collection('User').doc(_currentUserId).update(updates); await FirebaseFirestore.instance.collection('User').doc(_currentUserId).update(updates);
print('DEBUG: Database update completed successfully');
// Aktualisiere die UI sofort // Aktualisiere die UI sofort
setState(() { setState(() {
print('DEBUG: Updating UI...'); final normalizedDate = DateTime.parse(dateString);
if (_events.containsKey(normalizedDate)) {
// Für regelmäßige Trainings: Entferne alle Events an diesem Wochentag _events[normalizedDate]!.removeWhere((e) => e['id'] == trainingId);
if (trainingId != null && trainingId.startsWith('weekly_')) { if (_events[normalizedDate]!.isEmpty) {
final date = DateTime.parse(dateString); _events.remove(normalizedDate);
final weekday = date.weekday;
print('DEBUG: Removing events from UI for weekday $weekday');
_events.forEach((eventDate, eventList) {
if (eventDate.weekday == weekday) {
print('DEBUG: Checking event date $eventDate (weekday: ${eventDate.weekday})');
print('DEBUG: Events before removal: $eventList');
final beforeCount = eventList.length;
eventList.removeWhere((e) => e['id'] == trainingId);
final afterCount = eventList.length;
print('DEBUG: Removed ${beforeCount - afterCount} events with ID $trainingId');
print('DEBUG: Events after removal: $eventList');
}
});
// Entferne leere Event-Listen
final beforeEmptyCount = _events.length;
_events.removeWhere((date, events) => events.isEmpty);
final afterEmptyCount = _events.length;
print('DEBUG: Removed ${beforeEmptyCount - afterEmptyCount} empty event lists');
} else {
// Für spezifische Trainings: Entferne nur das Event an diesem Tag
final normalizedDate = DateTime.parse(dateString);
if (_events.containsKey(normalizedDate)) {
print('DEBUG: Removing specific event from UI for date $normalizedDate');
print('DEBUG: Events before removal: ${_events[normalizedDate]}');
final beforeCount = _events[normalizedDate]!.length;
_events[normalizedDate]!.removeWhere((e) => e['id'] == trainingId);
final afterCount = _events[normalizedDate]!.length;
print('DEBUG: Removed ${beforeCount - afterCount} events with ID $trainingId');
print('DEBUG: Events after removal: ${_events[normalizedDate]}');
if (_events[normalizedDate]!.isEmpty) {
_events.remove(normalizedDate);
print('DEBUG: Removed empty date $normalizedDate from events');
}
} }
} }
print('DEBUG: UI update completed');
}); });
if (mounted) { if (mounted) {
@ -724,11 +595,7 @@ class _CalendarTabState extends State<CalendarTab> {
const SnackBar(content: Text('Training wurde gelöscht')), const SnackBar(content: Text('Training wurde gelöscht')),
); );
} }
print('=== DEBUG: _deleteTraining completed successfully ===');
} catch (e) { } catch (e) {
print('DEBUG: Error in _deleteTraining: $e');
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Fehler beim Löschen des Trainings')), const SnackBar(content: Text('Fehler beim Löschen des Trainings')),

View File

@ -48,6 +48,38 @@ class _FavoritesTabState extends State<FavoritesTab> {
_selectedCategory = widget.categoryFilter; _selectedCategory = widget.categoryFilter;
} }
/// Loads the user's favorite exercises from Firestore.
Future<Set<String>> _loadFavorites() async {
final user = FirebaseAuth.instance.currentUser;
if (user == null) return {};
final doc = await FirebaseFirestore.instance.collection('User').doc(user.uid).get();
final data = doc.data();
if (data != null && data['favorites'] != null) {
return Set<String>.from(data['favorites']);
}
return {};
}
/// Toggles the favorite status of an exercise for the current user.
Future<void> _toggleFavorite(String trainingId, bool isFavorite) async {
final user = FirebaseAuth.instance.currentUser;
if (user == null) return;
if (isFavorite) {
await FirebaseFirestore.instance.collection('User').doc(user.uid).update({
'favorites': FieldValue.arrayRemove([trainingId]),
});
} else {
await FirebaseFirestore.instance.collection('User').doc(user.uid).update({
'favorites': FieldValue.arrayUnion([trainingId]),
});
}
// Refresh the favorites list
setState(() {});
}
/// Opens the filter modal for advanced filtering (duration, level, rating). /// Opens the filter modal for advanced filtering (duration, level, rating).
void _openFilterModal() async { void _openFilterModal() async {
_tempMinDuration = _minDuration; _tempMinDuration = _minDuration;
@ -196,7 +228,7 @@ class _FavoritesTabState extends State<FavoritesTab> {
children: [ children: [
const Text( const Text(
'Kategorien', 'Kategorien',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
), ),
// Filter button opens the modal with advanced filters // Filter button opens the modal with advanced filters
ElevatedButton.icon( ElevatedButton.icon(
@ -210,50 +242,64 @@ class _FavoritesTabState extends State<FavoritesTab> {
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 12),
// Category filter chips // Horizontal scrollable category chips
Wrap( SizedBox(
alignment: WrapAlignment.center, height: 40,
spacing: 8, child: ListView(
runSpacing: 8, scrollDirection: Axis.horizontal,
children: [ children: [
FilterChip( // "Alle" chip
label: const Text('Alle'), Padding(
selected: _selectedCategory == null, padding: const EdgeInsets.only(right: 8.0),
onSelected: (selected) { child: FilterChip(
setState(() => _selectedCategory = null); label: const Text('Alle'),
}, selected: _selectedCategory == null,
), onSelected: (selected) {
..._categories.map((cat) => FilterChip( setState(() {
label: Text(cat), _selectedCategory = null;
selected: _selectedCategory == cat, });
onSelected: (selected) { },
setState(() => _selectedCategory = selected ? cat : null); ),
}, ),
)), // Category chips
], ..._categories.map((category) => Padding(
padding: const EdgeInsets.only(right: 8.0),
child: FilterChip(
label: Text(category),
selected: _selectedCategory == category,
onSelected: (selected) {
setState(() {
_selectedCategory = selected ? category : null;
});
},
),
)).toList(),
],
),
), ),
], ],
), ),
), ),
// Favorites list
Expanded( Expanded(
child: StreamBuilder<DocumentSnapshot>( child: FutureBuilder<Set<String>>(
stream: FirebaseFirestore.instance.collection('User').doc(user.uid).snapshots(), future: _loadFavorites(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
if (!snapshot.hasData || !snapshot.data!.exists) {
return const Center(child: Text('Keine Favoriten gefunden')); if (snapshot.hasError) {
return Center(child: Text('Fehler: ${snapshot.error}'));
} }
final data = snapshot.data!.data() as Map<String, dynamic>;
final allFavorites = List<String>.from(data['favorites'] ?? []); final allFavorites = snapshot.data ?? {};
if (allFavorites.isEmpty) { if (allFavorites.isEmpty) {
return const Center(child: Text('Keine Favoriten gefunden')); return const Center(child: Text('Keine Favoriten gefunden'));
} }
// Load all favorite exercise documents at once
return FutureBuilder<List<DocumentSnapshot>>( return FutureBuilder<List<DocumentSnapshot>>(
future: Future.wait(allFavorites.map((id) => future: Future.wait(allFavorites.map((id) =>
FirebaseFirestore.instance.collection('Training').doc(id).get() FirebaseFirestore.instance.collection('Training').doc(id).get()
@ -296,7 +342,7 @@ class _FavoritesTabState extends State<FavoritesTab> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final doc = filteredFavorites[index]; final doc = filteredFavorites[index];
final trainingData = doc.data() as Map<String, dynamic>; final trainingData = doc.data() as Map<String, dynamic>;
return Card( return Card(
child: Stack( child: Stack(
children: [ children: [

View File

@ -249,14 +249,9 @@ class _SearchTabState extends State<SearchTab> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Übungen'), title: const Text('Suche'),
actions: [ actions: [
if (widget.selectMode) if (_isTrainer && _trainerChecked)
IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.pop(context),
),
if (_isTrainer && !widget.selectMode)
IconButton( IconButton(
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
onPressed: () => _showCreateTrainingDialog(context), onPressed: () => _showCreateTrainingDialog(context),
@ -265,34 +260,21 @@ class _SearchTabState extends State<SearchTab> {
), ),
body: Column( body: Column(
children: [ children: [
// Search input field. // Search bar
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(16.0),
child: TextField( child: TextField(
controller: _searchController, controller: _searchController,
decoration: InputDecoration( decoration: const InputDecoration(
hintText: 'Suche nach Übungen...', labelText: 'Suche nach Übungen...',
prefixIcon: const Icon(Icons.search), prefixIcon: Icon(Icons.search),
border: OutlineInputBorder( border: OutlineInputBorder(),
borderRadius: BorderRadius.circular(10),
),
), ),
), ),
), ),
if (widget.selectMode && widget.remainingTime != null) // Category filter chips
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Text(
'Verbleibende Zeit: ${widget.remainingTime} Minuten',
style: const TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
),
),
),
// Category filter chips and filter button only (no direct filter UI here)
Padding( Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -301,8 +283,9 @@ class _SearchTabState extends State<SearchTab> {
children: [ children: [
const Text( const Text(
'Kategorien', 'Kategorien',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
), ),
// Filter button opens the modal with advanced filters
ElevatedButton.icon( ElevatedButton.icon(
onPressed: _openFilterModal, onPressed: _openFilterModal,
icon: const Icon(Icons.filter_list), icon: const Icon(Icons.filter_list),
@ -314,26 +297,47 @@ class _SearchTabState extends State<SearchTab> {
), ),
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 12),
Wrap( // Horizontal scrollable category chips
spacing: 8, SizedBox(
runSpacing: 8, height: 40,
children: _categories.map((category) { child: ListView(
return FilterChip( scrollDirection: Axis.horizontal,
label: Text(category), children: [
selected: _selectedCategory == category, // "Alle" chip
onSelected: (bool selected) { Padding(
setState(() { padding: const EdgeInsets.only(right: 8.0),
_selectedCategory = selected ? category : null; child: FilterChip(
}); label: const Text('Alle'),
}, selected: _selectedCategory == null,
); onSelected: (selected) {
}).toList(), setState(() {
_selectedCategory = null;
});
},
),
),
// Category chips
..._categories.map((category) => Padding(
padding: const EdgeInsets.only(right: 8.0),
child: FilterChip(
label: Text(category),
selected: _selectedCategory == category,
onSelected: (selected) {
setState(() {
_selectedCategory = selected ? category : null;
});
},
),
)).toList(),
],
),
), ),
], ],
), ),
), ),
// Exercise grid view. const SizedBox(height: 16),
// Exercise list
Expanded( Expanded(
child: FutureBuilder<QuerySnapshot>( child: FutureBuilder<QuerySnapshot>(
future: FirebaseFirestore.instance.collection('Training').get(), future: FirebaseFirestore.instance.collection('Training').get(),