first commit
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
|
||||
enum ClinicalDocumentType {
|
||||
recetaMedica,
|
||||
autorizacionSeguro,
|
||||
referirDoctor,
|
||||
referirDepartamento,
|
||||
}
|
||||
|
||||
class ClinicalDocumentsDropdown extends StatelessWidget {
|
||||
final ValueChanged<ClinicalDocumentType> onSelect;
|
||||
|
||||
const ClinicalDocumentsDropdown({
|
||||
super.key,
|
||||
required this.onSelect,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopupMenuButton<ClinicalDocumentType>(
|
||||
tooltip: 'Documentos Clínicos e Impresión',
|
||||
offset: const Offset(0, 44),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
side: const BorderSide(color: Color(0xFFE2E8F0)),
|
||||
),
|
||||
elevation: 6,
|
||||
color: Colors.white,
|
||||
onSelected: onSelect,
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: ClinicalDocumentType.recetaMedica,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF00A65A).withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.medication_rounded,
|
||||
color: Color(0xFF00A65A),
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Receta Médica',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 13,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Formato media carta institucional',
|
||||
style: TextStyle(fontSize: 11, color: AppColors.textMuted),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const PopupMenuDivider(height: 1),
|
||||
PopupMenuItem(
|
||||
value: ClinicalDocumentType.autorizacionSeguro,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF00A65A).withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.verified_user_outlined,
|
||||
color: Color(0xFF00A65A),
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Autorización De Seguro',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 13,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Solicitud oficial de cobertura ARS',
|
||||
style: TextStyle(fontSize: 11, color: AppColors.textMuted),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const PopupMenuDivider(height: 1),
|
||||
PopupMenuItem(
|
||||
value: ClinicalDocumentType.referirDoctor,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF00A65A).withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.person_pin_circle_outlined,
|
||||
color: Color(0xFF00A65A),
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Referir a Doctor(a)',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 13,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Interconsulta médica a especialista',
|
||||
style: TextStyle(fontSize: 11, color: AppColors.textMuted),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const PopupMenuDivider(height: 1),
|
||||
PopupMenuItem(
|
||||
value: ClinicalDocumentType.referirDepartamento,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF00A65A).withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.apartment_rounded,
|
||||
color: Color(0xFF00A65A),
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Referir a Departamento',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 13,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Referimiento a servicio institucional',
|
||||
style: TextStyle(fontSize: 11, color: AppColors.textMuted),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: const Color(0xFFCBD5E1)),
|
||||
),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.description_outlined, color: AppColors.primary, size: 16),
|
||||
SizedBox(width: 6),
|
||||
Text(
|
||||
'Documentos Clínicos',
|
||||
style: TextStyle(
|
||||
color: AppColors.textPrimary,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 12.5,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 4),
|
||||
Icon(Icons.keyboard_arrow_down_rounded, color: AppColors.textSecondary, size: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,939 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../store/patient_store.dart';
|
||||
|
||||
class PatientClinicalTabs extends StatefulWidget {
|
||||
final PatientStore store;
|
||||
|
||||
const PatientClinicalTabs({super.key, required this.store});
|
||||
|
||||
@override
|
||||
State<PatientClinicalTabs> createState() => _PatientClinicalTabsState();
|
||||
}
|
||||
|
||||
class _PatientClinicalTabsState extends State<PatientClinicalTabs>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final TabController _tabController;
|
||||
late final TextEditingController _motConsController;
|
||||
late final FocusNode _motConsFocusNode;
|
||||
late final TextEditingController _hisEnfActController;
|
||||
late final FocusNode _hisEnfActFocusNode;
|
||||
ReactionDisposer? _disposerMotCons;
|
||||
ReactionDisposer? _disposerHisEnfAct;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tabController = TabController(length: 7, vsync: this);
|
||||
_motConsController = TextEditingController(text: widget.store.motCons);
|
||||
_motConsFocusNode = FocusNode();
|
||||
_hisEnfActController = TextEditingController(text: widget.store.hisEnfAct);
|
||||
_hisEnfActFocusNode = FocusNode();
|
||||
|
||||
_disposerMotCons = reaction((_) => widget.store.motCons, (String val) {
|
||||
if (_motConsController.text != val) {
|
||||
_motConsController.text = val;
|
||||
}
|
||||
});
|
||||
|
||||
_disposerHisEnfAct = reaction((_) => widget.store.hisEnfAct, (String val) {
|
||||
if (_hisEnfActController.text != val) {
|
||||
_hisEnfActController.text = val;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_disposerMotCons?.call();
|
||||
_disposerHisEnfAct?.call();
|
||||
_tabController.dispose();
|
||||
_motConsController.dispose();
|
||||
_motConsFocusNode.dispose();
|
||||
_hisEnfActController.dispose();
|
||||
_hisEnfActFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _insertDateAtCursor({
|
||||
required TextEditingController controller,
|
||||
required FocusNode focusNode,
|
||||
required ValueChanged<String> onUpdateStore,
|
||||
}) {
|
||||
final now = DateTime.now();
|
||||
final dateStr = DateFormat('dd/MM/yyyy').format(now);
|
||||
final insertionText = '$dateStr: ';
|
||||
|
||||
final currentText = controller.text;
|
||||
final selection = controller.selection;
|
||||
|
||||
int start = selection.start;
|
||||
int end = selection.end;
|
||||
|
||||
// Si el cursor no es válido o no está enfocado, insertar al final
|
||||
if (start < 0 || end < 0) {
|
||||
start = currentText.length;
|
||||
end = currentText.length;
|
||||
}
|
||||
|
||||
if (start > end) {
|
||||
final temp = start;
|
||||
start = end;
|
||||
end = temp;
|
||||
}
|
||||
|
||||
final before = currentText.substring(0, start);
|
||||
final after = currentText.substring(end);
|
||||
final newText = '$before$insertionText$after';
|
||||
final newOffset = start + insertionText.length;
|
||||
|
||||
controller.value = TextEditingValue(
|
||||
text: newText,
|
||||
selection: TextSelection.collapsed(offset: newOffset),
|
||||
);
|
||||
|
||||
onUpdateStore(newText);
|
||||
focusNode.requestFocus();
|
||||
}
|
||||
|
||||
InputDecoration _cleanTextAreaDecoration(String hintText) {
|
||||
return InputDecoration(
|
||||
hintText: hintText,
|
||||
hintStyle: const TextStyle(color: Color(0xFF94A3B8), fontSize: 13),
|
||||
filled: true,
|
||||
fillColor: const Color(0xFFF8FAFC),
|
||||
contentPadding: const EdgeInsets.all(16),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: Color(0xFFE2E8F0)),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: Color(0xFFE2E8F0)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: AppColors.primary, width: 1.5),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
InputDecoration _cleanDropdownDecoration(String label) {
|
||||
return InputDecoration(
|
||||
labelText: label,
|
||||
floatingLabelBehavior: FloatingLabelBehavior.always,
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
isDense: true,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: const BorderSide(color: Color(0xFFE2E8F0)),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: const BorderSide(color: Color(0xFFE2E8F0)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: const BorderSide(color: AppColors.primary, width: 1.5),
|
||||
),
|
||||
labelStyle: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
floatingLabelStyle: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
hintStyle: const TextStyle(
|
||||
fontSize: 13.5,
|
||||
color: Color(0xFF94A3B8),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildClinicalTextArea({
|
||||
required String name,
|
||||
String? initialValue,
|
||||
TextEditingController? controller,
|
||||
FocusNode? focusNode,
|
||||
required ValueChanged<String> onChanged,
|
||||
required String hintText,
|
||||
VoidCallback? onInsertDate,
|
||||
String? dateLabel,
|
||||
}) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: FormBuilderTextField(
|
||||
name: name,
|
||||
controller: controller,
|
||||
initialValue: controller == null ? initialValue : null,
|
||||
focusNode: focusNode,
|
||||
maxLines: null,
|
||||
expands: true,
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
style: const TextStyle(fontSize: 13.5, height: 1.5, color: AppColors.textPrimary),
|
||||
decoration: _cleanTextAreaDecoration(hintText),
|
||||
onChanged: (v) => onChanged(v ?? ''),
|
||||
),
|
||||
),
|
||||
if (onInsertDate != null) ...[
|
||||
const SizedBox(width: 16),
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (dateLabel != null && dateLabel.isNotEmpty) ...[
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primaryContainer.withValues(alpha: 0.6),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: AppColors.primary.withValues(alpha: 0.2)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.history_rounded, size: 16, color: AppColors.primary),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
dateLabel,
|
||||
style: const TextStyle(
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF8FAFC),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Text(
|
||||
'Entrada de Consulta',
|
||||
style: TextStyle(fontSize: 11, fontWeight: FontWeight.w700, color: AppColors.textSecondary),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Text(
|
||||
'Inserte el sello de tiempo para documentar una nueva nota clínica.',
|
||||
style: TextStyle(fontSize: 11, color: AppColors.textMuted, height: 1.3),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
ElevatedButton.icon(
|
||||
onPressed: onInsertDate,
|
||||
icon: const Icon(Icons.add_alarm_rounded, size: 16),
|
||||
label: const Text('Fecha actual', style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600)),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primary,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
elevation: 0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDropdownItem({
|
||||
required String name,
|
||||
required String label,
|
||||
required String initialValue,
|
||||
required List<String> items,
|
||||
required ValueChanged<String> onChanged,
|
||||
}) {
|
||||
final list = items.contains(initialValue)
|
||||
? items
|
||||
: (initialValue.isNotEmpty ? [initialValue, ...items] : items);
|
||||
return FormBuilderDropdown<String>(
|
||||
name: name,
|
||||
initialValue: initialValue.isNotEmpty ? initialValue : null,
|
||||
decoration: _cleanDropdownDecoration(label),
|
||||
hint: const Text(
|
||||
'Seleccionar...',
|
||||
style: TextStyle(fontSize: 13.5, color: Color(0xFF94A3B8)),
|
||||
),
|
||||
style: const TextStyle(fontSize: 13.5, color: AppColors.textPrimary, fontWeight: FontWeight.w500),
|
||||
onChanged: (v) {
|
||||
if (v != null) onChanged(v);
|
||||
},
|
||||
items: list.map((item) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: item,
|
||||
child: Text(
|
||||
item,
|
||||
style: const TextStyle(fontSize: 13.5),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAntecedentesTab(PatientStore s) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Patológicos
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
Icon(Icons.healing_rounded, size: 18, color: AppColors.primary),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Antecedentes Personales Patológicos',
|
||||
style: TextStyle(fontSize: 13, fontWeight: FontWeight.w700, color: AppColors.textPrimary),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildDropdownItem(
|
||||
name: 'dat_ni',
|
||||
label: 'Infancia',
|
||||
initialValue: s.datNi,
|
||||
items: const ['sarampión', 'varicela', 'Tos ferina', 'Hepatitis', 'Ninguna'],
|
||||
onChanged: (v) => s.datNi = v,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildDropdownItem(
|
||||
name: 'dat_ado',
|
||||
label: 'Adolescencia',
|
||||
initialValue: s.datAdo,
|
||||
items: const ['Asma', 'Trastornos alimenticios', 'TBC', 'VIH', 'Ninguna'],
|
||||
onChanged: (v) => s.datAdo = v,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildDropdownItem(
|
||||
name: 'dat_adu',
|
||||
label: 'Adultez',
|
||||
initialValue: s.datAdu,
|
||||
items: const [
|
||||
'TBC',
|
||||
'VIH',
|
||||
'Hepatopatías',
|
||||
'Cardiopatías',
|
||||
'Diabetes',
|
||||
'HTA',
|
||||
'Dislipidemias',
|
||||
'Ninguna',
|
||||
],
|
||||
onChanged: (v) => s.datAdu = v,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildDropdownItem(
|
||||
name: 'dat_alerg',
|
||||
label: 'Alergias',
|
||||
initialValue: s.datAlerg,
|
||||
items: const ['Ninguna', 'Penicilina', 'Sulfas', 'AINEs'],
|
||||
onChanged: (v) => s.datAlerg = v,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildDropdownItem(
|
||||
name: 'dat_tran',
|
||||
label: 'Transfusión sanguínea',
|
||||
initialValue: s.datTran,
|
||||
items: const ['1 ves', '2 veces', '3 veces', 'ninguna'],
|
||||
onChanged: (v) => s.datTran = v,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildDropdownItem(
|
||||
name: 'dat_cir',
|
||||
label: 'Intervenciones quirúrgicas',
|
||||
initialValue: s.datCir,
|
||||
items: const ['ninguna', 'Apendicectomía', 'Cesárea', 'Colecistectomía'],
|
||||
onChanged: (v) => s.datCir = v,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildDropdownItem(
|
||||
name: 'dat_ac',
|
||||
label: 'Accidentes',
|
||||
initialValue: s.datAc,
|
||||
items: const ['Si', 'ninguno'],
|
||||
onChanged: (v) => s.datAc = v,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildDropdownItem(
|
||||
name: 'dat_otrosp',
|
||||
label: 'Otros patológicos',
|
||||
initialValue: s.datOtrosp,
|
||||
items: const ['ninguno'],
|
||||
onChanged: (v) => s.datOtrosp = v,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// No patológicos
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
Icon(Icons.family_restroom_rounded, size: 18, color: AppColors.primary),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Antecedentes Personales No Patológicos (Familiares)',
|
||||
style: TextStyle(fontSize: 13, fontWeight: FontWeight.w700, color: AppColors.textPrimary),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildDropdownItem(
|
||||
name: 'dat_mad',
|
||||
label: 'Madre',
|
||||
initialValue: s.datMad,
|
||||
items: const ['Viva y sana', 'Fallecida', 'Enferma'],
|
||||
onChanged: (v) => s.datMad = v,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildDropdownItem(
|
||||
name: 'dat_esp',
|
||||
label: 'Pareja',
|
||||
initialValue: s.datEsp,
|
||||
items: const ['Viva y sana', 'Fallecida', 'Enferma'],
|
||||
onChanged: (v) => s.datEsp = v,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildDropdownItem(
|
||||
name: 'dat_padre',
|
||||
label: 'Padre',
|
||||
initialValue: s.datPadre,
|
||||
items: const ['Vivo y sano', 'Fallecido', 'Enfermo'],
|
||||
onChanged: (v) => s.datPadre = v,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _buildDropdownItem(
|
||||
name: 'dat_hijos',
|
||||
label: 'Hijos',
|
||||
initialValue: s.datHijos,
|
||||
items: const ['Vivos y sanos', 'Enfermos', 'Ninguno'],
|
||||
onChanged: (v) => s.datHijos = v,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildDropdownItem(
|
||||
name: 'dat_her',
|
||||
label: 'Hermanos',
|
||||
initialValue: s.datHer,
|
||||
items: const ['Vivos y sanos', 'Fallecidos', 'Enfermos', 'Ninguno'],
|
||||
onChanged: (v) => s.datHer = v,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _buildDropdownItem(
|
||||
name: 'dat_otrosnp',
|
||||
label: 'Otros no patológicos',
|
||||
initialValue: s.datOtrosnp,
|
||||
items: const ['Ningunos'],
|
||||
onChanged: (v) => s.datOtrosnp = v,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDiagnosticosTab(PatientStore s) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
Icon(Icons.biotech_rounded, size: 16, color: AppColors.primary),
|
||||
SizedBox(width: 6),
|
||||
Text(
|
||||
'Métodos diagnósticos, radiológicos e invasivos',
|
||||
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w700, color: AppColors.textPrimary),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: FormBuilderTextField(
|
||||
name: 'met_diagn_rad',
|
||||
initialValue: s.metDiagnRad,
|
||||
maxLines: null,
|
||||
expands: true,
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
decoration: _cleanTextAreaDecoration('Describa radiografías, ecografías, resonancias o procedimientos invasivos...'),
|
||||
onChanged: (v) => s.metDiagnRad = v ?? '',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Row(
|
||||
children: [
|
||||
Icon(Icons.star_outline_rounded, size: 16, color: AppColors.primary),
|
||||
SizedBox(width: 6),
|
||||
Text(
|
||||
'Diagnósticos a destacar',
|
||||
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w700, color: AppColors.textPrimary),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: FormBuilderTextField(
|
||||
name: 'diagn_desc',
|
||||
initialValue: s.diagnDesc,
|
||||
maxLines: null,
|
||||
expands: true,
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
decoration: _cleanTextAreaDecoration('Patologías principales, hallazgos o diagnósticos relevantes...'),
|
||||
onChanged: (v) => s.diagnDesc = v ?? '',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Row(
|
||||
children: [
|
||||
Icon(Icons.verified_outlined, size: 16, color: AppColors.primary),
|
||||
SizedBox(width: 6),
|
||||
Text(
|
||||
'Diagnóstico definitivo',
|
||||
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w700, color: AppColors.textPrimary),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: FormBuilderTextField(
|
||||
name: 'diagn_def',
|
||||
initialValue: s.diagnDef,
|
||||
maxLines: null,
|
||||
expands: true,
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
decoration: _cleanTextAreaDecoration('Conclusión diagnóstica definitiva y codificación clínica...'),
|
||||
onChanged: (v) => s.diagnDef = v ?? '',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProximaCitaTab(PatientStore s) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed: () async {
|
||||
final now = DateTime.now();
|
||||
final picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: now.add(const Duration(days: 7)),
|
||||
firstDate: now.subtract(const Duration(days: 30)),
|
||||
lastDate: now.add(const Duration(days: 365 * 3)),
|
||||
builder: (context, child) {
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: AppColors.primary,
|
||||
onPrimary: Colors.white,
|
||||
surface: Colors.white,
|
||||
),
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (picked != null) {
|
||||
s.fecProxcita = DateFormat('dd/MM/yyyy').format(picked);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.calendar_month_rounded, size: 18),
|
||||
label: const Text('Seleccionar Fecha de Cita', style: TextStyle(fontWeight: FontWeight.w600)),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: AppColors.primary,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
||||
elevation: 0,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
if (s.fecProxcita.isNotEmpty)
|
||||
TextButton.icon(
|
||||
onPressed: () => s.fecProxcita = '',
|
||||
icon: const Icon(Icons.clear_rounded, size: 16, color: AppColors.error),
|
||||
label: const Text('Quitar próxima cita', style: TextStyle(color: AppColors.error, fontWeight: FontWeight.w600)),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.03),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Icon(Icons.event_available_rounded, size: 22, color: AppColors.primary),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Control de Citas del Paciente',
|
||||
style: TextStyle(fontSize: 12, color: AppColors.textMuted, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
s.fecProxcita.isNotEmpty
|
||||
? 'Próxima cita programada: ${s.fecProxcita}'
|
||||
: 'Sin próxima cita programada',
|
||||
style: const TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (s.diasParaProximaCita != null) ...[
|
||||
const SizedBox(height: 14),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: s.diasParaProximaCita! < 0
|
||||
? AppColors.error.withValues(alpha: 0.1)
|
||||
: (s.diasParaProximaCita! == 0
|
||||
? AppColors.warning.withValues(alpha: 0.1)
|
||||
: AppColors.success.withValues(alpha: 0.1)),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
'Tiempo restante para la cita: ${s.diasParaProximaCita} días',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: s.diasParaProximaCita! < 0
|
||||
? AppColors.error
|
||||
: (s.diasParaProximaCita! == 0 ? AppColors.warning : AppColors.success),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = widget.store;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// Barra de Pestañas Moderna con Estilo Limpio
|
||||
Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFFF8FAFC),
|
||||
border: Border(
|
||||
bottom: BorderSide(color: Color(0xFFE2E8F0)),
|
||||
),
|
||||
),
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
isScrollable: true,
|
||||
labelColor: AppColors.primary,
|
||||
unselectedLabelColor: AppColors.textSecondary,
|
||||
labelStyle: const TextStyle(fontWeight: FontWeight.w700, fontSize: 12.5),
|
||||
unselectedLabelStyle: const TextStyle(fontWeight: FontWeight.w500, fontSize: 12.5),
|
||||
indicatorColor: AppColors.primary,
|
||||
indicatorWeight: 2.5,
|
||||
tabAlignment: TabAlignment.start,
|
||||
tabs: const [
|
||||
Tab(
|
||||
height: 38,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.notes_rounded, size: 15),
|
||||
SizedBox(width: 6),
|
||||
Text('Consultas'),
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
height: 38,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.history_edu_rounded, size: 15),
|
||||
SizedBox(width: 6),
|
||||
Text('Hist. enfermedad actual'),
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
height: 38,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.medical_information_outlined, size: 15),
|
||||
SizedBox(width: 6),
|
||||
Text('Antec. patológicos'),
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
height: 38,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.biotech_outlined, size: 15),
|
||||
SizedBox(width: 6),
|
||||
Text('Diagnósticos'),
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
height: 38,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.science_outlined, size: 15),
|
||||
SizedBox(width: 6),
|
||||
Text('Laboratorios'),
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
height: 38,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.medication_outlined, size: 15),
|
||||
SizedBox(width: 6),
|
||||
Text('Tratamiento'),
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
height: 38,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.event_outlined, size: 15),
|
||||
SizedBox(width: 6),
|
||||
Text('Próxima cita'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Observer(
|
||||
builder: (_) => TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
// 1. Consultas
|
||||
_buildClinicalTextArea(
|
||||
name: 'mot_cons',
|
||||
controller: _motConsController,
|
||||
focusNode: _motConsFocusNode,
|
||||
hintText: 'Registro de consultas clínicas, motivo y evolución...',
|
||||
dateLabel: s.fecUltcon.isNotEmpty ? 'Última consulta: ${s.fecUltcon}' : null,
|
||||
onInsertDate: () {
|
||||
_insertDateAtCursor(
|
||||
controller: _motConsController,
|
||||
focusNode: _motConsFocusNode,
|
||||
onUpdateStore: (v) => s.motCons = v,
|
||||
);
|
||||
},
|
||||
onChanged: (v) => s.motCons = v,
|
||||
),
|
||||
|
||||
// 2. Hist. enfermedad actual
|
||||
_buildClinicalTextArea(
|
||||
name: 'his_enf_act',
|
||||
controller: _hisEnfActController,
|
||||
focusNode: _hisEnfActFocusNode,
|
||||
hintText: 'Historia y cronología de la enfermedad actual...',
|
||||
onInsertDate: () {
|
||||
_insertDateAtCursor(
|
||||
controller: _hisEnfActController,
|
||||
focusNode: _hisEnfActFocusNode,
|
||||
onUpdateStore: (v) => s.hisEnfAct = v,
|
||||
);
|
||||
},
|
||||
onChanged: (v) => s.hisEnfAct = v,
|
||||
),
|
||||
|
||||
// 3. Antec. patológicos
|
||||
_buildAntecedentesTab(s),
|
||||
|
||||
// 4. Diagnósticos
|
||||
_buildDiagnosticosTab(s),
|
||||
|
||||
// 5. Laboratorios
|
||||
_buildClinicalTextArea(
|
||||
name: 'dat_lab',
|
||||
initialValue: s.datLab,
|
||||
hintText: 'Resultados de laboratorio, hemogramas, analíticas...',
|
||||
onChanged: (v) => s.datLab = v,
|
||||
),
|
||||
|
||||
// 6. Tratamiento
|
||||
_buildClinicalTextArea(
|
||||
name: 'tratamiento',
|
||||
initialValue: s.tratamiento,
|
||||
hintText: 'Plan terapéutico, recetas, dosis y recomendaciones...',
|
||||
onChanged: (v) => s.tratamiento = v,
|
||||
),
|
||||
|
||||
// 7. Próxima cita
|
||||
_buildProximaCitaTab(s),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,487 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:form_builder_validators/form_builder_validators.dart';
|
||||
import 'package:mask_text_input_formatter/mask_text_input_formatter.dart';
|
||||
import 'package:toggle_switch/toggle_switch.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../store/patient_store.dart';
|
||||
|
||||
class PatientDemographicsForm extends StatelessWidget {
|
||||
/// Altura estricta y compartida para todos los elementos de la ficha demográfica:
|
||||
/// los 8 campos de texto, las 3 secciones de toggle (sexo, seguro, estado civil)
|
||||
/// y el botón Guardar Paciente siempre tendrán exactamente la misma altura.
|
||||
static const double unifiedRowHeight = 46.0;
|
||||
|
||||
final PatientStore store;
|
||||
final GlobalKey<FormBuilderState> formKey;
|
||||
final VoidCallback onSave;
|
||||
|
||||
const PatientDemographicsForm({
|
||||
super.key,
|
||||
required this.store,
|
||||
required this.formKey,
|
||||
required this.onSave,
|
||||
});
|
||||
|
||||
InputDecoration _cleanDecoration({
|
||||
required String label,
|
||||
IconData? icon,
|
||||
bool isRequired = false,
|
||||
String? hintText,
|
||||
bool readOnly = false,
|
||||
}) {
|
||||
return InputDecoration(
|
||||
labelText: isRequired ? '$label *' : label,
|
||||
hintText: hintText,
|
||||
isDense: true,
|
||||
floatingLabelBehavior: FloatingLabelBehavior.auto,
|
||||
filled: true,
|
||||
fillColor: readOnly ? const Color(0xFFF1F5F9) : Colors.white,
|
||||
prefixIcon: icon != null
|
||||
? Icon(icon, size: 16, color: isRequired ? AppColors.primary : AppColors.textMuted)
|
||||
: null,
|
||||
prefixIconConstraints: const BoxConstraints(minWidth: 34, minHeight: 34),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 11, vertical: 14),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: const BorderSide(color: Color(0xFFE2E8F0)),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: const BorderSide(color: Color(0xFFE2E8F0)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: const BorderSide(color: AppColors.primary, width: 1.5),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: const BorderSide(color: AppColors.error),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
borderSide: const BorderSide(color: AppColors.error, width: 1.5),
|
||||
),
|
||||
labelStyle: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: isRequired ? AppColors.primary : AppColors.textSecondary,
|
||||
),
|
||||
hintStyle: const TextStyle(fontSize: 11.5, color: Color(0xFF94A3B8)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCompactSelector({
|
||||
required BuildContext context,
|
||||
required String title,
|
||||
required IconData titleIcon,
|
||||
required Widget child,
|
||||
bool isRequired = false,
|
||||
}) {
|
||||
return Container(
|
||||
height: unifiedRowHeight,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF8FAFC),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(titleIcon, size: 14, color: isRequired ? AppColors.primary : AppColors.textMuted),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
isRequired ? '$title *' : title,
|
||||
style: TextStyle(
|
||||
fontSize: 11.0,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: isRequired ? AppColors.primary : AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Flexible(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = store;
|
||||
|
||||
final cedulaFormatter = MaskTextInputFormatter(
|
||||
mask: '###-#######-#',
|
||||
filter: {'#': RegExp(r'[0-9]')},
|
||||
);
|
||||
|
||||
final telefonoFormatter = MaskTextInputFormatter(
|
||||
mask: '###-###-####',
|
||||
filter: {'#': RegExp(r'[0-9]')},
|
||||
);
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border(
|
||||
bottom: BorderSide(color: Color(0xFFE2E8F0), width: 1.2),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Sub-bloque 1 (Columna 1): Fecha de reg, Nombre, Dirección, Ocupación
|
||||
Expanded(
|
||||
flex: 7,
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: unifiedRowHeight,
|
||||
child: FormBuilderTextField(
|
||||
name: 'fec_reg',
|
||||
initialValue: s.fecReg,
|
||||
readOnly: true,
|
||||
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
|
||||
decoration: _cleanDecoration(
|
||||
label: 'Fecha de Registro',
|
||||
icon: Icons.calendar_today_rounded,
|
||||
readOnly: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: unifiedRowHeight,
|
||||
child: FormBuilderTextField(
|
||||
name: 'nombre',
|
||||
initialValue: s.nombre,
|
||||
textCapitalization: TextCapitalization.words,
|
||||
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
|
||||
decoration: _cleanDecoration(
|
||||
label: 'Nombre',
|
||||
icon: Icons.person_outline_rounded,
|
||||
isRequired: true,
|
||||
hintText: 'Ej. Juan Alberto',
|
||||
),
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(errorText: 'Requerido'),
|
||||
FormBuilderValidators.minLength(2, errorText: 'Mínimo 2 letras'),
|
||||
]),
|
||||
onChanged: (v) => s.nombre = v ?? '',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: unifiedRowHeight,
|
||||
child: FormBuilderTextField(
|
||||
name: 'direccion',
|
||||
initialValue: s.direccion,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
decoration: _cleanDecoration(
|
||||
label: 'Dirección',
|
||||
icon: Icons.location_on_outlined,
|
||||
hintText: 'Calle, número o sector',
|
||||
),
|
||||
onChanged: (v) => s.direccion = v ?? '',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: unifiedRowHeight,
|
||||
child: FormBuilderTextField(
|
||||
name: 'ocupacion',
|
||||
initialValue: s.ocupacion,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
decoration: _cleanDecoration(
|
||||
label: 'Ocupación',
|
||||
icon: Icons.work_outline_rounded,
|
||||
hintText: 'Profesión u oficio',
|
||||
),
|
||||
onChanged: (v) => s.ocupacion = v ?? '',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// Sub-bloque 2 (Columna 2): Cédula o Pasaporte, Apellido, Teléfono, Edad
|
||||
Expanded(
|
||||
flex: 7,
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: unifiedRowHeight,
|
||||
child: FormBuilderTextField(
|
||||
name: 'cedula',
|
||||
initialValue: s.cedula,
|
||||
inputFormatters: [cedulaFormatter],
|
||||
keyboardType: TextInputType.number,
|
||||
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
|
||||
decoration: _cleanDecoration(
|
||||
label: 'Cédula o Pasaporte',
|
||||
icon: Icons.badge_outlined,
|
||||
hintText: '001-0000000-0',
|
||||
),
|
||||
onChanged: (v) => s.cedula = v ?? '',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: unifiedRowHeight,
|
||||
child: FormBuilderTextField(
|
||||
name: 'apellido',
|
||||
initialValue: s.apellido,
|
||||
textCapitalization: TextCapitalization.words,
|
||||
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
|
||||
decoration: _cleanDecoration(
|
||||
label: 'Apellido',
|
||||
icon: Icons.badge_rounded,
|
||||
isRequired: true,
|
||||
hintText: 'Ej. Pérez Gómez',
|
||||
),
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(errorText: 'Requerido'),
|
||||
FormBuilderValidators.minLength(2, errorText: 'Mínimo 2 letras'),
|
||||
]),
|
||||
onChanged: (v) => s.apellido = v ?? '',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: unifiedRowHeight,
|
||||
child: FormBuilderTextField(
|
||||
name: 'telefono',
|
||||
initialValue: s.telefono,
|
||||
inputFormatters: [telefonoFormatter],
|
||||
keyboardType: TextInputType.phone,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
decoration: _cleanDecoration(
|
||||
label: 'Teléfono',
|
||||
icon: Icons.phone_outlined,
|
||||
hintText: '809-555-0101',
|
||||
),
|
||||
onChanged: (v) => s.telefono = v ?? '',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: unifiedRowHeight,
|
||||
child: FormBuilderTextField(
|
||||
name: 'edad',
|
||||
initialValue: s.edad,
|
||||
keyboardType: TextInputType.number,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
decoration: _cleanDecoration(
|
||||
label: 'Edad',
|
||||
icon: Icons.cake_outlined,
|
||||
hintText: 'Años',
|
||||
),
|
||||
onChanged: (v) => s.edad = v ?? '',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// Sub-bloque 3 (Columna 3): Sexo, Asegurado, Estado Civil, Botón Guardar
|
||||
Expanded(
|
||||
flex: 10,
|
||||
child: Column(
|
||||
children: [
|
||||
_buildCompactSelector(
|
||||
context: context,
|
||||
title: 'Sexo',
|
||||
titleIcon: Icons.wc_rounded,
|
||||
isRequired: true,
|
||||
child: FormBuilderField<String>(
|
||||
name: 'sexo',
|
||||
initialValue: s.sexo,
|
||||
validator: FormBuilderValidators.required(errorText: 'Requerido'),
|
||||
builder: (FormFieldState<String> field) {
|
||||
final currentVal = field.value ?? s.sexo;
|
||||
final initialIndex = currentVal.toLowerCase().contains('fem') ? 1 : 0;
|
||||
return ToggleSwitch(
|
||||
key: ValueKey('sexo_$currentVal'),
|
||||
customWidths: const [108.0, 108.0],
|
||||
minHeight: 28.0,
|
||||
cornerRadius: 6.0,
|
||||
radiusStyle: true,
|
||||
fontSize: 10.5,
|
||||
iconSize: 13.0,
|
||||
centerText: true,
|
||||
initialLabelIndex: initialIndex,
|
||||
totalSwitches: 2,
|
||||
labels: const ['Masculino', 'Femenino'],
|
||||
icons: const [Icons.male_rounded, Icons.female_rounded],
|
||||
activeBgColor: const [AppColors.primary],
|
||||
activeFgColor: Colors.white,
|
||||
inactiveBgColor: const Color(0xFFE2E8F0),
|
||||
inactiveFgColor: AppColors.textSecondary,
|
||||
animate: true,
|
||||
curve: Curves.easeInOut,
|
||||
onToggle: (index) {
|
||||
final val = (index == 1) ? 'Femenino' : 'Masculino';
|
||||
field.didChange(val);
|
||||
s.sexo = val;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildCompactSelector(
|
||||
context: context,
|
||||
title: 'Seguro',
|
||||
titleIcon: Icons.health_and_safety_outlined,
|
||||
isRequired: true,
|
||||
child: FormBuilderField<String>(
|
||||
name: 'seguro',
|
||||
initialValue: s.seguro,
|
||||
validator: FormBuilderValidators.required(errorText: 'Requerido'),
|
||||
builder: (FormFieldState<String> field) {
|
||||
final currentVal = field.value ?? s.seguro;
|
||||
final cleanVal = currentVal.trim().toLowerCase();
|
||||
final isNo = cleanVal == 'no' || cleanVal.contains('sin seguro') || cleanVal.contains('no asegurado');
|
||||
final initialIndex = isNo ? 1 : 0;
|
||||
return ToggleSwitch(
|
||||
key: ValueKey('seguro_$currentVal'),
|
||||
customWidths: const [116.0, 116.0],
|
||||
minHeight: 28.0,
|
||||
cornerRadius: 6.0,
|
||||
radiusStyle: true,
|
||||
fontSize: 10.5,
|
||||
iconSize: 12.0,
|
||||
centerText: true,
|
||||
initialLabelIndex: initialIndex,
|
||||
totalSwitches: 2,
|
||||
labels: const ['Con Seguro', 'Sin Seguro'],
|
||||
icons: const [Icons.check_rounded, Icons.close_rounded],
|
||||
activeBgColor: const [AppColors.primary],
|
||||
activeFgColor: Colors.white,
|
||||
inactiveBgColor: const Color(0xFFE2E8F0),
|
||||
inactiveFgColor: AppColors.textSecondary,
|
||||
animate: true,
|
||||
curve: Curves.easeInOut,
|
||||
onToggle: (index) {
|
||||
final val = (index == 1) ? 'Sin Seguro' : 'Con Seguro';
|
||||
field.didChange(val);
|
||||
s.seguro = val;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_buildCompactSelector(
|
||||
context: context,
|
||||
title: 'Estado Civil',
|
||||
titleIcon: Icons.favorite_border_rounded,
|
||||
child: FormBuilderField<String>(
|
||||
name: 'est_civil',
|
||||
initialValue: s.estCivil,
|
||||
builder: (FormFieldState<String> field) {
|
||||
const options = ['Casado', 'Soltero', 'Otros'];
|
||||
final currentVal = field.value ?? s.estCivil;
|
||||
int initialIndex = 1;
|
||||
if (currentVal.toLowerCase().contains('casad')) {
|
||||
initialIndex = 0;
|
||||
} else if (currentVal.toLowerCase().contains('otro')) {
|
||||
initialIndex = 2;
|
||||
}
|
||||
return ToggleSwitch(
|
||||
key: ValueKey('est_civil_$currentVal'),
|
||||
customWidths: const [86.0, 90.0, 76.0],
|
||||
minHeight: 28.0,
|
||||
cornerRadius: 6.0,
|
||||
radiusStyle: true,
|
||||
fontSize: 10.5,
|
||||
iconSize: 11.5,
|
||||
centerText: true,
|
||||
initialLabelIndex: initialIndex,
|
||||
totalSwitches: 3,
|
||||
labels: options,
|
||||
icons: const [Icons.people_alt_outlined, Icons.person_outline_rounded, Icons.more_horiz_rounded],
|
||||
activeBgColor: const [AppColors.primary],
|
||||
activeFgColor: Colors.white,
|
||||
inactiveBgColor: const Color(0xFFE2E8F0),
|
||||
inactiveFgColor: AppColors.textSecondary,
|
||||
animate: true,
|
||||
curve: Curves.easeInOut,
|
||||
onToggle: (index) {
|
||||
final selected = options[index ?? 1];
|
||||
field.didChange(selected);
|
||||
s.estCivil = selected;
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: unifiedRowHeight,
|
||||
width: double.infinity,
|
||||
child: Observer(
|
||||
builder: (_) => ElevatedButton(
|
||||
onPressed: s.isSaving ? null : onSave,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF00A65A),
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 1,
|
||||
shadowColor: const Color(0xFF00A65A).withValues(alpha: 0.35),
|
||||
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
child: s.isSaving
|
||||
? const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.check_circle_outline_rounded, size: 16),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
s.estadoEspera == 'en_consulta'
|
||||
? 'Finalizar Consulta'
|
||||
: (s.isEditing ? 'Guardar Cambios' : 'Guardar Paciente'),
|
||||
style: const TextStyle(
|
||||
fontSize: 12.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.2,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../store/patient_store.dart';
|
||||
|
||||
class SecretaryAppointmentSection extends StatelessWidget {
|
||||
final PatientStore store;
|
||||
|
||||
const SecretaryAppointmentSection({
|
||||
super.key,
|
||||
required this.store,
|
||||
});
|
||||
|
||||
void _setDate(DateTime date) {
|
||||
store.fecProxcita = DateFormat('dd/MM/yyyy').format(date);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final now = DateTime.now();
|
||||
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.02),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 1. Título de la sección
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF00A65A).withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.event_available_rounded,
|
||||
color: Color(0xFF00A65A),
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Gestión de Citas y Turno del Paciente',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 2),
|
||||
Text(
|
||||
'Coordine la próxima visita o turno del paciente en el consultorio',
|
||||
style: TextStyle(fontSize: 12, color: AppColors.textMuted),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
|
||||
// 2. Estado actual de la cita
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF8FAFC),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: const Color(0xFFE2E8F0)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
store.fecProxcita.isNotEmpty
|
||||
? Icons.check_circle_rounded
|
||||
: Icons.info_outline_rounded,
|
||||
color: store.fecProxcita.isNotEmpty
|
||||
? const Color(0xFF00A65A)
|
||||
: AppColors.textMuted,
|
||||
size: 22,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
store.fecProxcita.isNotEmpty
|
||||
? 'Próxima Cita Programada: ${store.fecProxcita}'
|
||||
: 'Sin fecha de cita programada',
|
||||
style: TextStyle(
|
||||
fontSize: 13.5,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: store.fecProxcita.isNotEmpty
|
||||
? AppColors.textPrimary
|
||||
: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
if (store.diasParaProximaCita != null) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
store.diasParaProximaCita! < 0
|
||||
? 'La cita programada venció hace ${store.diasParaProximaCita!.abs()} días.'
|
||||
: (store.diasParaProximaCita! == 0
|
||||
? '¡Cita pautada para el día de HOY!'
|
||||
: 'Faltan ${store.diasParaProximaCita} días para la cita.'),
|
||||
style: TextStyle(
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: store.diasParaProximaCita! < 0
|
||||
? AppColors.error
|
||||
: (store.diasParaProximaCita! == 0
|
||||
? const Color(0xFFE65100)
|
||||
: const Color(0xFF00A65A)),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
if (store.fecProxcita.isNotEmpty)
|
||||
TextButton.icon(
|
||||
onPressed: () => store.fecProxcita = '',
|
||||
icon: const Icon(Icons.clear_rounded, size: 16, color: AppColors.error),
|
||||
label: const Text(
|
||||
'Quitar Cita',
|
||||
style: TextStyle(color: AppColors.error, fontSize: 12, fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// 3. Botones para selección de fecha
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
ElevatedButton.icon(
|
||||
onPressed: () async {
|
||||
final picked = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: now.add(const Duration(days: 7)),
|
||||
firstDate: now.subtract(const Duration(days: 30)),
|
||||
lastDate: now.add(const Duration(days: 365 * 3)),
|
||||
builder: (context, child) {
|
||||
return Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: Color(0xFF00A65A),
|
||||
onPrimary: Colors.white,
|
||||
surface: Colors.white,
|
||||
),
|
||||
),
|
||||
child: child!,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (picked != null) {
|
||||
_setDate(picked);
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.calendar_month_rounded, size: 16),
|
||||
label: const Text('Elegir en Calendario', style: TextStyle(fontWeight: FontWeight.w600, fontSize: 12.5)),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF00A65A),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
elevation: 0,
|
||||
),
|
||||
),
|
||||
OutlinedButton(
|
||||
onPressed: () => _setDate(now),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
side: const BorderSide(color: Color(0xFFCBD5E1)),
|
||||
),
|
||||
child: const Text('Para Hoy', style: TextStyle(fontSize: 12, color: AppColors.textPrimary)),
|
||||
),
|
||||
OutlinedButton(
|
||||
onPressed: () => _setDate(now.add(const Duration(days: 1))),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
side: const BorderSide(color: Color(0xFFCBD5E1)),
|
||||
),
|
||||
child: const Text('Mañana', style: TextStyle(fontSize: 12, color: AppColors.textPrimary)),
|
||||
),
|
||||
OutlinedButton(
|
||||
onPressed: () => _setDate(now.add(const Duration(days: 7))),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
side: const BorderSide(color: Color(0xFFCBD5E1)),
|
||||
),
|
||||
child: const Text('En 7 días', style: TextStyle(fontSize: 12, color: AppColors.textPrimary)),
|
||||
),
|
||||
OutlinedButton(
|
||||
onPressed: () => _setDate(now.add(const Duration(days: 15))),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
side: const BorderSide(color: Color(0xFFCBD5E1)),
|
||||
),
|
||||
child: const Text('En 15 días', style: TextStyle(fontSize: 12, color: AppColors.textPrimary)),
|
||||
),
|
||||
OutlinedButton(
|
||||
onPressed: () => _setDate(now.add(const Duration(days: 30))),
|
||||
style: OutlinedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
side: const BorderSide(color: Color(0xFFCBD5E1)),
|
||||
),
|
||||
child: const Text('En 1 mes', style: TextStyle(fontSize: 12, color: AppColors.textPrimary)),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
|
||||
// 4. Nota de Confidencialidad
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFEFF6FF),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: const Color(0xFFBFDBFE)),
|
||||
),
|
||||
child: const Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Icons.shield_outlined, color: Color(0xFF2563EB), size: 18),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Privacidad de Datos: Los diagnósticos, notas de consulta y prescripciones médicas son de gestión exclusiva del médico tratante y se mantienen protegidos.',
|
||||
style: TextStyle(
|
||||
fontSize: 11.5,
|
||||
color: Color(0xFF1E40AF),
|
||||
height: 1.35,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user