385 lines
14 KiB
Dart
385 lines
14 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';
|
|
|
|
class CalendarTab extends StatefulWidget {
|
|
const CalendarTab({super.key});
|
|
|
|
@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;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_focusedDay = DateTime.now();
|
|
_selectedDay = _focusedDay;
|
|
_currentUserId = FirebaseAuth.instance.currentUser?.uid;
|
|
_initializeData();
|
|
}
|
|
|
|
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') {
|
|
// Trainer sieht nur seine eigenen Trainings
|
|
trainersSnapshot = await FirebaseFirestore.instance
|
|
.collection('User')
|
|
.where('role', isEqualTo: 'trainer')
|
|
.where(FieldPath.documentId, isEqualTo: _currentUserId)
|
|
.get();
|
|
} else {
|
|
// Spieler sehen alle Trainings
|
|
trainersSnapshot = await FirebaseFirestore.instance
|
|
.collection('User')
|
|
.where('role', isEqualTo: 'trainer')
|
|
.get();
|
|
}
|
|
|
|
final events = <DateTime, List<Map<String, dynamic>>>{};
|
|
|
|
for (var trainerDoc in trainersSnapshot.docs) {
|
|
final trainerData = trainerDoc.data() as Map<String, dynamic>;
|
|
final trainingTimes = trainerData['trainingTimes'] as Map<String, dynamic>? ?? {};
|
|
final trainingDurations = trainerData['trainingDurations'] as Map<String, dynamic>? ?? {};
|
|
final cancelledTrainings = trainerData['cancelledTrainings'] as List<dynamic>? ?? [];
|
|
|
|
trainingTimes.forEach((day, timeStr) {
|
|
if (timeStr == null) return;
|
|
|
|
final duration = trainingDurations[day] as int? ?? 60;
|
|
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 now = DateTime.now();
|
|
final daysUntilNext = _getDaysUntilNext(day, now.weekday);
|
|
final eventDate = DateTime(now.year, now.month, now.day + daysUntilNext, hour, minute);
|
|
|
|
// Erstelle Trainings für ein ganzes Jahr
|
|
for (var i = 0; i < 52; i++) {
|
|
final date = eventDate.add(Duration(days: i * 7));
|
|
final normalizedDate = DateTime(date.year, date.month, date.day);
|
|
|
|
// Prüfe, ob das Training an diesem Tag abgesagt wurde
|
|
final isCancelled = cancelledTrainings.any((cancelled) {
|
|
if (cancelled is Map<String, dynamic>) {
|
|
final cancelledDate = DateTime.parse(cancelled['date'] as String);
|
|
return isSameDay(cancelledDate, normalizedDate);
|
|
}
|
|
return false;
|
|
});
|
|
|
|
if (!isCancelled) {
|
|
final event = {
|
|
'trainerName': trainerData['name'] ?? 'Unbekannter Trainer',
|
|
'time': timeStr,
|
|
'duration': duration,
|
|
'trainerId': trainerDoc.id,
|
|
'isCurrentUser': trainerDoc.id == _currentUserId,
|
|
'day': day,
|
|
'date': normalizedDate.toIso8601String(),
|
|
};
|
|
|
|
if (events.containsKey(normalizedDate)) {
|
|
events[normalizedDate]!.add(event);
|
|
} else {
|
|
events[normalizedDate] = [event];
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
setState(() {
|
|
_events = events;
|
|
_isLoading = false;
|
|
});
|
|
} catch (e) {
|
|
print('Error loading events: $e');
|
|
setState(() => _isLoading = false);
|
|
}
|
|
}
|
|
|
|
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 cancelledTrainings = List<Map<String, dynamic>>.from(data['cancelledTrainings'] ?? []);
|
|
|
|
// Füge das Training zur Liste der abgesagten Trainings hinzu
|
|
cancelledTrainings.add({
|
|
'date': event['date'],
|
|
'day': event['day'],
|
|
'time': event['time'],
|
|
'duration': event['duration'],
|
|
});
|
|
|
|
// Aktualisiere die Daten in Firestore
|
|
await FirebaseFirestore.instance
|
|
.collection('User')
|
|
.doc(_currentUserId)
|
|
.update({
|
|
'cancelledTrainings': cancelledTrainings,
|
|
});
|
|
|
|
// Lade die Events neu
|
|
await _loadEvents();
|
|
|
|
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')),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
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) {
|
|
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,
|
|
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',
|
|
},
|
|
),
|
|
),
|
|
const Divider(),
|
|
Expanded(
|
|
child: _isLoading
|
|
? const Center(child: CircularProgressIndicator())
|
|
: _selectedDay == null
|
|
? const Center(child: Text('Bitte wähle einen Tag aus'))
|
|
: ListView.builder(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
itemCount: _getEventsForDay(_selectedDay!).length,
|
|
itemBuilder: (context, index) {
|
|
final event = _getEventsForDay(_selectedDay!)[index];
|
|
final isCurrentUser = event['isCurrentUser'] as bool;
|
|
return Card(
|
|
margin: const EdgeInsets.only(bottom: 8),
|
|
color: isCurrentUser ? Colors.blue.withOpacity(0.1) : null,
|
|
child: ListTile(
|
|
leading: Icon(
|
|
Icons.sports,
|
|
color: isCurrentUser ? Colors.blue : null,
|
|
),
|
|
title: Text(
|
|
isCurrentUser ? 'Training' : event['trainerName'],
|
|
style: TextStyle(
|
|
fontWeight: isCurrentUser ? FontWeight.bold : null,
|
|
),
|
|
),
|
|
subtitle: Text(
|
|
'${event['time']} - ${event['duration']} Minuten',
|
|
),
|
|
trailing: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
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'),
|
|
],
|
|
),
|
|
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)),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|