1415 lines
62 KiB
Dart
1415 lines
62 KiB
Dart
// calendar_tab.dart
|
|
// This file contains the CalendarTab widget, which displays a calendar view of trainings and allows users to view, add, and manage training events.
|
|
|
|
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';
|
|
|
|
/// Mapping of training categories to colors for calendar display.
|
|
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,
|
|
};
|
|
|
|
/// The CalendarTab displays a calendar with all training events for the user.
|
|
class CalendarTab extends StatefulWidget {
|
|
final DateTime? initialDate;
|
|
const CalendarTab({super.key, this.initialDate});
|
|
|
|
@override
|
|
State<CalendarTab> createState() => _CalendarTabState();
|
|
}
|
|
|
|
/// State for the CalendarTab, manages event loading, calendar state, and user interactions.
|
|
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!;
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Initializes user data and loads events for the calendar.
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Loads all training events for the current user (trainer or player).
|
|
Future<void> _loadEvents() async {
|
|
if (_userRole == null) return;
|
|
|
|
setState(() => _isLoading = true);
|
|
try {
|
|
QuerySnapshot trainersSnapshot;
|
|
|
|
if (_userRole == 'trainer') {
|
|
// Trainer: only their own trainings
|
|
trainersSnapshot = await FirebaseFirestore.instance
|
|
.collection('User')
|
|
.where('role', isEqualTo: 'trainer')
|
|
.where(FieldPath.documentId, isEqualTo: _currentUserId)
|
|
.get();
|
|
} else {
|
|
// Player: trainings from trainers in their club
|
|
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>? ?? {};
|
|
|
|
// Load all specific trainings for each date
|
|
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;
|
|
});
|
|
|
|
// Build the event map for this training
|
|
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];
|
|
}
|
|
}
|
|
});
|
|
|
|
// Add recurring weekly trainings for the year
|
|
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,
|
|
};
|
|
|
|
// Create a set of cancelled dates for fast lookup
|
|
final cancelledDates = cancelledTrainings
|
|
.where((cancelled) => cancelled is Map<String, dynamic>)
|
|
.map((cancelled) => DateTime.parse((cancelled as Map<String, dynamic>)['date'] as String))
|
|
.toSet();
|
|
|
|
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;
|
|
|
|
// Find the first desired weekday date from the start of the year
|
|
DateTime firstDate = yearStart;
|
|
while (firstDate.weekday != targetWeekday) {
|
|
firstDate = firstDate.add(const Duration(days: 1));
|
|
}
|
|
|
|
// Generate weekly trainings for the whole year
|
|
while (firstDate.isBefore(yearEnd)) {
|
|
final normalizedDate = DateTime(firstDate.year, firstDate.month, firstDate.day);
|
|
final dateString = normalizedDate.toIso8601String();
|
|
|
|
// Check if the training for this date is cancelled
|
|
final isCancelled = cancelledDates.any((cancelledDate) =>
|
|
isSameDay(cancelledDate, normalizedDate)
|
|
);
|
|
|
|
// Check if a specific training already exists for this date
|
|
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) {
|
|
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 {
|
|
bool shouldAddExercise = true;
|
|
|
|
// Prüfe die Bewertungen der Übung in der Training-Collection
|
|
final exerciseDoc = await FirebaseFirestore.instance
|
|
.collection('Training')
|
|
.doc(result['id'])
|
|
.get();
|
|
|
|
// NEU: Prüfe, ob die Übung in den letzten 4 Wochen schon einmal durchgeführt wurde
|
|
final userDocForCheck = await FirebaseFirestore.instance
|
|
.collection('User')
|
|
.doc(_currentUserId)
|
|
.get();
|
|
if (userDocForCheck.exists) {
|
|
final userData = userDocForCheck.data() as Map<String, dynamic>;
|
|
final trainings = Map<String, dynamic>.from(userData['trainings'] ?? {});
|
|
final trainingDate = DateTime.tryParse(event['date'] as String) ?? DateTime.now();
|
|
final fourWeeksAgo = trainingDate.subtract(const Duration(days: 28));
|
|
bool foundInLast4Weeks = false;
|
|
trainings.forEach((dateString, trainingsList) {
|
|
final date = DateTime.tryParse(dateString);
|
|
// Prüfe: im Zeitraum [fourWeeksAgo, trainingDate) (also vor dem aktuellen Training)
|
|
if (date != null && date.isAfter(fourWeeksAgo) && date.isBefore(trainingDate)) {
|
|
final list = List<Map<String, dynamic>>.from(trainingsList);
|
|
for (final training in list) {
|
|
final exercises = List<Map<String, dynamic>>.from(training['exercises'] ?? []);
|
|
for (final exercise in exercises) {
|
|
if (exercise['id'] == result['id']) {
|
|
foundInLast4Weeks = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
if (foundInLast4Weeks) {
|
|
shouldAddExercise = await showDialog<bool>(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text('Übung kürzlich verwendet'),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text('Die Übung "${result['title']}" wurde in den 4 Wochen vor diesem Training bereits durchgeführt.'),
|
|
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;
|
|
if (!shouldAddExercise) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (exerciseDoc.exists) {
|
|
final exerciseData = exerciseDoc.data() as Map<String, dynamic>;
|
|
|
|
// 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'] ?? []);
|
|
} else if (exerciseData.containsKey('rating')) {
|
|
ratings = List<Map<String, dynamic>>.from(exerciseData['rating'] ?? []);
|
|
}
|
|
|
|
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;
|
|
|
|
// Wenn die durchschnittliche Bewertung unter 3 liegt, zeige eine Warnung
|
|
if (averageRating < 3) {
|
|
if (mounted) {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!shouldAddExercise) {
|
|
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;
|
|
|
|
final newExerciseData = {
|
|
'id': result['id'],
|
|
'name': result['title'],
|
|
'description': result['description'],
|
|
'duration': result['duration'],
|
|
};
|
|
|
|
final trainingsForDay = List<Map<String, dynamic>>.from(trainings[dateString] ?? []);
|
|
final trainingIndex = trainingsForDay.indexWhere((t) => t['id'] == trainingId);
|
|
|
|
String finalTrainingId = trainingId;
|
|
|
|
if (trainingIndex != -1) {
|
|
// Fall 1: Training ist bereits spezifisch, füge die Übung hinzu
|
|
final exercises = List<Map<String, dynamic>>.from(trainingsForDay[trainingIndex]['exercises'] ?? []);
|
|
exercises.add(newExerciseData);
|
|
trainingsForDay[trainingIndex]['exercises'] = exercises;
|
|
} else {
|
|
// Fall 2: Training ist wiederkehrend, erstelle eine neue spezifische Instanz
|
|
final newId = const Uuid().v4();
|
|
finalTrainingId = newId;
|
|
trainingsForDay.add({
|
|
'id': newId,
|
|
'time': event['time'],
|
|
'duration': event['duration'],
|
|
'exercises': [newExerciseData],
|
|
});
|
|
}
|
|
|
|
trainings[dateString] = trainingsForDay;
|
|
|
|
final updates = <String, dynamic>{'trainings': trainings};
|
|
|
|
// Wenn das Original-Event wiederkehrend war, unterdrücke es für diesen Tag
|
|
if (trainingId.startsWith('weekly_')) {
|
|
final cancelledTrainings = List<Map<String, dynamic>>.from(data['cancelledTrainings'] ?? []);
|
|
if (!cancelledTrainings.any((c) => c['date'] == dateString)) {
|
|
cancelledTrainings.add({'date': dateString});
|
|
updates['cancelledTrainings'] = cancelledTrainings;
|
|
}
|
|
}
|
|
|
|
// Aktualisiere die Datenbank
|
|
await FirebaseFirestore.instance
|
|
.collection('User')
|
|
.doc(_currentUserId)
|
|
.update(updates);
|
|
|
|
// 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 oldEvent = _events[normalizedDate]![eventIndex];
|
|
final exercises = List<Map<String, dynamic>>.from(oldEvent['exercises'] ?? []);
|
|
exercises.add(newExerciseData);
|
|
|
|
oldEvent['exercises'] = exercises;
|
|
oldEvent['remainingTime'] = (oldEvent['duration'] as int) - exercises.fold<int>(0, (sum, exercise) => sum + (exercise['duration'] as int));
|
|
|
|
if (trainingId.startsWith('weekly_')) {
|
|
oldEvent['id'] = finalTrainingId;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Übung wurde hinzugefügt')),
|
|
);
|
|
}
|
|
} catch (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;
|
|
|
|
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;
|
|
}
|
|
|
|
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');
|
|
|
|
} else {
|
|
print('DEBUG: Processing specific training deletion');
|
|
|
|
// Für spezifische Trainings: Entferne nur das Training an diesem Tag
|
|
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');
|
|
}
|
|
}
|
|
|
|
// 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>? ?? {};
|
|
|
|
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.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');
|
|
}
|
|
}
|
|
}
|
|
|
|
print('DEBUG: UI update completed');
|
|
});
|
|
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
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')),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Fehler beim Entfernen der Übung')),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
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>? ?? {};
|
|
|
|
// 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
|
|
);
|
|
}
|
|
|
|
// 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'),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|