import 'package:flutter/material.dart'; import '../../../../core/theme/app_colors.dart'; import '../../data/models/patient_summary_model.dart'; class TodayAppointmentsCard extends StatelessWidget { final List appointments; final VoidCallback? onSchedulePressed; final ValueChanged? onPatientTap; final bool isSecretary; final Future Function(PatientSummaryModel patient, String newStatus)? onStatusChange; const TodayAppointmentsCard({ super.key, required this.appointments, this.onSchedulePressed, this.onPatientTap, this.isSecretary = false, this.onStatusChange, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(22), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.border), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.02), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Encabezado Row( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: AppColors.warning.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(10), ), child: const Icon( Icons.event_available_rounded, color: AppColors.warning, size: 20, ), ), const SizedBox(width: 12), const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Citas para Hoy', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w700, color: AppColors.textPrimary, ), ), Text( 'Agenda y turnos de consulta médica', style: TextStyle( fontSize: 12, color: AppColors.textSecondary, ), ), ], ), ), if (onSchedulePressed != null) TextButton( onPressed: onSchedulePressed, child: const Text( 'Ver todos', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.primary, ), ), ), ], ), const SizedBox(height: 18), const Divider(color: AppColors.divider, height: 1), const SizedBox(height: 14), // Contenido de Citas con Scroll Interno delimitado ConstrainedBox( constraints: const BoxConstraints(maxHeight: 330), child: Scrollbar( child: SingleChildScrollView( child: appointments.isEmpty ? Padding( padding: const EdgeInsets.symmetric(vertical: 32), child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.event_note_rounded, size: 40, color: AppColors.textMuted.withValues(alpha: 0.6), ), const SizedBox(height: 8), const Text( 'No hay citas registradas para el día de hoy', style: TextStyle(color: AppColors.textSecondary, fontSize: 13), ), const SizedBox(height: 4), const Text( 'Los nuevos turnos agendados aparecerán aquí', style: TextStyle(color: AppColors.textMuted, fontSize: 11), ), ], ), ), ) : Padding( padding: const EdgeInsets.only(right: 6), child: Column( children: [ for (var i = 0; i < appointments.length; i++) ...[ if (i > 0) const Divider(color: AppColors.divider, height: 16), Material( color: Colors.transparent, borderRadius: BorderRadius.circular(10), child: InkWell( onTap: onPatientTap != null && appointments[i].id > 0 ? () => onPatientTap!(appointments[i]) : null, borderRadius: BorderRadius.circular(10), hoverColor: AppColors.primary.withValues(alpha: 0.05), child: Padding( padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 6), child: Row( children: [ // Icono de Cita Container( width: 38, height: 38, decoration: BoxDecoration( color: AppColors.teal.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(10), ), child: const Icon( Icons.access_time_rounded, color: AppColors.teal, size: 20, ), ), const SizedBox(width: 14), // Paciente y Motivo de Consulta Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( appointments[i].fullName, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), const SizedBox(height: 2), Wrap( spacing: 8, runSpacing: 2, crossAxisAlignment: WrapCrossAlignment.center, children: [ if (appointments[i].telefono.isNotEmpty) Text( appointments[i].telefono, style: const TextStyle( fontSize: 11, color: AppColors.textSecondary, ), ), Text( appointments[i].motCons.isNotEmpty ? appointments[i].motCons.replaceAll('\n', ' ').trim() : 'Consulta de rutina', style: const TextStyle( fontSize: 11, color: AppColors.textMuted, ), ), ], ), ], ), ), const SizedBox(width: 10), // Estado de Sala de Espera y Acciones Rápidas _buildStatusAndAction(context, appointments[i]), ], ), ), ), ), ], ], ), ), ), ), ), ], ), ); } Widget _buildStatusAndAction(BuildContext context, PatientSummaryModel patient) { const double uniformHeight = 28.0; const double uniformRadius = 6.0; if (patient.isEnEspera) { return Row( mainAxisSize: MainAxisSize.min, children: [ // Badge En Espera Container( height: uniformHeight, padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( color: const Color(0xFFDCFCE7), borderRadius: BorderRadius.circular(uniformRadius), border: Border.all(color: const Color(0xFF86EFAC)), ), alignment: Alignment.center, child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.hourglass_top_rounded, size: 12, color: Color(0xFF15803D)), SizedBox(width: 4), Text( 'En Espera', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, color: Color(0xFF15803D), ), ), ], ), ), // Botón Doctor: Llamar a Consulta if (!isSecretary) ...[ const SizedBox(width: 6), Tooltip( message: 'Llamar a consulta médica ahora', child: SizedBox( height: uniformHeight, child: ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: AppColors.primary, foregroundColor: Colors.white, elevation: 0, padding: const EdgeInsets.symmetric(horizontal: 10), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(uniformRadius), ), visualDensity: VisualDensity.compact, ), onPressed: () async { if (onStatusChange != null) { await onStatusChange!(patient, 'en_consulta'); } if (onPatientTap != null) { onPatientTap!(patient); } }, icon: const Icon(Icons.arrow_forward_rounded, size: 12), label: const Text( 'Atender', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w700), ), ), ), ), ], // Botón Secretaria: Revertir si fue error if (isSecretary && onStatusChange != null) ...[ const SizedBox(width: 4), Tooltip( message: 'Quitar de la sala de espera', child: InkWell( onTap: () => onStatusChange!(patient, 'no_presente'), borderRadius: BorderRadius.circular(uniformRadius), child: Container( height: uniformHeight, width: uniformHeight, alignment: Alignment.center, child: const Icon(Icons.close_rounded, size: 15, color: AppColors.textMuted), ), ), ), ], ], ); } if (patient.isEnConsulta) { return Row( mainAxisSize: MainAxisSize.min, children: [ InkWell( onTap: onPatientTap != null ? () => onPatientTap!(patient) : null, borderRadius: BorderRadius.circular(uniformRadius), child: Container( height: uniformHeight, padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( color: const Color(0xFFE0F2FE), borderRadius: BorderRadius.circular(uniformRadius), border: Border.all(color: const Color(0xFF7DD3FC)), ), alignment: Alignment.center, child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.medical_services_rounded, size: 12, color: Color(0xFF0369A1)), SizedBox(width: 4), Text( 'En Consulta', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w700, color: Color(0xFF0369A1), ), ), ], ), ), ), if (!isSecretary && onStatusChange != null) ...[ const SizedBox(width: 6), Tooltip( message: 'Finalizar consulta médica y marcar como Atendido', child: SizedBox( height: uniformHeight, child: ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF0284C7), foregroundColor: Colors.white, elevation: 0, padding: const EdgeInsets.symmetric(horizontal: 10), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(uniformRadius), ), visualDensity: VisualDensity.compact, ), onPressed: () => onStatusChange!(patient, 'atendido'), icon: const Icon(Icons.check_rounded, size: 13), label: const Text( 'Finalizar', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w700), ), ), ), ), ], ], ); } if (patient.isAtendido) { return Container( height: uniformHeight, padding: const EdgeInsets.symmetric(horizontal: 9), decoration: BoxDecoration( color: const Color(0xFFF1F5F9), borderRadius: BorderRadius.circular(uniformRadius), border: Border.all(color: const Color(0xFFE2E8F0)), ), alignment: Alignment.center, child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.check_circle_rounded, size: 12, color: Color(0xFF64748B)), SizedBox(width: 4), Text( 'Atendido', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: Color(0xFF64748B), ), ), ], ), ); } // Estado no_presente (No ha llegado aún) return Row( mainAxisSize: MainAxisSize.min, children: [ if (isSecretary && onStatusChange != null) SizedBox( height: uniformHeight, child: ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF10B981), foregroundColor: Colors.white, elevation: 0, padding: const EdgeInsets.symmetric(horizontal: 10), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(uniformRadius), ), visualDensity: VisualDensity.compact, ), onPressed: () => onStatusChange!(patient, 'en_espera'), icon: const Icon(Icons.how_to_reg_rounded, size: 13), label: const Text( 'Llegó', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w700), ), ), ) else Container( height: uniformHeight, padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( color: AppColors.primaryContainer, borderRadius: BorderRadius.circular(uniformRadius), ), alignment: Alignment.center, child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon( Icons.calendar_month_rounded, size: 11, color: AppColors.primary, ), const SizedBox(width: 4), Text( patient.fecProxCita.isNotEmpty ? patient.fecProxCita.split(' ').first : 'Hoy', style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: AppColors.primary, ), ), ], ), ), ], ); } }