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 {
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 {
final userDoc = await FirebaseFirestore.instance.collection('User').doc(_currentUserId).get();
if (!userDoc.exists) {
print('DEBUG: User document does not exist');
return;
}
if (!userDoc.exists) return;
final data = userDoc.data() as Map<String, dynamic>;
final trainings = Map<String, dynamic>.from(data['trainings'] ?? {});
final dateString = event['date'] as String;
final trainingId = event['id'] as String?;
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_')
if (trainingId != null && trainingId.startsWith('weekly_')) {
print('DEBUG: Processing weekly training deletion');
// Für regelmäßige Trainings: Lösche alle Trainings dieser Serie
final weekdays = {
'Montag': 1,
'Dienstag': 2,
'Mittwoch': 3,
'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');
// Füge das Datum zu cancelledTrainings hinzu, wenn es noch nicht existiert
if (!cancelledTrainings.any((cancelled) =>
//cancelled is Map<String, dynamic> &&
cancelled['date'] == dateString
)) {
cancelledTrainings.add({
'date': dateString,
});
}
} else {
print('DEBUG: Processing specific training deletion');
// Für spezifische Trainings: Entferne nur das Training an diesem Tag
// Für spezifische Trainings: Entferne das Training aus der Liste
if (trainings.containsKey(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);
final afterCount = trainingsList.length;
print('DEBUG: Removed ${beforeCount - afterCount} trainings with ID $trainingId');
if (trainingsList.isEmpty) {
trainings.remove(dateString);
print('DEBUG: Removed empty date $dateString from trainings');
} else {
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 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
if (!trainingTimes.containsKey(weekdayName)) {
final beforeCancelledCount = cancelledTrainings.length;
cancelledTrainings.removeWhere((cancelled) =>
//cancelled is Map<String, dynamic> &&
cancelled.containsKey('date') &&
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
final updates = <String, dynamic>{};
// Aktualisiere trainings nur, wenn es nicht leer ist
if (trainings.isNotEmpty) {
updates['trainings'] = trainings;
print('DEBUG: Will update trainings in DB');
} else {
updates['trainings'] = null;
print('DEBUG: Will set trainings to null in DB');
}
// Aktualisiere cancelledTrainings nur, wenn es nicht leer ist
if (cancelledTrainings.isNotEmpty) {
updates['cancelledTrainings'] = cancelledTrainings;
print('DEBUG: Will update cancelledTrainings in DB');
} else {
updates['cancelledTrainings'] = null;
print('DEBUG: Will set cancelledTrainings to null in DB');
}
print('DEBUG: Final updates to DB: $updates');
// Führe die Aktualisierung durch
await FirebaseFirestore.instance.collection('User').doc(_currentUserId).update(updates);
print('DEBUG: Database update completed successfully');
// Aktualisiere die UI sofort
setState(() {
print('DEBUG: Updating UI...');
// Für regelmäßige Trainings: Entferne alle Events an diesem Wochentag
if (trainingId != null && trainingId.startsWith('weekly_')) {
final date = DateTime.parse(dateString);
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');
}
final normalizedDate = DateTime.parse(dateString);
if (_events.containsKey(normalizedDate)) {
_events[normalizedDate]!.removeWhere((e) => e['id'] == trainingId);
if (_events[normalizedDate]!.isEmpty) {
_events.remove(normalizedDate);
}
}
print('DEBUG: UI update completed');
});
if (mounted) {
@ -724,11 +595,7 @@ class _CalendarTabState extends State<CalendarTab> {
const SnackBar(content: Text('Training wurde gelöscht')),
);
}
print('=== DEBUG: _deleteTraining completed successfully ===');
} catch (e) {
print('DEBUG: Error in _deleteTraining: $e');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Fehler beim Löschen des Trainings')),

View File

@ -48,6 +48,38 @@ class _FavoritesTabState extends State<FavoritesTab> {
_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).
void _openFilterModal() async {
_tempMinDuration = _minDuration;
@ -196,7 +228,7 @@ class _FavoritesTabState extends State<FavoritesTab> {
children: [
const Text(
'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(
@ -210,50 +242,64 @@ class _FavoritesTabState extends State<FavoritesTab> {
),
],
),
const SizedBox(height: 16),
// Category filter chips
Wrap(
alignment: WrapAlignment.center,
spacing: 8,
runSpacing: 8,
children: [
FilterChip(
label: const Text('Alle'),
selected: _selectedCategory == null,
onSelected: (selected) {
setState(() => _selectedCategory = null);
},
),
..._categories.map((cat) => FilterChip(
label: Text(cat),
selected: _selectedCategory == cat,
onSelected: (selected) {
setState(() => _selectedCategory = selected ? cat : null);
},
)),
],
const SizedBox(height: 12),
// Horizontal scrollable category chips
SizedBox(
height: 40,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
// "Alle" chip
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: FilterChip(
label: const Text('Alle'),
selected: _selectedCategory == null,
onSelected: (selected) {
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(),
],
),
),
],
),
),
// Favorites list
Expanded(
child: StreamBuilder<DocumentSnapshot>(
stream: FirebaseFirestore.instance.collection('User').doc(user.uid).snapshots(),
child: FutureBuilder<Set<String>>(
future: _loadFavorites(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
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) {
return const Center(child: Text('Keine Favoriten gefunden'));
}
// Load all favorite exercise documents at once
return FutureBuilder<List<DocumentSnapshot>>(
future: Future.wait(allFavorites.map((id) =>
FirebaseFirestore.instance.collection('Training').doc(id).get()
@ -296,7 +342,7 @@ class _FavoritesTabState extends State<FavoritesTab> {
itemBuilder: (context, index) {
final doc = filteredFavorites[index];
final trainingData = doc.data() as Map<String, dynamic>;
return Card(
child: Stack(
children: [

View File

@ -249,14 +249,9 @@ class _SearchTabState extends State<SearchTab> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Übungen'),
title: const Text('Suche'),
actions: [
if (widget.selectMode)
IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.pop(context),
),
if (_isTrainer && !widget.selectMode)
if (_isTrainer && _trainerChecked)
IconButton(
icon: const Icon(Icons.add),
onPressed: () => _showCreateTrainingDialog(context),
@ -265,34 +260,21 @@ class _SearchTabState extends State<SearchTab> {
),
body: Column(
children: [
// Search input field.
// Search bar
Padding(
padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: 'Suche nach Übungen...',
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
decoration: const InputDecoration(
labelText: 'Suche nach Übungen...',
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(),
),
),
),
if (widget.selectMode && widget.remainingTime != null)
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)
// Category filter chips
Padding(
padding: const EdgeInsets.all(16.0),
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -301,8 +283,9 @@ class _SearchTabState extends State<SearchTab> {
children: [
const Text(
'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(
onPressed: _openFilterModal,
icon: const Icon(Icons.filter_list),
@ -314,26 +297,47 @@ class _SearchTabState extends State<SearchTab> {
),
],
),
const SizedBox(height: 16),
Wrap(
spacing: 8,
runSpacing: 8,
children: _categories.map((category) {
return FilterChip(
label: Text(category),
selected: _selectedCategory == category,
onSelected: (bool selected) {
setState(() {
_selectedCategory = selected ? category : null;
});
},
);
}).toList(),
const SizedBox(height: 12),
// Horizontal scrollable category chips
SizedBox(
height: 40,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
// "Alle" chip
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: FilterChip(
label: const Text('Alle'),
selected: _selectedCategory == null,
onSelected: (selected) {
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(
child: FutureBuilder<QuerySnapshot>(
future: FirebaseFirestore.instance.collection('Training').get(),