import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:injector/injector.dart'; import 'package:intl/intl.dart'; import 'package:professional_repository/professional_repository.dart'; import 'package:prosappco/blocs/professional_list_bloc/professional_list_bloc.dart'; import 'package:prosappco/utils/time_of_day_extension.dart'; import 'package:prosappco/utils/time_of_day_utils.dart'; import 'package:service_repository/service_repository.dart'; import 'package:setting_repository/setting_repository.dart'; import 'package:table_calendar/table_calendar.dart'; class UserCalendarScreen extends StatefulWidget { final UserProfessional userProfessional; const UserCalendarScreen({super.key, required this.userProfessional}); @override State createState() => UserCalendarScreenState(); } class UserCalendarScreenState extends State { final settingRepository = Injector.appInstance.get(); final serviceRepository = Injector.appInstance.get(); SettingEntity? settings; DateTime today = DateTime.now(); DateTime now = DateTime.now(); late int numDay; List? _services; CalendarFormat _calendarFormat = CalendarFormat.month; @override void initState() { super.initState(); today = DateTime.utc(today.year, today.month, today.day); numDay = today.weekday; _loadSettings(); _loadServices(); } void _loadSettings() { settingRepository.getSettings().then( (value) => setState(() { settings = value; }), ); } void _loadServices() { serviceRepository .getServicesForProfessionalforCalendar( widget.userProfessional.myUser.id) .then((services) { setState(() { _services = services; }); }); } void _onDaySelected(DateTime day, DateTime focusedDay) { setState(() { today = day; numDay = today.weekday; }); } void _onFormatChange(CalendarFormat format) { setState(() { _calendarFormat = format; }); } @override Widget build(BuildContext context) { DateTime lastDay = today.add(const Duration(days: 365)); return Scaffold( appBar: AppBar( title: const Text('Calendario'), ), floatingActionButtonLocation: kIsWeb ? FloatingActionButtonLocation.startFloat : FloatingActionButtonLocation.endFloat, resizeToAvoidBottomInset: false, body: Column( children: [ Container( color: const Color.fromARGB(255, 224, 247, 255), child: TableCalendar( locale: 'es_MX', firstDay: DateTime.now(), lastDay: lastDay, focusedDay: today, availableGestures: AvailableGestures.all, onDaySelected: _onDaySelected, selectedDayPredicate: (day) => isSameDay(day, today), calendarFormat: _calendarFormat, onFormatChanged: _onFormatChange, availableCalendarFormats: const { CalendarFormat.month: 'Mes', CalendarFormat.week: 'Semana', CalendarFormat.twoWeeks: '2 Semanas', }, ), ), SizedBox( width: double.infinity, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8), child: Text( DateFormat('dd MMMM yyyy', 'es').format(today), style: const TextStyle( color: Colors.black, fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ), const Divider( height: 0, ), Expanded( child: SingleChildScrollView( padding: const EdgeInsets.only(bottom: 15), child: Column( children: [ ..._rangesItems(_getScheduleFromNumDay(numDay)), ], ), ), ) ], ), ); } ScheduleEntity? _getScheduleFromNumDay(int numDay) { switch (numDay) { case 1: return widget.userProfessional.professionalInfo.schedules.monday; case 2: return widget.userProfessional.professionalInfo.schedules.tuesday; case 3: return widget.userProfessional.professionalInfo.schedules.wednesday; case 4: return widget.userProfessional.professionalInfo.schedules.thursday; case 5: return widget.userProfessional.professionalInfo.schedules.friday; case 6: return widget.userProfessional.professionalInfo.schedules.saturday; case 7: return widget.userProfessional.professionalInfo.schedules.sunday; default: return null; } } List _rangesItems(ScheduleEntity? schedule) { if (schedule == null) { return [ const Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Text("No hay horarios disponibles"), ) ]; } if (!schedule.enabled) { return [ const Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Text("No hay horarios disponibles"), ) ]; } if (schedule.continuousDay) { if (schedule.range1Hour1 == null || schedule.range2Hour2 == null || schedule.range1Hour1!.compareTo(schedule.range2Hour2!) >= 0) { return [ const Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Text("No hay horarios disponibles"), ) ]; } List ranges = TimeOfDayUtils.genRanges( schedule.range1Hour1!, schedule.range2Hour2!, ); return rangesItemList(ranges, _services, today); } else { if (schedule.range1Hour1 == null || schedule.range1Hour2 == null || schedule.range2Hour1 == null || schedule.range2Hour2 == null || schedule.range1Hour1!.compareTo(schedule.range1Hour2!) >= 0 || schedule.range2Hour1!.compareTo(schedule.range2Hour2!) >= 0) { return [ const Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Text("No hay horarios disponibles"), ) ]; } List ranges1 = TimeOfDayUtils.genRanges( schedule.range1Hour1!, schedule.range1Hour2!, ); List ranges2 = TimeOfDayUtils.genRanges( schedule.range2Hour1!, schedule.range2Hour2!, ); return [ ...rangesItemList(ranges1, _services, today), ...rangesItemList(ranges2, _services, today), ]; } } bool _isHora1Ocupada( TimeOfDay hora1, List? events, DateTime selectedDay) { if (events != null) { for (ServiceEntity event in events) { if (selectedDay.toString() == event.day) { if (hora1 == event.range1Hour1) { return true; } } } } return false; } List rangesItemList(List ranges, List? events, DateTime selectedDay) { final currentDateTime = DateTime.now(); return ranges.map((time) { final selectedDateTime = DateTime( selectedDay.year, selectedDay.month, selectedDay.day, time.hour, time.minute, ); if (selectedDateTime .isBefore(currentDateTime.add(const Duration(hours: 3)))) { return Card( elevation: 4, margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), child: ListTile( contentPadding: const EdgeInsets.all(16), leading: Container( width: 40, height: 40, decoration: const BoxDecoration( color: Colors.grey, shape: BoxShape.circle, ), child: const Center( child: Icon( Icons.access_time, color: Colors.white, ), ), ), title: Text( ScheduleEntity.getFormatTime(time) ?? '', style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold), ), subtitle: const Text( 'No disponible', style: TextStyle( color: Colors.red, fontSize: 13, fontWeight: FontWeight.bold), ), trailing: const Icon( Icons.arrow_forward_ios, color: Colors.grey, ), ), ); } if (_isHora1Ocupada(time, events, selectedDay)) { return Card( elevation: 4, margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), child: ListTile( onTap: () { ScaffoldMessenger.of(context).clearSnackBars(); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Este horario ya fue ocupado'), )); }, contentPadding: const EdgeInsets.all(16), leading: Container( width: 40, height: 40, decoration: const BoxDecoration( gradient: LinearGradient( colors: [Colors.yellow, Colors.red, Colors.red], begin: Alignment.topLeft, end: Alignment.bottomRight, ), shape: BoxShape.circle, ), child: const Center( child: Icon( Icons.access_time, color: Colors.white, ), ), ), title: Text( ScheduleEntity.getFormatTime(time) ?? '', style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold), ), subtitle: const Text( 'Ocupado', style: TextStyle( color: Colors.red, fontSize: 13, fontWeight: FontWeight.bold), ), trailing: const Icon( Icons.arrow_forward_ios, color: Colors.grey, ), ), ); } else { return Card( elevation: 4, margin: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), child: ListTile( onTap: () { if (settings?.domicilios == true) { if (widget.userProfessional.professionalInfo .locationPreferences == LocationPreferences.both) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ GestureDetector( onTap: () { Navigator.pop(context); Navigator.pop(context, [ today, time, ServiceLocationPreferences.delivery, widget.userProfessional, ]); }, child: const Text('A domicilio'), ), const Divider(color: Colors.black54), GestureDetector( onTap: () { Navigator.pop(context); Navigator.pop(context, [ today, time, ServiceLocationPreferences.office, widget.userProfessional, ]); }, child: const Text('En sitio'), ), ], ), ), ); }, ); } else if (widget.userProfessional.professionalInfo .locationPreferences == LocationPreferences.delivery) { Navigator.pop(context, [ today, time, ServiceLocationPreferences.delivery, widget.userProfessional, ]); } else { Navigator.pop(context, [ today, time, ServiceLocationPreferences.office, widget.userProfessional, ]); } } else { Navigator.pop(context, [ today, time, ServiceLocationPreferences.office, widget.userProfessional, ]); } }, contentPadding: const EdgeInsets.all(16), leading: Container( width: 40, height: 40, decoration: const BoxDecoration( gradient: LinearGradient( colors: [Colors.blue, Colors.green], begin: Alignment.topLeft, end: Alignment.bottomRight, ), shape: BoxShape.circle, ), child: const Center( child: Icon( Icons.access_time, color: Colors.white, ), ), ), title: Text( ScheduleEntity.getFormatTime(time) ?? '', style: const TextStyle(fontSize: 15, fontWeight: FontWeight.bold), ), subtitle: const Text( 'Disponible', style: TextStyle( color: Colors.green, fontSize: 13, fontWeight: FontWeight.bold, ), ), trailing: const Icon( Icons.arrow_forward_ios, color: Colors.grey, ), ), ); } }).toList(); } }