Files
hidalyx-drluiscastro/lib/features/dashboard/presentation/views/appointments_modal_view.dart
T

964 lines
33 KiB
Dart

import 'package:flutter/material.dart';
import '../../../../core/theme/app_colors.dart';
import '../../../../core/widgets/medical_modal.dart';
import '../../../patient/presentation/views/patient_modal_view.dart';
import '../../data/models/patient_summary_model.dart';
import '../../data/services/dashboard_service.dart';
class AppointmentsModalView extends StatefulWidget {
final DateTime? initialDate;
final DashboardService? service;
final bool isSecretary;
final void Function(int patientId, String newStatus)? onStatusChanged;
const AppointmentsModalView({
super.key,
this.initialDate,
this.service,
this.isSecretary = false,
this.onStatusChanged,
});
@override
State<AppointmentsModalView> createState() => _AppointmentsModalViewState();
}
class _AppointmentsModalViewState extends State<AppointmentsModalView> {
late final DashboardService _service;
late DateTime _selectedDate;
bool _isLoading = false;
String? _errorMessage;
List<PatientSummaryModel> _appointments = [];
String _waitingFilter = 'todos'; // 'todos', 'en_espera', 'en_consulta', 'atendido', 'no_presente'
@override
void initState() {
super.initState();
_service = widget.service ?? DashboardService();
_selectedDate = widget.initialDate ?? DateTime.now();
_fetchAppointments(_selectedDate);
}
Future<void> _handleStatusChange(PatientSummaryModel item, String newStatus) async {
final success = await _service.updatePatientWaitingStatus(item.id, newStatus);
if (success && mounted) {
setState(() {
final index = _appointments.indexWhere((p) => p.id == item.id);
if (index != -1) {
_appointments[index] = _appointments[index].copyWith(estadoEspera: newStatus);
}
});
if (widget.onStatusChanged != null) {
widget.onStatusChanged!(item.id, newStatus);
}
}
}
List<PatientSummaryModel> get _filteredAppointments {
if (_waitingFilter == 'todos') return _appointments;
if (_waitingFilter == 'en_espera') return _appointments.where((a) => a.isEnEspera).toList();
if (_waitingFilter == 'en_consulta') return _appointments.where((a) => a.isEnConsulta).toList();
if (_waitingFilter == 'atendido') return _appointments.where((a) => a.isAtendido).toList();
if (_waitingFilter == 'no_presente') return _appointments.where((a) => a.isNoPresente).toList();
return _appointments;
}
bool _isToday(DateTime date) {
final now = DateTime.now();
return date.year == now.year && date.month == now.month && date.day == now.day;
}
String _formatDate(DateTime date) {
final day = date.day.toString().padLeft(2, '0');
final month = date.month.toString().padLeft(2, '0');
final year = date.year.toString();
return '$day/$month/$year';
}
String _formatDateHuman(DateTime date) {
const weekdays = [
'Lunes',
'Martes',
'Miércoles',
'Jueves',
'Viernes',
'Sábado',
'Domingo'
];
const months = [
'Enero',
'Febrero',
'Marzo',
'Abril',
'Mayo',
'Junio',
'Julio',
'Agosto',
'Septiembre',
'Octubre',
'Noviembre',
'Diciembre'
];
final weekdayName = weekdays[date.weekday - 1];
final monthName = months[date.month - 1];
return '$weekdayName, ${date.day} de $monthName de ${date.year}';
}
Future<void> _fetchAppointments(DateTime date) async {
setState(() {
_isLoading = true;
_errorMessage = null;
});
try {
final results = await _service.getAppointmentsByDate(date);
if (mounted) {
setState(() {
_appointments = results;
_isLoading = false;
});
}
} catch (e) {
if (mounted) {
setState(() {
_errorMessage = 'Error al cargar las citas para la fecha seleccionada: $e';
_isLoading = false;
});
}
}
}
void _onDateSelected(DateTime newDate) {
if (newDate.year == _selectedDate.year &&
newDate.month == _selectedDate.month &&
newDate.day == _selectedDate.day) {
return;
}
setState(() {
_selectedDate = newDate;
});
_fetchAppointments(newDate);
}
Future<void> _pickDate() async {
final picked = await showDatePicker(
context: context,
initialDate: _selectedDate,
firstDate: DateTime(2000),
lastDate: DateTime(2050),
helpText: 'SELECCIONAR FECHA DE CITAS',
confirmText: 'FILTRAR',
cancelText: 'CANCELAR',
builder: (context, child) {
return Theme(
data: Theme.of(context).copyWith(
colorScheme: const ColorScheme.light(
primary: AppColors.primary,
onPrimary: Colors.white,
onSurface: AppColors.textPrimary,
),
),
child: child!,
);
},
);
if (picked != null) {
_onDateSelected(picked);
}
}
void _openPatient(int patientId) {
showMedicalModal(
context: context,
builder: (_) => PatientModalView(
patientId: patientId,
isSecretary: widget.isSecretary,
onSaved: (_) {
_fetchAppointments(_selectedDate);
if (widget.onStatusChanged != null) {
widget.onStatusChanged!(patientId, 'atendido');
}
},
onDeleted: (_) {
_fetchAppointments(_selectedDate);
},
),
);
}
@override
Widget build(BuildContext context) {
final isFilteringToday = _isToday(_selectedDate);
return MedicalModalDialog(
titleIcon: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColors.warning.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(10),
),
child: const Icon(
Icons.event_available_rounded,
color: AppColors.warning,
size: 22,
),
),
title: 'Agenda de Citas Médicas',
subtitle: const Text(
'Consulte y gestione los pacientes citados por fecha de consulta',
style: TextStyle(fontSize: 12, color: AppColors.textMuted),
),
maxWidth: 820,
maxHeight: 700,
bottomBar: _buildBottomBar(),
child: Column(
children: [
// Barra de Filtro de Fecha
_buildDateFilterBar(isFilteringToday),
// Divisor
const Divider(height: 1, color: Color(0xFFE2E8F0)),
// Contenido Principal (Lista de Citas)
Expanded(
child: _buildBody(isFilteringToday),
),
],
),
);
}
Widget _buildDateFilterBar(bool isFilteringToday) {
final filterText = isFilteringToday
? 'Hoy (${_formatDate(_selectedDate)})'
: _formatDate(_selectedDate);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
decoration: const BoxDecoration(
color: Color(0xFFF8FAFC),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
alignment: WrapAlignment.spaceBetween,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 12,
runSpacing: 8,
children: [
// Indicador dinámico de filtro activo (Hoy vs Fecha Específica)
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: isFilteringToday
? AppColors.primaryContainer
: const Color(0xFFEFF6FF),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isFilteringToday
? AppColors.primary.withValues(alpha: 0.3)
: const Color(0xFF93C5FD),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
isFilteringToday ? Icons.today_rounded : Icons.date_range_rounded,
size: 16,
color: isFilteringToday ? AppColors.primary : const Color(0xFF2563EB),
),
const SizedBox(width: 8),
Text.rich(
TextSpan(
children: [
const TextSpan(
text: 'Filtro: ',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: AppColors.textSecondary,
),
),
TextSpan(
text: filterText,
style: TextStyle(
fontSize: 12.5,
fontWeight: FontWeight.w700,
color: isFilteringToday
? AppColors.primary
: const Color(0xFF1D4ED8),
),
),
],
),
),
],
),
),
// Botones de Navegación Rápida
Row(
mainAxisSize: MainAxisSize.min,
children: [
// Botón Día Anterior
IconButton(
icon: const Icon(Icons.chevron_left_rounded, size: 20),
tooltip: 'Día anterior',
splashRadius: 18,
onPressed: _isLoading
? null
: () => _onDateSelected(_selectedDate.subtract(const Duration(days: 1))),
),
// Botón acceso directo a "Hoy"
InkWell(
onTap: _isLoading || isFilteringToday
? null
: () => _onDateSelected(DateTime.now()),
borderRadius: BorderRadius.circular(6),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: isFilteringToday
? AppColors.primary
: Colors.white,
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: isFilteringToday
? AppColors.primary
: const Color(0xFFCBD5E1),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.refresh_rounded,
size: 14,
color: isFilteringToday ? Colors.white : AppColors.textPrimary,
),
const SizedBox(width: 4),
Text(
'Hoy',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: isFilteringToday ? Colors.white : AppColors.textPrimary,
),
),
],
),
),
),
// Botón Día Siguiente
IconButton(
icon: const Icon(Icons.chevron_right_rounded, size: 20),
tooltip: 'Día siguiente',
splashRadius: 18,
onPressed: _isLoading
? null
: () => _onDateSelected(_selectedDate.add(const Duration(days: 1))),
),
const SizedBox(width: 6),
// Botón Selector de Fecha
ElevatedButton.icon(
onPressed: _isLoading ? null : _pickDate,
icon: const Icon(Icons.calendar_month_outlined, size: 16),
label: const Text(
'Cambiar Fecha',
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: AppColors.textPrimary,
elevation: 0,
side: const BorderSide(color: Color(0xFFCBD5E1)),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
),
),
],
),
],
),
const SizedBox(height: 6),
Wrap(
alignment: WrapAlignment.spaceBetween,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 12,
runSpacing: 6,
children: [
Text(
_formatDateHuman(_selectedDate),
style: const TextStyle(
fontSize: 11.5,
color: AppColors.textMuted,
fontWeight: FontWeight.w500,
),
),
// Píldoras de Filtro de Sala de Espera
Wrap(
spacing: 6,
runSpacing: 4,
children: [
_buildFilterChip('todos', 'Todos (${_appointments.length})', null),
_buildFilterChip(
'en_espera',
'En Espera (${_appointments.where((a) => a.isEnEspera).length})',
const Color(0xFF15803D),
),
_buildFilterChip(
'en_consulta',
'En Consulta (${_appointments.where((a) => a.isEnConsulta).length})',
const Color(0xFF0369A1),
),
_buildFilterChip(
'atendido',
'Atendidos (${_appointments.where((a) => a.isAtendido).length})',
const Color(0xFF64748B),
),
],
),
],
),
],
),
);
}
Widget _buildFilterChip(String value, String label, Color? highlightColor) {
final isSelected = _waitingFilter == value;
return InkWell(
onTap: () {
setState(() {
_waitingFilter = value;
});
},
borderRadius: BorderRadius.circular(20),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: isSelected
? (highlightColor ?? AppColors.primary).withValues(alpha: 0.15)
: Colors.white,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: isSelected
? (highlightColor ?? AppColors.primary)
: const Color(0xFFE2E8F0),
width: isSelected ? 1.5 : 1,
),
),
child: Text(
label,
style: TextStyle(
fontSize: 11,
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w500,
color: isSelected
? (highlightColor ?? AppColors.primary)
: AppColors.textSecondary,
),
),
),
);
}
Widget _buildBody(bool isFilteringToday) {
if (_isLoading) {
return const Center(
child: Padding(
padding: EdgeInsets.all(40),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(),
SizedBox(height: 14),
Text(
'Consultando agenda médica...',
style: TextStyle(fontSize: 13, color: AppColors.textSecondary),
),
],
),
),
);
}
if (_errorMessage != null) {
return Center(
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.error_outline_rounded, color: AppColors.error, size: 40),
const SizedBox(height: 12),
Text(
_errorMessage!,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 13, color: AppColors.error),
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: () => _fetchAppointments(_selectedDate),
icon: const Icon(Icons.refresh_rounded, size: 16),
label: const Text('Reintentar'),
),
],
),
),
);
}
if (_appointments.isEmpty) {
return Center(
child: Padding(
padding: const EdgeInsets.all(36),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFF1F5F9),
borderRadius: BorderRadius.circular(50),
),
child: const Icon(
Icons.event_busy_rounded,
size: 42,
color: AppColors.textMuted,
),
),
const SizedBox(height: 16),
Text(
isFilteringToday
? 'No hay citas programadas para el día de hoy'
: 'No hay citas registradas para el ${_formatDate(_selectedDate)}',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: AppColors.textPrimary,
),
),
const SizedBox(height: 6),
Text(
isFilteringToday
? 'Puede consultar otra fecha utilizando el selector de calendario superior.'
: 'Intente seleccionar otro día o presione "Hoy" para volver a la agenda actual.',
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 12,
color: AppColors.textMuted,
),
),
],
),
),
);
}
final list = _filteredAppointments;
if (list.isEmpty) {
return Center(
child: Padding(
padding: const EdgeInsets.all(36),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.filter_list_off_rounded, size: 38, color: AppColors.textMuted),
const SizedBox(height: 12),
Text(
'No hay pacientes con el filtro seleccionado',
style: const TextStyle(fontWeight: FontWeight.w600, color: AppColors.textSecondary),
),
const SizedBox(height: 8),
TextButton(
onPressed: () => setState(() => _waitingFilter = 'todos'),
child: const Text('Mostrar todos'),
),
],
),
),
);
}
return ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
itemCount: list.length,
separatorBuilder: (_, index) => const SizedBox(height: 10),
itemBuilder: (context, index) {
final item = list[index];
return _buildAppointmentTile(item);
},
);
}
Widget _buildAppointmentTile(PatientSummaryModel item) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: item.isEnEspera ? const Color(0xFF86EFAC) : const Color(0xFFE2E8F0),
width: item.isEnEspera ? 1.5 : 1,
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.02),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(12),
child: InkWell(
onTap: item.id > 0 ? () => _openPatient(item.id) : null,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.all(14),
child: Row(
children: [
// Icono de Cita
Container(
width: 42,
height: 42,
decoration: BoxDecoration(
color: item.isEnEspera
? const Color(0xFFDCFCE7)
: AppColors.teal.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
item.isEnEspera ? Icons.chair_rounded : Icons.calendar_today_rounded,
color: item.isEnEspera ? const Color(0xFF15803D) : AppColors.teal,
size: 20,
),
),
const SizedBox(width: 14),
// Datos del Paciente y Motivo
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
item.fullName,
style: const TextStyle(
fontSize: 13.5,
fontWeight: FontWeight.w700,
color: AppColors.textPrimary,
),
),
),
if (item.cedula.isNotEmpty)
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: const Color(0xFFF1F5F9),
borderRadius: BorderRadius.circular(4),
),
child: Text(
item.cedula,
style: const TextStyle(
fontSize: 10.5,
fontWeight: FontWeight.w600,
color: AppColors.textSecondary,
),
),
),
],
),
const SizedBox(height: 4),
Row(
children: [
if (item.telefono.isNotEmpty) ...[
const Icon(Icons.phone_outlined, size: 13, color: AppColors.textMuted),
const SizedBox(width: 4),
Text(
item.telefono,
style: const TextStyle(fontSize: 11.5, color: AppColors.textSecondary),
),
const SizedBox(width: 12),
],
const Icon(Icons.medical_information_outlined, size: 13, color: AppColors.textMuted),
const SizedBox(width: 4),
Expanded(
child: Text(
item.motCons.isNotEmpty
? item.motCons.replaceAll('\n', ' ').trim()
: 'Consulta médica programada',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 11.5, color: AppColors.textMuted),
),
),
],
),
],
),
),
const SizedBox(width: 12),
// Acciones de Sala de Espera / Estado
_buildAppointmentActions(item),
const SizedBox(width: 8),
// Botón Ver Ficha / Abrir Historial
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColors.primaryContainer,
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.arrow_forward_ios_rounded,
color: AppColors.primary,
size: 14,
),
),
],
),
),
),
),
);
}
Widget _buildAppointmentActions(PatientSummaryModel item) {
const double uniformHeight = 28.0;
const double uniformRadius = 6.0;
if (item.isEnEspera) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
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),
),
),
],
),
),
if (!widget.isSecretary) ...[
const SizedBox(width: 6),
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 {
await _handleStatusChange(item, 'en_consulta');
_openPatient(item.id);
},
icon: const Icon(Icons.arrow_forward_rounded, size: 12),
label: const Text('Atender', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w700)),
),
),
] else ...[
const SizedBox(width: 4),
Tooltip(
message: 'Quitar de la sala de espera',
child: InkWell(
onTap: () => _handleStatusChange(item, '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 (item.isEnConsulta) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: () => _openPatient(item.id),
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 (!widget.isSecretary) ...[
const SizedBox(width: 6),
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: () => _handleStatusChange(item, 'atendido'),
icon: const Icon(Icons.check_rounded, size: 13),
label: const Text('Finalizar', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w700)),
),
),
],
],
);
}
if (item.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)),
),
],
),
);
}
// no_presente
return Row(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.isSecretary)
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: () => _handleStatusChange(item, 'en_espera'),
icon: const Icon(Icons.how_to_reg_rounded, size: 13),
label: const Text('Llegó', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w700)),
),
),
],
);
}
Widget _buildBottomBar() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
decoration: const BoxDecoration(
color: Color(0xFFF8FAFC),
border: Border(top: BorderSide(color: Color(0xFFE2E8F0))),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6),
border: Border.all(color: const Color(0xFFE2E8F0)),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.keyboard_outlined, size: 12, color: AppColors.textMuted),
SizedBox(width: 4),
Text(
'ESC para salir',
style: TextStyle(fontSize: 11, color: AppColors.textMuted, fontWeight: FontWeight.w500),
),
],
),
),
const Spacer(),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.primary,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10),
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: const Text(
'Cerrar',
style: TextStyle(fontSize: 13, fontWeight: FontWeight.w600),
),
),
],
),
);
}
}