Kategorien horizontal
parent
4e966656d3
commit
e01a0203d1
|
@ -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')),
|
||||
|
|
|
@ -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: [
|
||||
|
|
|
@ -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(),
|
||||
|
|
Loading…
Reference in New Issue