CPD-Gitty/trainerbox/lib/screens/calendar_tab.dart

1280 lines
55 KiB
Dart

import 'package:flutter/material.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'training_detail_screen.dart';
import 'package:uuid/uuid.dart';
const Map<String, Color> categoryColors = {
'Aufwärmen & Mobilisation': Colors.deepOrange,
'Wurf- & Torabschluss': Colors.orange,
'Torwarttraining': Colors.green,
'Athletik': Colors.blue,
'Pass': Colors.purple,
'Koordination': Colors.teal,
};
class CalendarTab extends StatefulWidget {
final DateTime? initialDate;
const CalendarTab({super.key, this.initialDate});
@override
State<CalendarTab> createState() => _CalendarTabState();
}
class _CalendarTabState extends State<CalendarTab> {
CalendarFormat _calendarFormat = CalendarFormat.week;
late DateTime _focusedDay;
DateTime? _selectedDay;
Map<DateTime, List<Map<String, dynamic>>> _events = {};
bool _isLoading = false;
String? _currentUserId;
String? _userRole;
final _exerciseController = TextEditingController();
final _durationController = TextEditingController();
@override
void dispose() {
_exerciseController.dispose();
_durationController.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
_focusedDay = widget.initialDate ?? DateTime.now();
_selectedDay = _focusedDay;
_currentUserId = FirebaseAuth.instance.currentUser?.uid;
_initializeData();
}
@override
void didUpdateWidget(covariant CalendarTab oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.initialDate != null && widget.initialDate != oldWidget.initialDate) {
setState(() {
_focusedDay = widget.initialDate!;
_selectedDay = widget.initialDate!;
});
}
}
Future<void> _initializeData() async {
if (_currentUserId != null) {
final userDoc = await FirebaseFirestore.instance
.collection('User')
.doc(_currentUserId)
.get();
if (userDoc.exists) {
setState(() {
_userRole = userDoc.data()?['role'] as String?;
});
await _loadEvents();
}
}
}
Future<void> _loadEvents() async {
if (_userRole == null) return;
setState(() => _isLoading = true);
try {
QuerySnapshot trainersSnapshot;
if (_userRole == 'trainer') {
trainersSnapshot = await FirebaseFirestore.instance
.collection('User')
.where('role', isEqualTo: 'trainer')
.where(FieldPath.documentId, isEqualTo: _currentUserId)
.get();
} else {
final userDoc = await FirebaseFirestore.instance
.collection('User')
.doc(_currentUserId)
.get();
if (!userDoc.exists) {
setState(() => _isLoading = false);
return;
}
final userData = userDoc.data() as Map<String, dynamic>;
final userClub = userData['club'] as String?;
if (userClub == null || userClub.isEmpty) {
setState(() {
_events = {};
_isLoading = false;
});
return;
}
trainersSnapshot = await FirebaseFirestore.instance
.collection('User')
.where('role', isEqualTo: 'trainer')
.where('club', isEqualTo: userClub)
.get();
}
final events = <DateTime, List<Map<String, dynamic>>>{};
for (var trainerDoc in trainersSnapshot.docs) {
final trainerData = trainerDoc.data() as Map<String, dynamic>;
final trainings = trainerData['trainings'] as Map<String, dynamic>? ?? {};
final cancelledTrainings = trainerData['cancelledTrainings'] as List<dynamic>? ?? [];
final trainingTimes = trainerData['trainingTimes'] as Map<String, dynamic>? ?? {};
final trainingDurations = trainerData['trainingDurations'] as Map<String, dynamic>? ?? {};
print('Lade Events:');
print('trainings: $trainings');
print('cancelledTrainings: $cancelledTrainings');
print('trainingTimes: $trainingTimes');
print('trainingDurations: $trainingDurations');
// Lade alle Trainings für das Datum
trainings.forEach((dateString, trainingsList) {
final date = DateTime.tryParse(dateString);
if (date == null) return;
final normalizedDate = DateTime(date.year, date.month, date.day);
final list = List<Map<String, dynamic>>.from(trainingsList);
for (final training in list) {
final timeStr = training['time'] as String? ?? '00:00';
final duration = training['duration'] as int? ?? 60;
final exercises = training['exercises'] as List<dynamic>? ?? [];
final totalExerciseDuration = exercises.fold<int>(0, (sum, exercise) {
if (exercise is Map<String, dynamic>) {
return sum + (exercise['duration'] as int? ?? 0);
}
return sum;
});
final event = {
'trainerName': trainerData['name'] ?? 'Unbekannter Trainer',
'time': timeStr,
'duration': duration,
'trainerId': trainerDoc.id,
'isCurrentUser': trainerDoc.id == _currentUserId,
'day': dateString,
'date': dateString,
'exercises': exercises,
'remainingTime': duration - totalExerciseDuration,
'club': trainerData['club'] ?? 'Kein Verein',
'id': training['id'],
};
if (events.containsKey(normalizedDate)) {
events[normalizedDate]!.add(event);
} else {
events[normalizedDate] = [event];
}
}
});
// Füge regelmäßige Trainings hinzu
final now = DateTime.now();
final yearStart = DateTime(now.year, 1, 1);
final yearEnd = DateTime(now.year + 1, 1, 1);
final weekdays = {
'Montag': 1,
'Dienstag': 2,
'Mittwoch': 3,
'Donnerstag': 4,
'Freitag': 5,
'Samstag': 6,
'Sonntag': 7,
};
// Erstelle eine Set von gecancelten Daten für schnelleren Zugriff
final cancelledDates = cancelledTrainings
.where((cancelled) => cancelled is Map<String, dynamic>)
.map((cancelled) => DateTime.parse((cancelled as Map<String, dynamic>)['date'] as String))
.toSet();
print('Gecancelte Daten: $cancelledDates');
trainingTimes.forEach((day, timeStr) {
if (timeStr == null) return;
final timeParts = (timeStr as String).split(':');
if (timeParts.length != 2) return;
final hour = int.tryParse(timeParts[0]) ?? 0;
final minute = int.tryParse(timeParts[1]) ?? 0;
final duration = trainingDurations[day] ?? 60;
final targetWeekday = weekdays[day] ?? 1;
// Finde das erste gewünschte Wochentags-Datum ab Jahresanfang
DateTime firstDate = yearStart;
while (firstDate.weekday != targetWeekday) {
firstDate = firstDate.add(const Duration(days: 1));
}
// Generiere Trainings für das gesamte Jahr
while (firstDate.isBefore(yearEnd)) {
final normalizedDate = DateTime(firstDate.year, firstDate.month, firstDate.day);
final dateString = normalizedDate.toIso8601String();
// Prüfe, ob das Training für dieses Datum gecancelt ist
final isCancelled = cancelledDates.any((cancelledDate) =>
isSameDay(cancelledDate, normalizedDate)
);
// Prüfe, ob bereits ein spezifisches Training für dieses Datum existiert
final hasSpecificTraining = trainings.containsKey(dateString);
if (!isCancelled && !hasSpecificTraining) {
final event = {
'trainerName': trainerData['name'] ?? 'Unbekannter Trainer',
'time': timeStr,
'duration': duration,
'trainerId': trainerDoc.id,
'isCurrentUser': trainerDoc.id == _currentUserId,
'day': day,
'date': dateString,
'exercises': [],
'remainingTime': duration,
'club': trainerData['club'] ?? 'Kein Verein',
'id': 'weekly_${day}_${dateString}',
};
if (events.containsKey(normalizedDate)) {
events[normalizedDate]!.add(event);
} else {
events[normalizedDate] = [event];
}
}
firstDate = firstDate.add(const Duration(days: 7));
}
});
}
setState(() {
_events = events;
_isLoading = false;
});
} catch (e) {
print('Error loading events: $e');
setState(() => _isLoading = false);
}
}
Future<void> _addExercise(Map<String, dynamic> event) async {
if (_userRole != 'trainer' || !event['isCurrentUser']) return;
// Navigiere zum Suchbildschirm und warte auf das Ergebnis
final result = await Navigator.pushNamed(
context,
'/search',
arguments: {
'selectMode': true,
'remainingTime': event['remainingTime'],
},
);
// Wenn eine Übung ausgewählt wurde
if (result != null && result is Map<String, dynamic>) {
try {
print('Übung ausgewählt:');
print('ID: ${result['id']}');
print('Titel: ${result['title']}');
print('Vollständige Übungsdaten: $result');
bool shouldAddExercise = true;
// Prüfe die Bewertungen der Übung in der Training-Collection
print('Suche Übung in der Datenbank...');
final exerciseDoc = await FirebaseFirestore.instance
.collection('Training')
.doc(result['id'])
.get();
print('Übungsdokument gefunden: ${exerciseDoc.exists}');
print('Collection-Pfad: Training/${result['id']}');
if (exerciseDoc.exists) {
final exerciseData = exerciseDoc.data() as Map<String, dynamic>;
print('Übungsdaten: $exerciseData');
// Versuche die Bewertungen aus verschiedenen möglichen Feldern zu lesen
List<Map<String, dynamic>> ratings = [];
if (exerciseData.containsKey('ratings')) {
ratings = List<Map<String, dynamic>>.from(exerciseData['ratings'] ?? []);
print('Bewertungen aus Feld "ratings" gefunden: $ratings');
} else if (exerciseData.containsKey('rating')) {
ratings = List<Map<String, dynamic>>.from(exerciseData['rating'] ?? []);
print('Bewertungen aus Feld "rating" gefunden: $ratings');
}
print('Gefundene Bewertungen: $ratings');
if (ratings.isNotEmpty) {
// Berechne den Durchschnitt der Bewertungen
double totalRating = 0;
for (var rating in ratings) {
if (rating.containsKey('rating')) {
totalRating += (rating['rating'] as num).toDouble();
}
}
final averageRating = totalRating / ratings.length;
print('Durchschnittliche Bewertung: $averageRating');
// Wenn die durchschnittliche Bewertung unter 3 liegt, zeige eine Warnung
if (averageRating < 3) {
print('Zeige Warnung für schlechte Bewertung');
if (mounted) {
shouldAddExercise = await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (context) => AlertDialog(
title: const Text('Hinweis zur Übungsbewertung'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Die Übung "${result['title']}" wurde von anderen Trainern mit ${averageRating.toStringAsFixed(1)} von 5 Sternen bewertet.'),
const SizedBox(height: 16),
const Text('Möchten Sie diese Übung trotzdem zu Ihrem Training hinzufügen?'),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('Abbrechen'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('Trotzdem hinzufügen'),
),
],
),
) ?? false;
print('Benutzer hat entschieden: ${shouldAddExercise ? "Hinzufügen" : "Abbrechen"}');
}
} else {
print('Bewertung ist gut genug, keine Warnung nötig');
}
} else {
print('Keine Bewertungen vorhanden');
}
} else {
print('Warnung: Übungsdokument nicht gefunden');
}
if (!shouldAddExercise) {
print('Übung wird nicht hinzugefügt (Benutzer hat abgebrochen)');
return;
}
// Füge die Übung zum Training hinzu
final userDoc = await FirebaseFirestore.instance
.collection('User')
.doc(_currentUserId)
.get();
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;
print('Füge Übung zum Training hinzu:');
print('dateString: $dateString');
print('trainingId: $trainingId');
print('Vorher - trainings: $trainings');
// Finde das Training und füge die Übung hinzu
if (trainings.containsKey(dateString)) {
final trainingsList = List<Map<String, dynamic>>.from(trainings[dateString]);
final trainingIndex = trainingsList.indexWhere((t) => t['id'] == trainingId);
if (trainingIndex != -1) {
final exercises = List<Map<String, dynamic>>.from(trainingsList[trainingIndex]['exercises'] ?? []);
exercises.add({
'id': result['id'],
'name': result['title'],
'description': result['description'],
'duration': result['duration'],
});
trainingsList[trainingIndex]['exercises'] = exercises;
trainings[dateString] = trainingsList;
}
}
print('Nachher - trainings: $trainings');
// Aktualisiere die Datenbank
await FirebaseFirestore.instance
.collection('User')
.doc(_currentUserId)
.update({
'trainings': trainings,
});
// Aktualisiere die UI sofort
setState(() {
final normalizedDate = DateTime.parse(dateString);
if (_events.containsKey(normalizedDate)) {
final eventIndex = _events[normalizedDate]!.indexWhere((e) => e['id'] == trainingId);
if (eventIndex != -1) {
final exercises = List<Map<String, dynamic>>.from(_events[normalizedDate]![eventIndex]['exercises'] ?? []);
exercises.add({
'id': result['id'],
'name': result['title'],
'description': result['description'],
'duration': result['duration'],
});
_events[normalizedDate]![eventIndex]['exercises'] = exercises;
_events[normalizedDate]![eventIndex]['remainingTime'] =
(_events[normalizedDate]![eventIndex]['duration'] as int) -
exercises.fold<int>(0, (sum, exercise) => sum + (exercise['duration'] as int));
}
}
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Übung wurde hinzugefügt')),
);
}
} catch (e) {
print('Error adding exercise: $e');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Fehler beim Hinzufügen der Übung')),
);
}
}
}
}
Future<void> _deleteTraining(Map<String, dynamic> event) async {
if (_userRole != 'trainer' || !event['isCurrentUser']) return;
try {
final userDoc = await FirebaseFirestore.instance.collection('User').doc(_currentUserId).get();
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('Lösche Training:');
print('dateString: $dateString');
print('trainingId: $trainingId');
print('Vorher - trainings: $trainings');
print('Vorher - cancelledTrainings: $cancelledTrainings');
// Wenn es sich um ein regelmäßiges Training handelt (ID beginnt mit 'weekly_')
if (trainingId != null && trainingId.startsWith('weekly_')) {
// 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 {
// Für spezifische Trainings: Entferne das Training aus der Liste
if (trainings.containsKey(dateString)) {
final trainingsList = List<Map<String, dynamic>>.from(trainings[dateString]);
trainingsList.removeWhere((t) => t['id'] == trainingId);
if (trainingsList.isEmpty) {
trainings.remove(dateString);
} else {
trainings[dateString] = trainingsList;
}
}
// Entferne den Eintrag aus cancelledTrainings nur, wenn es sich um ein spezifisches Training handelt
// und kein regelmäßiges Training an diesem Tag stattfindet
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;
final weekdayName = weekdays.entries.firstWhere((entry) => entry.value == weekday).key;
final trainingTimes = data['trainingTimes'] as Map<String, dynamic>? ?? {};
// Wenn an diesem Tag kein regelmäßiges Training stattfindet, entferne den Eintrag aus cancelledTrainings
if (!trainingTimes.containsKey(weekdayName)) {
cancelledTrainings.removeWhere((cancelled) =>
cancelled is Map<String, dynamic> &&
cancelled.containsKey('date') &&
cancelled['date'] == dateString
);
}
}
print('Nachher - trainings: $trainings');
print('Nachher - cancelledTrainings: $cancelledTrainings');
// Aktualisiere die Datenbank
final updates = <String, dynamic>{};
// Aktualisiere trainings nur, wenn es nicht leer ist
if (trainings.isNotEmpty) {
updates['trainings'] = trainings;
} else {
updates['trainings'] = null;
}
// Aktualisiere cancelledTrainings nur, wenn es nicht leer ist
if (cancelledTrainings.isNotEmpty) {
updates['cancelledTrainings'] = cancelledTrainings;
} else {
updates['cancelledTrainings'] = null;
}
// Führe die Aktualisierung durch
await FirebaseFirestore.instance.collection('User').doc(_currentUserId).update(updates);
// Aktualisiere die UI sofort
setState(() {
final normalizedDate = DateTime.parse(dateString);
if (_events.containsKey(normalizedDate)) {
_events[normalizedDate]!.removeWhere((e) => e['id'] == trainingId);
if (_events[normalizedDate]!.isEmpty) {
_events.remove(normalizedDate);
}
}
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Training wurde gelöscht')),
);
}
} catch (e) {
print('Error deleting training: $e');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Fehler beim Löschen des Trainings')),
);
}
}
}
Future<void> _removeExercise(Map<String, dynamic> event, Map<String, dynamic> exercise) async {
if (_userRole != 'trainer' || !event['isCurrentUser']) return;
try {
final userDoc = await FirebaseFirestore.instance
.collection('User')
.doc(_currentUserId)
.get();
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;
print('Entferne Übung:');
print('dateString: $dateString');
print('trainingId: $trainingId');
print('Vorher - trainings: $trainings');
// Finde das Training und entferne die Übung
if (trainings.containsKey(dateString)) {
final trainingsList = List<Map<String, dynamic>>.from(trainings[dateString]);
final trainingIndex = trainingsList.indexWhere((t) => t['id'] == trainingId);
if (trainingIndex != -1) {
final exercises = List<Map<String, dynamic>>.from(trainingsList[trainingIndex]['exercises'] ?? []);
exercises.removeWhere((e) => e['id'] == exercise['id']);
trainingsList[trainingIndex]['exercises'] = exercises;
trainings[dateString] = trainingsList;
}
}
print('Nachher - trainings: $trainings');
// Aktualisiere die Datenbank
await FirebaseFirestore.instance
.collection('User')
.doc(_currentUserId)
.update({
'trainings': trainings,
});
// Aktualisiere die UI sofort
setState(() {
final normalizedDate = DateTime.parse(dateString);
if (_events.containsKey(normalizedDate)) {
final eventIndex = _events[normalizedDate]!.indexWhere((e) => e['id'] == trainingId);
if (eventIndex != -1) {
final exercises = List<Map<String, dynamic>>.from(_events[normalizedDate]![eventIndex]['exercises'] ?? []);
exercises.removeWhere((e) => e['id'] == exercise['id']);
_events[normalizedDate]![eventIndex]['exercises'] = exercises;
_events[normalizedDate]![eventIndex]['remainingTime'] =
(_events[normalizedDate]![eventIndex]['duration'] as int) -
exercises.fold<int>(0, (sum, exercise) => sum + (exercise['duration'] as int));
}
}
});
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Übung wurde entfernt')),
);
}
} catch (e) {
print('Error removing exercise: $e');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Fehler beim Entfernen der Übung')),
);
}
}
}
int _getDaysUntilNext(String day, int currentWeekday) {
final weekdays = {
'Montag': 1,
'Dienstag': 2,
'Mittwoch': 3,
'Donnerstag': 4,
'Freitag': 5,
'Samstag': 6,
'Sonntag': 7,
};
final targetWeekday = weekdays[day] ?? 1;
var daysUntilNext = targetWeekday - currentWeekday;
if (daysUntilNext <= 0) {
daysUntilNext += 7;
}
return daysUntilNext;
}
List<Map<String, dynamic>> _getEventsForDay(DateTime day) {
final normalizedDate = DateTime(day.year, day.month, day.day);
return _events[normalizedDate] ?? [];
}
@override
Widget build(BuildContext context) {
final isTrainer = _userRole == 'trainer';
return Scaffold(
appBar: AppBar(
title: const Text('Kalender'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _isLoading ? null : _loadEvents,
),
],
),
body: SafeArea(
child: Column(
children: [
Container(
padding: const EdgeInsets.all(8.0),
child: TableCalendar(
firstDay: DateTime.utc(2024, 1, 1),
lastDay: DateTime.utc(2025, 12, 31),
focusedDay: _focusedDay,
calendarFormat: _calendarFormat,
locale: 'de_DE',
selectedDayPredicate: (day) {
return isSameDay(_selectedDay, day);
},
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
},
onFormatChanged: (format) {
setState(() {
_calendarFormat = format;
});
},
onPageChanged: (focusedDay) {
setState(() {
_focusedDay = focusedDay;
});
},
eventLoader: _getEventsForDay,
calendarStyle: const CalendarStyle(
markersMaxCount: 1,
markerDecoration: BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
),
headerStyle: const HeaderStyle(
formatButtonVisible: true,
titleCentered: true,
),
calendarBuilders: CalendarBuilders(
markerBuilder: (context, date, events) {
if (events.isEmpty) return null;
return Positioned(
bottom: 1,
child: Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
),
);
},
),
startingDayOfWeek: StartingDayOfWeek.monday,
daysOfWeekStyle: const DaysOfWeekStyle(
weekdayStyle: TextStyle(fontWeight: FontWeight.bold),
weekendStyle: TextStyle(color: Colors.red),
),
availableCalendarFormats: const {
CalendarFormat.week: 'Woche',
CalendarFormat.month: 'Monat',
},
),
),
if (isTrainer && _selectedDay != null) ...[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
icon: const Icon(Icons.add),
label: const Text('Training an diesem Tag hinzufügen'),
onPressed: () async {
final result = await showDialog<Map<String, dynamic>>(
context: context,
builder: (context) => _TrainingEditDialog(
date: _selectedDay!,
),
);
if (result != null) {
await _addOrEditTraining(_selectedDay!, result['time'], result['duration'], isException: false);
await _loadEvents();
}
},
),
),
),
],
const Divider(),
Expanded(
child: _isLoading
? const Center(child: CircularProgressIndicator())
: _selectedDay == null
? const Center(child: Text('Bitte wähle einen Tag aus'))
: (() {
final events = _getEventsForDay(_selectedDay!);
// Sortiere nach Uhrzeit (Format: 'HH:mm')
events.sort((a, b) {
final aTime = a['time'] as String? ?? '00:00';
final bTime = b['time'] as String? ?? '00:00';
final aParts = aTime.split(':').map(int.parse).toList();
final bParts = bTime.split(':').map(int.parse).toList();
final aMinutes = aParts[0] * 60 + aParts[1];
final bMinutes = bParts[0] * 60 + bParts[1];
return aMinutes.compareTo(bMinutes);
});
return ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: events.length,
itemBuilder: (context, index) {
final event = events[index];
final isCurrentUser = event['isCurrentUser'] as bool;
final exercises = event['exercises'] as List<dynamic>;
final remainingTime = event['remainingTime'] as int;
return Card(
margin: const EdgeInsets.only(bottom: 8),
color: isCurrentUser ? Colors.blue.withOpacity(0.1) : null,
child: Column(
children: [
ListTile(
leading: Icon(
Icons.sports,
color: categoryColors[event['day']] ?? (isCurrentUser ? Colors.blue : Colors.grey),
),
title: Text(
isCurrentUser ? 'Training' : event['trainerName'],
style: TextStyle(
fontWeight: isCurrentUser ? FontWeight.bold : null,
),
),
subtitle: Text(
'${event['time']} - ${event['duration']} Minuten\nVerbleibende Zeit: $remainingTime Minuten',
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (isCurrentUser)
IconButton(
icon: const Icon(Icons.edit),
onPressed: () async {
final result = await showDialog<Map<String, dynamic>>(
context: context,
builder: (context) => _TrainingEditDialog(
date: _selectedDay!,
initialTime: event['time'],
initialDuration: event['duration'],
trainingId: event['id'],
),
);
if (result != null) {
await _addOrEditTraining(
_selectedDay!,
result['time'],
result['duration'],
isException: true,
trainingId: result['trainingId'],
);
await _loadEvents();
}
},
),
if (isCurrentUser)
IconButton(
icon: const Icon(Icons.add),
onPressed: () => _addExercise(event),
),
IconButton(
icon: const Icon(Icons.info),
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(isCurrentUser ? 'Training' : event['trainerName']),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Zeit: ${event['time']}'),
const SizedBox(height: 8),
Text('Dauer: ${event['duration']} Minuten'),
const SizedBox(height: 8),
Text('Verbleibende Zeit: $remainingTime Minuten'),
if (exercises.isNotEmpty) ...[
const SizedBox(height: 16),
const Text('Übungen:', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
...exercises.map((exercise) {
if (exercise is Map<String, dynamic>) {
return Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Row(
children: [
const Icon(Icons.fitness_center, size: 16),
const SizedBox(width: 8),
Expanded(
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TrainingDetailScreen(
trainingId: exercise['id'],
),
),
);
},
child: Text(
'${exercise['name']} - ${exercise['duration']} Minuten',
style: const TextStyle(
decoration: TextDecoration.underline,
color: Colors.blue,
),
),
),
),
if (isCurrentUser)
IconButton(
icon: const Icon(Icons.delete, color: Colors.red, size: 20),
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Übung entfernen'),
content: Text('Möchten Sie die Übung "${exercise['name']}" wirklich entfernen?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Abbrechen'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
_removeExercise(event, exercise);
},
child: const Text('Entfernen', style: TextStyle(color: Colors.red)),
),
],
),
);
},
),
],
),
);
}
return const SizedBox.shrink();
}),
],
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Schließen'),
),
],
),
);
},
),
if (isCurrentUser)
IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Training löschen'),
content: const Text('Möchten Sie dieses Training wirklich löschen?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Abbrechen'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
_deleteTraining(event);
},
child: const Text('Löschen', style: TextStyle(color: Colors.red)),
),
],
),
);
},
),
],
),
),
if (exercises.isNotEmpty)
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Divider(),
const Text('Übungen:', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
...exercises.map((exercise) {
if (exercise is Map<String, dynamic>) {
return Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Row(
children: [
const Icon(Icons.fitness_center, size: 16),
const SizedBox(width: 8),
Expanded(
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TrainingDetailScreen(
trainingId: exercise['id'],
),
),
);
},
child: Text(
'${exercise['name']} - ${exercise['duration']} Minuten',
style: const TextStyle(
decoration: TextDecoration.underline,
color: Colors.blue,
),
),
),
),
if (isCurrentUser)
IconButton(
icon: const Icon(Icons.delete, color: Colors.red, size: 20),
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Übung entfernen'),
content: Text('Möchten Sie die Übung "${exercise['name']}" wirklich entfernen?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Abbrechen'),
),
TextButton(
onPressed: () {
Navigator.pop(context);
_removeExercise(event, exercise);
},
child: const Text('Entfernen', style: TextStyle(color: Colors.red)),
),
],
),
);
},
),
],
),
);
}
return const SizedBox.shrink();
}),
],
),
),
],
),
);
},
);
})()
),
],
),
),
);
}
Future<void> _addOrEditTraining(DateTime date, String time, int duration, {bool isException = false, String? trainingId}) async {
final dateString = DateTime(date.year, date.month, date.day).toIso8601String();
final userDoc = await FirebaseFirestore.instance.collection('User').doc(_currentUserId).get();
final data = userDoc.data() ?? {};
final trainings = Map<String, dynamic>.from(data['trainings'] ?? {});
final uuid = const Uuid();
final cancelledTrainings = List<Map<String, dynamic>>.from(data['cancelledTrainings'] ?? []);
final trainingTimes = data['trainingTimes'] as Map<String, dynamic>? ?? {};
print('Füge/Bearbeite Training:');
print('dateString: $dateString');
print('trainingId: $trainingId');
print('Vorher - trainings: $trainings');
print('Vorher - cancelledTrainings: $cancelledTrainings');
// Trainingsliste für das Datum holen oder neu anlegen
final trainingsList = List<Map<String, dynamic>>.from(trainings[dateString] ?? []);
if (trainingId != null) {
// Bearbeiten: Training mit ID ersetzen
final idx = trainingsList.indexWhere((t) => t['id'] == trainingId);
if (idx != -1) {
trainingsList[idx] = {
'id': trainingId,
'time': time,
'duration': duration,
'exercises': trainingsList[idx]['exercises'] ?? [],
};
}
} else {
// Hinzufügen: neues Training-Objekt anfügen
trainingsList.add({
'id': uuid.v4(),
'time': time,
'duration': duration,
'exercises': [],
});
}
trainings[dateString] = trainingsList;
// Prüfe, ob an diesem Tag ein regelmäßiges Training stattfindet
final weekdays = {
'Montag': 1,
'Dienstag': 2,
'Mittwoch': 3,
'Donnerstag': 4,
'Freitag': 5,
'Samstag': 6,
'Sonntag': 7,
};
final weekday = date.weekday;
final weekdayName = weekdays.entries.firstWhere((entry) => entry.value == weekday).key;
// Wenn an diesem Tag ein regelmäßiges Training stattfindet, füge es zu cancelledTrainings hinzu
if (trainingTimes.containsKey(weekdayName)) {
if (!cancelledTrainings.any((cancelled) =>
cancelled is Map<String, dynamic> &&
cancelled['date'] == dateString
)) {
cancelledTrainings.add({
'date': dateString,
});
}
} else {
// Wenn kein regelmäßiges Training stattfindet, entferne den Eintrag aus cancelledTrainings
cancelledTrainings.removeWhere((cancelled) =>
cancelled is Map<String, dynamic> &&
cancelled.containsKey('date') &&
cancelled['date'] == dateString
);
}
print('Nachher - trainings: $trainings');
print('Nachher - cancelledTrainings: $cancelledTrainings');
// Aktualisiere die Datenbank
final updates = <String, dynamic>{};
// Aktualisiere trainings nur, wenn es nicht leer ist
if (trainings.isNotEmpty) {
updates['trainings'] = trainings;
} else {
updates['trainings'] = null;
}
// Aktualisiere cancelledTrainings nur, wenn es nicht leer ist
if (cancelledTrainings.isNotEmpty) {
updates['cancelledTrainings'] = cancelledTrainings;
} else {
updates['cancelledTrainings'] = null;
}
// Führe die Aktualisierung durch
await FirebaseFirestore.instance.collection('User').doc(_currentUserId).update(updates);
// Aktualisiere die UI sofort
setState(() {
final normalizedDate = DateTime(date.year, date.month, date.day);
final event = {
'trainerName': data['name'] ?? 'Unbekannter Trainer',
'time': time,
'duration': duration,
'trainerId': _currentUserId,
'isCurrentUser': true,
'day': dateString,
'date': dateString,
'exercises': [],
'remainingTime': duration,
'club': data['club'] ?? 'Kein Verein',
'id': trainingId ?? uuid.v4(),
};
if (_events.containsKey(normalizedDate)) {
_events[normalizedDate]!.add(event);
} else {
_events[normalizedDate] = [event];
}
});
}
}
class _TrainingEditDialog extends StatefulWidget {
final DateTime date;
final String? initialTime;
final int? initialDuration;
final String? trainingId;
const _TrainingEditDialog({
required this.date,
this.initialTime,
this.initialDuration,
this.trainingId,
});
@override
State<_TrainingEditDialog> createState() => _TrainingEditDialogState();
}
class _TrainingEditDialogState extends State<_TrainingEditDialog> {
TimeOfDay? _selectedTime;
int _duration = 60;
@override
void initState() {
super.initState();
if (widget.initialTime != null) {
final parts = widget.initialTime!.split(':');
if (parts.length == 2) {
_selectedTime = TimeOfDay(hour: int.parse(parts[0]), minute: int.parse(parts[1]));
}
}
if (widget.initialDuration != null) {
_duration = widget.initialDuration!;
}
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Training bearbeiten (${widget.date.day}.${widget.date.month}.${widget.date.year})'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.access_time),
title: const Text('Uhrzeit wählen'),
subtitle: Text(_selectedTime != null ? _selectedTime!.format(context) : 'Keine Uhrzeit gewählt'),
onTap: () async {
final picked = await showTimePicker(
context: context,
initialTime: _selectedTime ?? TimeOfDay.now(),
);
if (picked != null) {
setState(() => _selectedTime = picked);
}
},
),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: () {
if (_duration > 15) setState(() => _duration -= 15);
},
),
Text('$_duration Minuten', style: const TextStyle(fontSize: 18)),
IconButton(
icon: const Icon(Icons.add),
onPressed: _duration < 300 ? () => setState(() => _duration += 15) : null,
),
],
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Abbrechen'),
),
ElevatedButton(
onPressed: _selectedTime != null
? () {
final timeString = _selectedTime!.hour.toString().padLeft(2, '0') + ':' + _selectedTime!.minute.toString().padLeft(2, '0');
Navigator.pop(context, {
'time': timeString,
'duration': _duration,
'trainingId': widget.trainingId,
});
}
: null,
child: const Text('Speichern'),
),
],
);
}
}