964 lines
33 KiB
Dart
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),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|