Files
prosappweb/lib/ui/views/professional_calendar_view.dart
T
Lizandro GuarnizoandClaude Sonnet 4.6 15175c1b91 replace: swap prosappweb content for prosapp_web_app (more complete version)
prosapp_web_app has chat, dashboard, calendar, support, 13 providers and
Fluro URL routing. Keep Dockerfile + nginx.conf from previous prosappweb.
Upgrade google_fonts 6.2.1 → 8.1.0 (Dart 3.12 compat fix).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 15:26:32 -05:00

357 lines
12 KiB
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/models/usuario.dart';
import 'package:prosapp_web_app/providers/auth_provider.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/services_provider.dart';
import 'package:prosapp_web_app/ui/cards/white_card.dart';
import 'package:prosapp_web_app/utils/time_of_day_utils.dart';
import 'package:provider/provider.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:flutter/material.dart';
class ProfessionalCalendarView extends StatefulWidget {
const ProfessionalCalendarView({super.key});
@override
State<ProfessionalCalendarView> createState() =>
_ProfessionalCalendarViewState();
}
class _ProfessionalCalendarViewState extends State<ProfessionalCalendarView> {
DateTime today = DateTime.now();
late int numDay;
List<Service>? _services;
Usuario? user;
@override
void initState() {
super.initState();
numDay = today.weekday;
_fetchProfessionalAndServices();
}
void _fetchProfessionalAndServices() async {
final authProvider = Provider.of<AuthProvider>(context, listen: false);
final professionalFormProvider = Provider.of<ProfessionalFormProvider>(context, listen: false);
final servicesProvider = Provider.of<ServicesProvider>(context, listen: false);
final proProvider = Provider.of<ProfessionalProvider>(context, listen: false);
final professional = await proProvider.getProfessional(authProvider.user!.id);
professionalFormProvider.setProfesional(professional);
final services = await servicesProvider.getServicesForProfessional(professional.id);
setState(() {
_services = services;
user = authProvider.user;
});
}
void _onDaySelected(DateTime day, DateTime focusedDay) {
setState(() {
today = day;
numDay = today.weekday;
});
_fetchProfessionalAndServices();
}
@override
Widget build(BuildContext context) {
return Consumer<ProfessionalFormProvider>(
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.utc(2030, 3, 14),
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),
],
),
],
),
),
),
),
],
);
},
);
}
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<Widget> _rangesItems(ScheduleEntity? schedule, BuildContext context) {
if (schedule == null ||
!schedule.enabled ||
schedule.range1Hour1 == null ||
schedule.range2Hour2 == null) {
return [
const Padding(
padding: EdgeInsets.symmetric(vertical: 25),
child: Text("No hay horarios disponibles"),
)
];
}
if (schedule.continuousDay) {
List<TimeOfDay> ranges = TimeOfDayUtils.genRanges(
schedule.range1Hour1!,
schedule.range2Hour2!,
);
return rangesItemList(ranges, _services, today, context);
}
List<TimeOfDay> ranges1 = TimeOfDayUtils.genRanges(
schedule.range1Hour1!,
schedule.range1Hour2!,
);
List<TimeOfDay> ranges2 = TimeOfDayUtils.genRanges(
schedule.range2Hour1!,
schedule.range2Hour2!,
);
return [
...rangesItemList(ranges1, _services, today, context),
...rangesItemList(ranges2, _services, today, context),
];
}
bool _isHora1Ocupada(
TimeOfDay hora1, List<Service>? 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<Widget> rangesItemList(List<TimeOfDay> ranges, List<Service>? events,
DateTime selectedDay, BuildContext context) {
return ranges.map((time) {
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: () {
if (events != null) {
for (Service event in events) {
if (selectedDay.toIso8601String().split('T').first ==
event.day) {
if (time == event.range1Hour1) {
if (event.userId == event.professionalId) {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Horario ocupado por ti'),
),
);
} else {
// Navigator.push(
// context,
// CupertinoPageRoute(
// builder: (context) => ProfessionalServiceScreen(
// serviceId: event.id!,
// ),
// ),
// );
}
}
}
}
}
},
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.symmetric(vertical: 5, horizontal: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: ListTile(
onTap: () {
showDialog(
context: context,
builder: (BuildContext dialogContext) {
return AlertDialog(
title: const Text('Reservar hora'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'¿Estás seguro de que deseas reservar a las ${ScheduleEntity.getFormatTime(time)} del ${DateFormat('dd-MM-yyyy').format(today)}?',
),
const SizedBox(height: 10),
const Text(
'⚠️ Esta acción no se puede deshacer ⚠️',
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(dialogContext);
},
child: const Text('No, cancelar'),
),
TextButton(
onPressed: () {
Navigator.pop(dialogContext);
Navigator.pop(context);
},
child: const Text('Sí, reservar'),
),
],
);
},
);
},
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();
}
}