import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:prosapp_web_app/models/profesional.dart'; import 'package:prosapp_web_app/models/schedules_entity.dart'; import 'package:prosapp_web_app/models/service.dart'; import 'package:prosapp_web_app/providers/professional_form_provider.dart'; import 'package:prosapp_web_app/providers/professional_provider.dart'; import 'package:prosapp_web_app/providers/calendar_services_provider.dart'; import 'package:prosapp_web_app/services/notifications_service.dart'; import 'package:prosapp_web_app/ui/cards/white_card.dart'; import 'package:prosapp_web_app/utils/time_of_day_extension.dart'; import 'package:prosapp_web_app/utils/time_of_day_utils.dart'; import 'package:provider/provider.dart'; import 'package:table_calendar/table_calendar.dart'; class CalendarView extends StatefulWidget { final String professionalId; const CalendarView({super.key, required this.professionalId}); @override State createState() => _CalendarViewState(); } class _CalendarViewState extends State { List? _services; DateTime today = DateTime.now(); late int numDay; @override void initState() { super.initState(); today = DateTime.utc(today.year, today.month, today.day); numDay = today.weekday; _fetchProfessionalAndServices(); } void _fetchProfessionalAndServices() async { final professionalFormProvider = Provider.of(context, listen: false); final servicesProvider = Provider.of(context, listen: false); final proProvider = Provider.of(context, listen: false); final professional = await proProvider.getProfessional(widget.professionalId); professionalFormProvider.setProfesional(professional); final services = await servicesProvider.getServicesForProfessional(professional.id); setState(() { _services = services; }); } void _onDaySelected(DateTime day, DateTime focusedDay) { setState(() { today = day; numDay = today.weekday; }); _fetchProfessionalAndServices(); } @override Widget build(BuildContext context) { return Consumer( builder: (context, professionalFormProvider, child) { if (professionalFormProvider.profesional == null) { return const Center( child: CircularProgressIndicator(), ); } final profesional = professionalFormProvider.profesional!; return ListView( physics: const ClampingScrollPhysics(), children: [ Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 900), child: WhiteCard( title: 'Calendario', child: Column( children: [ TableCalendar( locale: 'es_CO', firstDay: DateTime.now(), lastDay: DateTime.now().add(const Duration(days: 180)), focusedDay: today, availableGestures: AvailableGestures.all, onDaySelected: _onDaySelected, selectedDayPredicate: (day) => isSameDay(day, today), ), const Divider(height: 0), 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), Column( children: [ ..._rangesItems( _getScheduleFromNumDay(numDay, profesional), context), const SizedBox(height: 8), ], ), ], ), ), ), ), ], ); }); } ScheduleEntity? _getScheduleFromNumDay( int numDay, Profesional userProfessional) { switch (numDay) { case 1: return userProfessional.schedules.monday; case 2: return userProfessional.schedules.tuesday; case 3: return userProfessional.schedules.wednesday; case 4: return userProfessional.schedules.thursday; case 5: return userProfessional.schedules.friday; case 6: return userProfessional.schedules.saturday; case 7: return userProfessional.schedules.sunday; default: return null; } } List _rangesItems(ScheduleEntity? schedule, BuildContext context) { if (schedule == null) { return [ const Padding( padding: EdgeInsets.only(top: 20), child: Text("No hay horarios disponibles"), ) ]; } if (!schedule.enabled) { return [ const Padding( padding: EdgeInsets.only(top: 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.only(top: 20), child: Text("No hay horarios disponibles"), ) ]; } List ranges = TimeOfDayUtils.genRanges( schedule.range1Hour1!, schedule.range2Hour2!, ); return rangesItemList(ranges, _services, today, context); } 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.only(top: 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, context), ...rangesItemList(ranges2, _services, today, context), ]; } } bool _isHora1Ocupada( TimeOfDay hora1, List? events, DateTime selectedDay) { if (events != null) { for (Service event in events) { if (selectedDay.toIso8601String().split('T').first == event.day) { if (hora1 == event.range1Hour1) { return true; } } } } return false; } List rangesItemList(List ranges, List? events,DateTime selectedDay, BuildContext context) { 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.only(top: 15, left: 10, right: 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, color: Colors.grey, ), ), subtitle: const Text( 'No disponible', style: TextStyle( color: Colors.red, fontSize: 13, fontWeight: FontWeight.bold, ), ), ), ); } if (_isHora1Ocupada(time, events, selectedDay)) { return Card( elevation: 4, margin: const EdgeInsets.only(top: 15, left: 10, right: 10), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), child: ListTile( onTap: () { if (events != null) { for (Service event in events) { if (selectedDay.toIso8601String().split('T').first == event.day) { if (time == event.range1Hour1) { NotificationsService.showSnackbar('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), ), ), ); } else { return Card( elevation: 4, margin: const EdgeInsets.only(top: 15, left: 10, right: 10), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), child: ListTile( onTap: () { Navigator.pop(context, [selectedDay, time]); }, 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, ), ), ), ); } }).toList(); } }