first commit
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import '../../../../core/network/api_client.dart';
|
||||
import '../../../dashboard/data/models/doctor_info_model.dart';
|
||||
|
||||
class ConfigService {
|
||||
final ApiClient apiClient;
|
||||
|
||||
ConfigService({ApiClient? client}) : apiClient = client ?? ApiClient();
|
||||
|
||||
/// Obtiene la configuración del consultorio desde Directus.
|
||||
Future<DoctorInfoModel> getConfig() async {
|
||||
try {
|
||||
final response = await apiClient.dio.get('/items/configuracion');
|
||||
if (response.statusCode == 200 && response.data != null) {
|
||||
final data = response.data['data'];
|
||||
if (data is Map<String, dynamic>) {
|
||||
return DoctorInfoModel.fromJson(data);
|
||||
} else if (data is List && data.isNotEmpty && data.first is Map<String, dynamic>) {
|
||||
return DoctorInfoModel.fromJson(data.first as Map<String, dynamic>);
|
||||
}
|
||||
}
|
||||
return const DoctorInfoModel(
|
||||
nombreDoctor: 'Dr. Luis A. Castro Sosa',
|
||||
especialidad: 'Cirujano Urólogo',
|
||||
descripEspecialidad: '',
|
||||
descripGeneral: '',
|
||||
);
|
||||
} catch (e) {
|
||||
throw Exception('Error al cargar la configuración del consultorio: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Actualiza la configuración del consultorio en Directus mediante PATCH.
|
||||
Future<DoctorInfoModel> updateConfig(DoctorInfoModel model) async {
|
||||
try {
|
||||
final response = await apiClient.dio.patch(
|
||||
'/items/configuracion',
|
||||
data: model.toJson(includeId: false),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200 && response.data != null) {
|
||||
final data = response.data['data'];
|
||||
if (data is Map<String, dynamic>) {
|
||||
return DoctorInfoModel.fromJson(data);
|
||||
} else if (data is List && data.isNotEmpty && data.first is Map<String, dynamic>) {
|
||||
return DoctorInfoModel.fromJson(data.first as Map<String, dynamic>);
|
||||
}
|
||||
}
|
||||
return model;
|
||||
} catch (e) {
|
||||
throw Exception('Error al guardar la configuración: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import 'package:mobx/mobx.dart';
|
||||
import '../../../dashboard/data/models/doctor_info_model.dart';
|
||||
import '../../data/services/config_service.dart';
|
||||
|
||||
part 'config_store.g.dart';
|
||||
|
||||
// ignore: library_private_types_in_public_api
|
||||
class ConfigStore = _ConfigStoreBase with _$ConfigStore;
|
||||
|
||||
abstract class _ConfigStoreBase with Store {
|
||||
final ConfigService service;
|
||||
|
||||
_ConfigStoreBase({ConfigService? configService})
|
||||
: service = configService ?? ConfigService();
|
||||
|
||||
@observable
|
||||
int? id;
|
||||
|
||||
@observable
|
||||
bool isLoading = false;
|
||||
|
||||
@observable
|
||||
bool isSaving = false;
|
||||
|
||||
@observable
|
||||
String? errorMessage;
|
||||
|
||||
@observable
|
||||
String? successMessage;
|
||||
|
||||
@observable
|
||||
String nombreDoctor = '';
|
||||
|
||||
@observable
|
||||
String especialidad = '';
|
||||
|
||||
@observable
|
||||
String descripEspecialidad = '';
|
||||
|
||||
@observable
|
||||
String descripGeneral = '';
|
||||
|
||||
@computed
|
||||
bool get isValid => nombreDoctor.trim().isNotEmpty && especialidad.trim().isNotEmpty;
|
||||
|
||||
@action
|
||||
void initFromModel(DoctorInfoModel model) {
|
||||
id = model.id;
|
||||
nombreDoctor = model.nombreDoctor;
|
||||
especialidad = model.especialidad;
|
||||
descripEspecialidad = model.descripEspecialidad;
|
||||
descripGeneral = model.descripGeneral;
|
||||
errorMessage = null;
|
||||
successMessage = null;
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> loadConfig() async {
|
||||
isLoading = true;
|
||||
errorMessage = null;
|
||||
try {
|
||||
final config = await service.getConfig();
|
||||
initFromModel(config);
|
||||
} catch (e) {
|
||||
errorMessage = e.toString();
|
||||
} finally {
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
Future<DoctorInfoModel?> saveConfig() async {
|
||||
if (!isValid) {
|
||||
errorMessage = 'El nombre del doctor y la especialidad son obligatorios.';
|
||||
return null;
|
||||
}
|
||||
|
||||
isSaving = true;
|
||||
errorMessage = null;
|
||||
successMessage = null;
|
||||
|
||||
try {
|
||||
final modelToSave = DoctorInfoModel(
|
||||
id: id,
|
||||
nombreDoctor: nombreDoctor.trim(),
|
||||
especialidad: especialidad.trim(),
|
||||
descripEspecialidad: descripEspecialidad.trim(),
|
||||
descripGeneral: descripGeneral.trim(),
|
||||
);
|
||||
|
||||
final updated = await service.updateConfig(modelToSave);
|
||||
initFromModel(updated);
|
||||
successMessage = 'Configuración guardada exitosamente';
|
||||
return updated;
|
||||
} catch (e) {
|
||||
errorMessage = e.toString().replaceAll('Exception: ', '');
|
||||
return null;
|
||||
} finally {
|
||||
isSaving = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'config_store.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// StoreGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic, no_leading_underscores_for_local_identifiers
|
||||
|
||||
mixin _$ConfigStore on _ConfigStoreBase, Store {
|
||||
Computed<bool>? _$isValidComputed;
|
||||
|
||||
@override
|
||||
bool get isValid => (_$isValidComputed ??= Computed<bool>(
|
||||
() => super.isValid,
|
||||
name: '_ConfigStoreBase.isValid',
|
||||
)).value;
|
||||
|
||||
late final _$idAtom = Atom(name: '_ConfigStoreBase.id', context: context);
|
||||
|
||||
@override
|
||||
int? get id {
|
||||
_$idAtom.reportRead();
|
||||
return super.id;
|
||||
}
|
||||
|
||||
@override
|
||||
set id(int? value) {
|
||||
_$idAtom.reportWrite(value, super.id, () {
|
||||
super.id = value;
|
||||
});
|
||||
}
|
||||
|
||||
late final _$isLoadingAtom = Atom(
|
||||
name: '_ConfigStoreBase.isLoading',
|
||||
context: context,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get isLoading {
|
||||
_$isLoadingAtom.reportRead();
|
||||
return super.isLoading;
|
||||
}
|
||||
|
||||
@override
|
||||
set isLoading(bool value) {
|
||||
_$isLoadingAtom.reportWrite(value, super.isLoading, () {
|
||||
super.isLoading = value;
|
||||
});
|
||||
}
|
||||
|
||||
late final _$isSavingAtom = Atom(
|
||||
name: '_ConfigStoreBase.isSaving',
|
||||
context: context,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get isSaving {
|
||||
_$isSavingAtom.reportRead();
|
||||
return super.isSaving;
|
||||
}
|
||||
|
||||
@override
|
||||
set isSaving(bool value) {
|
||||
_$isSavingAtom.reportWrite(value, super.isSaving, () {
|
||||
super.isSaving = value;
|
||||
});
|
||||
}
|
||||
|
||||
late final _$errorMessageAtom = Atom(
|
||||
name: '_ConfigStoreBase.errorMessage',
|
||||
context: context,
|
||||
);
|
||||
|
||||
@override
|
||||
String? get errorMessage {
|
||||
_$errorMessageAtom.reportRead();
|
||||
return super.errorMessage;
|
||||
}
|
||||
|
||||
@override
|
||||
set errorMessage(String? value) {
|
||||
_$errorMessageAtom.reportWrite(value, super.errorMessage, () {
|
||||
super.errorMessage = value;
|
||||
});
|
||||
}
|
||||
|
||||
late final _$successMessageAtom = Atom(
|
||||
name: '_ConfigStoreBase.successMessage',
|
||||
context: context,
|
||||
);
|
||||
|
||||
@override
|
||||
String? get successMessage {
|
||||
_$successMessageAtom.reportRead();
|
||||
return super.successMessage;
|
||||
}
|
||||
|
||||
@override
|
||||
set successMessage(String? value) {
|
||||
_$successMessageAtom.reportWrite(value, super.successMessage, () {
|
||||
super.successMessage = value;
|
||||
});
|
||||
}
|
||||
|
||||
late final _$nombreDoctorAtom = Atom(
|
||||
name: '_ConfigStoreBase.nombreDoctor',
|
||||
context: context,
|
||||
);
|
||||
|
||||
@override
|
||||
String get nombreDoctor {
|
||||
_$nombreDoctorAtom.reportRead();
|
||||
return super.nombreDoctor;
|
||||
}
|
||||
|
||||
@override
|
||||
set nombreDoctor(String value) {
|
||||
_$nombreDoctorAtom.reportWrite(value, super.nombreDoctor, () {
|
||||
super.nombreDoctor = value;
|
||||
});
|
||||
}
|
||||
|
||||
late final _$especialidadAtom = Atom(
|
||||
name: '_ConfigStoreBase.especialidad',
|
||||
context: context,
|
||||
);
|
||||
|
||||
@override
|
||||
String get especialidad {
|
||||
_$especialidadAtom.reportRead();
|
||||
return super.especialidad;
|
||||
}
|
||||
|
||||
@override
|
||||
set especialidad(String value) {
|
||||
_$especialidadAtom.reportWrite(value, super.especialidad, () {
|
||||
super.especialidad = value;
|
||||
});
|
||||
}
|
||||
|
||||
late final _$descripEspecialidadAtom = Atom(
|
||||
name: '_ConfigStoreBase.descripEspecialidad',
|
||||
context: context,
|
||||
);
|
||||
|
||||
@override
|
||||
String get descripEspecialidad {
|
||||
_$descripEspecialidadAtom.reportRead();
|
||||
return super.descripEspecialidad;
|
||||
}
|
||||
|
||||
@override
|
||||
set descripEspecialidad(String value) {
|
||||
_$descripEspecialidadAtom.reportWrite(value, super.descripEspecialidad, () {
|
||||
super.descripEspecialidad = value;
|
||||
});
|
||||
}
|
||||
|
||||
late final _$descripGeneralAtom = Atom(
|
||||
name: '_ConfigStoreBase.descripGeneral',
|
||||
context: context,
|
||||
);
|
||||
|
||||
@override
|
||||
String get descripGeneral {
|
||||
_$descripGeneralAtom.reportRead();
|
||||
return super.descripGeneral;
|
||||
}
|
||||
|
||||
@override
|
||||
set descripGeneral(String value) {
|
||||
_$descripGeneralAtom.reportWrite(value, super.descripGeneral, () {
|
||||
super.descripGeneral = value;
|
||||
});
|
||||
}
|
||||
|
||||
late final _$loadConfigAsyncAction = AsyncAction(
|
||||
'_ConfigStoreBase.loadConfig',
|
||||
context: context,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<void> loadConfig() {
|
||||
return _$loadConfigAsyncAction.run(() => super.loadConfig());
|
||||
}
|
||||
|
||||
late final _$saveConfigAsyncAction = AsyncAction(
|
||||
'_ConfigStoreBase.saveConfig',
|
||||
context: context,
|
||||
);
|
||||
|
||||
@override
|
||||
Future<DoctorInfoModel?> saveConfig() {
|
||||
return _$saveConfigAsyncAction.run(() => super.saveConfig());
|
||||
}
|
||||
|
||||
late final _$_ConfigStoreBaseActionController = ActionController(
|
||||
name: '_ConfigStoreBase',
|
||||
context: context,
|
||||
);
|
||||
|
||||
@override
|
||||
void initFromModel(DoctorInfoModel model) {
|
||||
final _$actionInfo = _$_ConfigStoreBaseActionController.startAction(
|
||||
name: '_ConfigStoreBase.initFromModel',
|
||||
);
|
||||
try {
|
||||
return super.initFromModel(model);
|
||||
} finally {
|
||||
_$_ConfigStoreBaseActionController.endAction(_$actionInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '''
|
||||
id: ${id},
|
||||
isLoading: ${isLoading},
|
||||
isSaving: ${isSaving},
|
||||
errorMessage: ${errorMessage},
|
||||
successMessage: ${successMessage},
|
||||
nombreDoctor: ${nombreDoctor},
|
||||
especialidad: ${especialidad},
|
||||
descripEspecialidad: ${descripEspecialidad},
|
||||
descripGeneral: ${descripGeneral},
|
||||
isValid: ${isValid}
|
||||
''';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,391 @@
|
||||
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 '../../../../core/theme/app_colors.dart';
|
||||
import '../../../../core/widgets/medical_modal.dart';
|
||||
import '../../../dashboard/data/models/doctor_info_model.dart';
|
||||
import '../store/config_store.dart';
|
||||
|
||||
class ConfigModalView extends StatefulWidget {
|
||||
final ValueChanged<DoctorInfoModel>? onSaved;
|
||||
final ConfigStore? store;
|
||||
|
||||
const ConfigModalView({
|
||||
super.key,
|
||||
this.onSaved,
|
||||
this.store,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ConfigModalView> createState() => _ConfigModalViewState();
|
||||
}
|
||||
|
||||
class _ConfigModalViewState extends State<ConfigModalView> {
|
||||
late final ConfigStore _store;
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_store = widget.store ?? ConfigStore();
|
||||
if (widget.store == null) {
|
||||
_store.loadConfig();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleSave() async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
final values = _formKey.currentState!.value;
|
||||
if (values['nombre_doctor'] != null) {
|
||||
_store.nombreDoctor = values['nombre_doctor'].toString();
|
||||
}
|
||||
if (values['especialidad'] != null) {
|
||||
_store.especialidad = values['especialidad'].toString();
|
||||
}
|
||||
if (values['descrip_especialidad'] != null) {
|
||||
_store.descripEspecialidad = values['descrip_especialidad'].toString();
|
||||
}
|
||||
if (values['descrip_general'] != null) {
|
||||
_store.descripGeneral = values['descrip_general'].toString();
|
||||
}
|
||||
|
||||
final updated = await _store.saveConfig();
|
||||
if (updated != null && mounted) {
|
||||
widget.onSaved?.call(updated);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Row(
|
||||
children: [
|
||||
Icon(Icons.check_circle_rounded, color: Colors.white, size: 18),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text('Configuración del consultorio guardada correctamente'),
|
||||
),
|
||||
],
|
||||
),
|
||||
backgroundColor: Color(0xFF00A65A),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InputDecoration _cleanDecoration({
|
||||
required String label,
|
||||
IconData? icon,
|
||||
bool isRequired = false,
|
||||
String? hintText,
|
||||
}) {
|
||||
return InputDecoration(
|
||||
labelText: isRequired ? '$label *' : label,
|
||||
hintText: hintText,
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
isDense: true,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 13),
|
||||
prefixIcon: icon != null
|
||||
? Icon(icon, size: 18, color: isRequired ? AppColors.primary : AppColors.textMuted)
|
||||
: null,
|
||||
prefixIconConstraints: const BoxConstraints(minWidth: 38, minHeight: 38),
|
||||
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),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: const BorderSide(color: AppColors.error),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: const BorderSide(color: AppColors.error, width: 1.5),
|
||||
),
|
||||
labelStyle: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isRequired ? AppColors.primary : AppColors.textSecondary,
|
||||
),
|
||||
hintStyle: const TextStyle(fontSize: 12.5, color: Color(0xFF94A3B8)),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (_) {
|
||||
return MedicalModalDialog(
|
||||
titleIcon: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: const Icon(Icons.settings_suggest_rounded, color: AppColors.primary, size: 22),
|
||||
),
|
||||
title: 'Configuración del Consultorio',
|
||||
subtitle: const Text(
|
||||
'Actualice la información del doctor, especialidad y datos de contacto de la clínica',
|
||||
style: TextStyle(fontSize: 12, color: AppColors.textMuted),
|
||||
),
|
||||
maxWidth: 780,
|
||||
maxHeight: 660,
|
||||
bottomBar: _buildBottomBar(),
|
||||
child: _store.isLoading
|
||||
? const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(40),
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
)
|
||||
: _buildForm(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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(),
|
||||
TextButton(
|
||||
onPressed: _store.isSaving ? null : () => Navigator.of(context).pop(),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: AppColors.textSecondary,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
),
|
||||
child: const Text('Cancelar', style: TextStyle(fontWeight: FontWeight.w600)),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
ElevatedButton(
|
||||
onPressed: _store.isSaving ? null : _handleSave,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF00A65A),
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 22, vertical: 12),
|
||||
elevation: 1,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
),
|
||||
child: _store.isSaving
|
||||
? const SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
)
|
||||
: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.check_circle_outline_rounded, size: 18),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'Guardar Configuración',
|
||||
style: TextStyle(fontSize: 13, fontWeight: FontWeight.w700),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildForm() {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
|
||||
child: FormBuilder(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (_store.errorMessage != null) ...[
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.error.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: AppColors.error.withValues(alpha: 0.3)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.error_outline_rounded, color: AppColors.error, size: 20),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_store.errorMessage!,
|
||||
style: const TextStyle(fontSize: 12.5, color: AppColors.error, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
// Sección 1: Identidad Médica
|
||||
_buildSectionHeader(
|
||||
icon: Icons.person_pin_rounded,
|
||||
title: 'Identidad del Profesional',
|
||||
subtitle: 'Nombre y especialidad principal reflejados en el encabezado y recetas',
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 6,
|
||||
child: FormBuilderTextField(
|
||||
name: 'nombre_doctor',
|
||||
initialValue: _store.nombreDoctor,
|
||||
decoration: _cleanDecoration(
|
||||
label: 'Nombre del Doctor',
|
||||
icon: Icons.badge_outlined,
|
||||
isRequired: true,
|
||||
hintText: 'Ej. Dr. Luis A. Castro Sosa',
|
||||
),
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(errorText: 'Requerido'),
|
||||
FormBuilderValidators.minLength(3, errorText: 'Mínimo 3 caracteres'),
|
||||
]),
|
||||
onChanged: (v) => _store.nombreDoctor = v ?? '',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: FormBuilderTextField(
|
||||
name: 'especialidad',
|
||||
initialValue: _store.especialidad,
|
||||
decoration: _cleanDecoration(
|
||||
label: 'Especialidad',
|
||||
icon: Icons.medical_services_outlined,
|
||||
isRequired: true,
|
||||
hintText: 'Ej. Cirujano Urólogo',
|
||||
),
|
||||
validator: FormBuilderValidators.compose([
|
||||
FormBuilderValidators.required(errorText: 'Requerido'),
|
||||
FormBuilderValidators.minLength(3, errorText: 'Mínimo 3 caracteres'),
|
||||
]),
|
||||
onChanged: (v) => _store.especialidad = v ?? '',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 22),
|
||||
|
||||
// Sección 2: Áreas de Enfoque y Subespecialidades
|
||||
_buildSectionHeader(
|
||||
icon: Icons.local_hospital_outlined,
|
||||
title: 'Áreas de Enfoque y Subespecialidades',
|
||||
subtitle: 'Enfermedades, órganos o áreas de tratamiento de su consulta',
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FormBuilderTextField(
|
||||
name: 'descrip_especialidad',
|
||||
initialValue: _store.descripEspecialidad,
|
||||
maxLines: 4,
|
||||
decoration: _cleanDecoration(
|
||||
label: 'Descripción de Especialidades',
|
||||
hintText: 'Ej. Enfermedades Vías Urinarias, Riñones, Vejiga, Próstata, Disfunción eréctil...',
|
||||
),
|
||||
onChanged: (v) => _store.descripEspecialidad = v ?? '',
|
||||
),
|
||||
const SizedBox(height: 22),
|
||||
|
||||
// Sección 3: Datos de Contacto y Clínica
|
||||
_buildSectionHeader(
|
||||
icon: Icons.apartment_rounded,
|
||||
title: 'Datos del Consultorio y Contacto',
|
||||
subtitle: 'Dirección física de la clínica, edificio, teléfonos, celular y correo',
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FormBuilderTextField(
|
||||
name: 'descrip_general',
|
||||
initialValue: _store.descripGeneral,
|
||||
maxLines: 4,
|
||||
decoration: _cleanDecoration(
|
||||
label: 'Información General y Contacto',
|
||||
hintText: 'Ej. Instituto materno infantil, Av. Juan Pablo Duarte, Santiago. Tel: 809-583-2394...',
|
||||
),
|
||||
onChanged: (v) => _store.descripGeneral = v ?? '',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSectionHeader({
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
}) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(icon, size: 18, color: AppColors.primary),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
subtitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 11.5,
|
||||
color: AppColors.textMuted,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user