first commit

This commit is contained in:
=
2026-09-21 16:33:51 -04:00
commit a899479dc9
204 changed files with 21132 additions and 0 deletions
@@ -0,0 +1,164 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.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_search_item_model.dart';
import '../store/patient_search_store.dart';
import '../widgets/search_empty_state.dart';
import '../widgets/search_input_bar.dart';
import '../widgets/search_results_list.dart';
class PatientSearchModalView extends StatefulWidget {
final ValueChanged<PatientSearchItemModel>? onPatientSelected;
final bool isSecretary;
const PatientSearchModalView({
super.key,
this.onPatientSelected,
this.isSecretary = false,
});
@override
State<PatientSearchModalView> createState() => _PatientSearchModalViewState();
}
class _PatientSearchModalViewState extends State<PatientSearchModalView> {
late final PatientSearchStore _store;
@override
void initState() {
super.initState();
_store = PatientSearchStore();
}
void _onSelectPatient(PatientSearchItemModel patient) {
if (widget.onPatientSelected != null) {
widget.onPatientSelected!(patient);
} else {
Navigator.of(context).pop(patient);
showMedicalModal(
context: context,
builder: (_) => PatientModalView(
patientId: patient.id,
isSecretary: widget.isSecretary,
),
);
}
}
@override
Widget build(BuildContext context) {
return MedicalModalDialog(
title: 'Búsqueda de Pacientes',
titleIcon: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: AppColors.primaryContainer,
borderRadius: BorderRadius.circular(10),
),
child: const Icon(
Icons.person_search_rounded,
color: AppColors.primary,
size: 20,
),
),
subtitle: Observer(
builder: (_) {
if (_store.isLoading) {
return const Text(
'Consultando registros...',
style: TextStyle(
fontSize: 12,
color: AppColors.textMuted,
),
);
}
if (_store.hasSearched) {
return Text(
'${_store.resultsCount} ${_store.resultsCount == 1 ? 'coincidencia encontrada' : 'coincidencias encontradas'}',
style: const TextStyle(
fontSize: 12,
color: AppColors.primary,
fontWeight: FontWeight.w500,
),
);
}
return const Text(
'Localice pacientes por nombre, apellido, cédula o teléfono',
style: TextStyle(
fontSize: 12,
color: AppColors.textMuted,
),
);
},
),
bottomBar: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
decoration: const BoxDecoration(
color: AppColors.cardBg,
border: Border(
top: BorderSide(color: AppColors.border),
),
),
child: const Row(
children: [
Icon(Icons.info_outline_rounded, size: 14, color: AppColors.textMuted),
SizedBox(width: 6),
Text(
'Presione Enter para buscar · Esc para cerrar',
style: TextStyle(
fontSize: 11.5,
color: AppColors.textMuted,
fontWeight: FontWeight.w500,
),
),
],
),
),
child: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 12),
child: Observer(
builder: (_) => SearchInputBar(
isLoading: _store.isLoading,
onSearch: (q) => _store.search(q),
onClear: () => _store.clear(),
),
),
),
Expanded(
child: Observer(
builder: (_) {
if (_store.isLoading) {
return const SearchEmptyState(type: SearchStateViewType.loading);
}
if (_store.errorMessage != null) {
return SearchEmptyState(
type: SearchStateViewType.error,
errorMessage: _store.errorMessage,
onRetry: () => _store.search(_store.query),
);
}
if (!_store.hasSearched) {
return const SearchEmptyState(type: SearchStateViewType.initial);
}
if (_store.results.isEmpty) {
return SearchEmptyState(
type: SearchStateViewType.empty,
query: _store.query,
);
}
return SearchResultsList(
patients: _store.results,
onPatientSelected: _onSelectPatient,
);
},
),
),
],
),
);
}
}