first commit
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
class DashboardMetricsModel {
|
||||
final int nuevosPacientes;
|
||||
final int consultadosHoy;
|
||||
final int citasHoy;
|
||||
final int pacientesMasculinos;
|
||||
final int pacientesFemeninos;
|
||||
final int totalPacientes;
|
||||
|
||||
const DashboardMetricsModel({
|
||||
this.nuevosPacientes = 0,
|
||||
this.consultadosHoy = 0,
|
||||
this.citasHoy = 0,
|
||||
this.pacientesMasculinos = 11078,
|
||||
this.pacientesFemeninos = 2634,
|
||||
this.totalPacientes = 13712,
|
||||
});
|
||||
|
||||
factory DashboardMetricsModel.fromJson(Map<String, dynamic> json) {
|
||||
return DashboardMetricsModel(
|
||||
nuevosPacientes: json['nuevos_pacientes'] as int? ?? 0,
|
||||
consultadosHoy: json['consultados_hoy'] as int? ?? 0,
|
||||
citasHoy: json['citas_hoy'] as int? ?? 0,
|
||||
pacientesMasculinos: json['pacientes_masculinos'] as int? ?? 0,
|
||||
pacientesFemeninos: json['pacientes_femeninos'] as int? ?? 0,
|
||||
totalPacientes: json['total_pacientes'] as int? ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'nuevos_pacientes': nuevosPacientes,
|
||||
'consultados_hoy': consultadosHoy,
|
||||
'citas_hoy': citasHoy,
|
||||
'pacientes_masculinos': pacientesMasculinos,
|
||||
'pacientes_femeninos': pacientesFemeninos,
|
||||
'total_pacientes': totalPacientes,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
class DoctorInfoModel {
|
||||
final int? id;
|
||||
final String nombreDoctor;
|
||||
final String especialidad;
|
||||
final String descripEspecialidad;
|
||||
final String descripGeneral;
|
||||
|
||||
const DoctorInfoModel({
|
||||
this.id,
|
||||
required this.nombreDoctor,
|
||||
required this.especialidad,
|
||||
required this.descripEspecialidad,
|
||||
required this.descripGeneral,
|
||||
});
|
||||
|
||||
factory DoctorInfoModel.fromJson(Map<String, dynamic> json) {
|
||||
final doctor = (json['nombre_doctor']?.toString() ?? 'Dr. Luis A. Castro Sosa').trim();
|
||||
final esp = (json['especialidad']?.toString() ?? 'Cirujano Urólogo').trim();
|
||||
|
||||
return DoctorInfoModel(
|
||||
id: json['id'] is int ? json['id'] as int : int.tryParse(json['id']?.toString() ?? ''),
|
||||
nombreDoctor: doctor.isEmpty ? 'Dr. Luis A. Castro Sosa' : doctor,
|
||||
especialidad: esp.isEmpty ? 'Cirujano Urólogo' : esp,
|
||||
descripEspecialidad: json['descrip_especialidad']?.toString() ?? '',
|
||||
descripGeneral: json['descrip_general']?.toString() ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson({bool includeId = false}) {
|
||||
final map = <String, dynamic>{
|
||||
'nombre_doctor': nombreDoctor,
|
||||
'especialidad': especialidad,
|
||||
'descrip_especialidad': descripEspecialidad,
|
||||
'descrip_general': descripGeneral,
|
||||
};
|
||||
if (includeId && id != null) {
|
||||
map['id'] = id;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
DoctorInfoModel copyWith({
|
||||
int? id,
|
||||
String? nombreDoctor,
|
||||
String? especialidad,
|
||||
String? descripEspecialidad,
|
||||
String? descripGeneral,
|
||||
}) {
|
||||
return DoctorInfoModel(
|
||||
id: id ?? this.id,
|
||||
nombreDoctor: nombreDoctor ?? this.nombreDoctor,
|
||||
especialidad: especialidad ?? this.especialidad,
|
||||
descripEspecialidad: descripEspecialidad ?? this.descripEspecialidad,
|
||||
descripGeneral: descripGeneral ?? this.descripGeneral,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
class PatientSummaryModel {
|
||||
final int id;
|
||||
final String nombre;
|
||||
final String apellido;
|
||||
final String cedula;
|
||||
final String telefono;
|
||||
final String fecReg;
|
||||
final String fecProxCita;
|
||||
final String seguro;
|
||||
final String motCons;
|
||||
final String estadoEspera;
|
||||
|
||||
const PatientSummaryModel({
|
||||
required this.id,
|
||||
required this.nombre,
|
||||
required this.apellido,
|
||||
required this.cedula,
|
||||
required this.telefono,
|
||||
required this.fecReg,
|
||||
required this.fecProxCita,
|
||||
required this.seguro,
|
||||
required this.motCons,
|
||||
this.estadoEspera = 'no_presente',
|
||||
});
|
||||
|
||||
String get fullName => '$nombre $apellido'.trim();
|
||||
|
||||
bool get isNoPresente => estadoEspera == 'no_presente' || estadoEspera.isEmpty;
|
||||
bool get isEnEspera => estadoEspera == 'en_espera';
|
||||
bool get isEnConsulta => estadoEspera == 'en_consulta';
|
||||
bool get isAtendido => estadoEspera == 'atendido';
|
||||
|
||||
String get estadoEsperaLabel {
|
||||
switch (estadoEspera) {
|
||||
case 'en_espera':
|
||||
return 'En Sala de Espera';
|
||||
case 'en_consulta':
|
||||
return 'En Consulta';
|
||||
case 'atendido':
|
||||
return 'Atendido';
|
||||
case 'no_presente':
|
||||
default:
|
||||
return 'No Presente';
|
||||
}
|
||||
}
|
||||
|
||||
PatientSummaryModel copyWith({
|
||||
int? id,
|
||||
String? nombre,
|
||||
String? apellido,
|
||||
String? cedula,
|
||||
String? telefono,
|
||||
String? fecReg,
|
||||
String? fecProxCita,
|
||||
String? seguro,
|
||||
String? motCons,
|
||||
String? estadoEspera,
|
||||
}) {
|
||||
return PatientSummaryModel(
|
||||
id: id ?? this.id,
|
||||
nombre: nombre ?? this.nombre,
|
||||
apellido: apellido ?? this.apellido,
|
||||
cedula: cedula ?? this.cedula,
|
||||
telefono: telefono ?? this.telefono,
|
||||
fecReg: fecReg ?? this.fecReg,
|
||||
fecProxCita: fecProxCita ?? this.fecProxCita,
|
||||
seguro: seguro ?? this.seguro,
|
||||
motCons: motCons ?? this.motCons,
|
||||
estadoEspera: estadoEspera ?? this.estadoEspera,
|
||||
);
|
||||
}
|
||||
|
||||
factory PatientSummaryModel.fromJson(Map<String, dynamic> json) {
|
||||
int parsedId = 0;
|
||||
if (json['id'] is int) {
|
||||
parsedId = json['id'] as int;
|
||||
} else if (json['id'] != null) {
|
||||
parsedId = int.tryParse(json['id'].toString()) ?? 0;
|
||||
}
|
||||
|
||||
return PatientSummaryModel(
|
||||
id: parsedId,
|
||||
nombre: json['nombre']?.toString() ?? '',
|
||||
apellido: json['apellido']?.toString() ?? '',
|
||||
cedula: json['cedula']?.toString() ?? '',
|
||||
telefono: json['telefono']?.toString() ?? '',
|
||||
fecReg: json['fec_reg']?.toString() ?? '',
|
||||
fecProxCita: json['fec_proxcita']?.toString() ?? '',
|
||||
seguro: json['seguro']?.toString() ?? '',
|
||||
motCons: json['mot_cons']?.toString() ?? '',
|
||||
estadoEspera: json['estado_espera']?.toString() ?? 'no_presente',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'nombre': nombre,
|
||||
'apellido': apellido,
|
||||
'cedula': cedula,
|
||||
'telefono': telefono,
|
||||
'fec_reg': fecReg,
|
||||
'fec_proxcita': fecProxCita,
|
||||
'seguro': seguro,
|
||||
'mot_cons': motCons,
|
||||
'estado_espera': estadoEspera,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
import '../../../../core/network/api_client.dart';
|
||||
import '../models/dashboard_metrics_model.dart';
|
||||
import '../models/doctor_info_model.dart';
|
||||
import '../models/patient_summary_model.dart';
|
||||
|
||||
class DashboardService {
|
||||
final ApiClient apiClient;
|
||||
|
||||
DashboardService({ApiClient? client}) : apiClient = client ?? ApiClient();
|
||||
|
||||
String _formatTwoDigits(int n) => n.toString().padLeft(2, '0');
|
||||
|
||||
Future<DoctorInfoModel> getDoctorConfig() async {
|
||||
try {
|
||||
final response = await apiClient.dio.get('/items/configuracion');
|
||||
if (response.statusCode == 200 && response.data != null) {
|
||||
final data = response.data['data'] as Map<String, dynamic>;
|
||||
return DoctorInfoModel.fromJson(data);
|
||||
}
|
||||
return const DoctorInfoModel(
|
||||
nombreDoctor: 'Dr. Luis A. Castro Sosa',
|
||||
especialidad: 'Cirujano Urólogo',
|
||||
descripEspecialidad: '',
|
||||
descripGeneral: '',
|
||||
);
|
||||
} catch (_) {
|
||||
return const DoctorInfoModel(
|
||||
nombreDoctor: 'Dr. Luis A. Castro Sosa',
|
||||
especialidad: 'Cirujano Urólogo',
|
||||
descripEspecialidad: '',
|
||||
descripGeneral: '',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<DashboardMetricsModel> getDashboardMetrics() async {
|
||||
try {
|
||||
final now = DateTime.now();
|
||||
final day = _formatTwoDigits(now.day);
|
||||
final month = _formatTwoDigits(now.month);
|
||||
final year = now.year.toString();
|
||||
final dmy = '$day/$month/$year';
|
||||
final iso = '$year-$month-$day';
|
||||
|
||||
// Consultas concurrentes optimizadas a Directus
|
||||
final results = await Future.wait([
|
||||
// 0: Desglose por sexo
|
||||
apiClient.dio.get('/items/pacientes?aggregate[count]=*&groupBy[]=sexo'),
|
||||
|
||||
// 1: Citas para hoy (condición estricta: fec_proxcita igual a la fecha del día actual)
|
||||
apiClient.dio.get(
|
||||
'/items/pacientes?aggregate[count]=*&filter[_or][0][fec_proxcita][_eq]=$dmy&filter[_or][1][fec_proxcita][_starts_with]=$dmy&filter[_or][2][fec_proxcita][_starts_with]=$iso',
|
||||
),
|
||||
|
||||
// 2: Pacientes nuevos registrados hoy
|
||||
apiClient.dio.get(
|
||||
'/items/pacientes?aggregate[count]=*&filter[_or][0][fec_reg][_contains]=$dmy&filter[_or][1][fec_reg][_contains]=$iso',
|
||||
),
|
||||
|
||||
// 3: Pacientes consultados hoy
|
||||
apiClient.dio.get(
|
||||
'/items/pacientes?aggregate[count]=*&filter[_or][0][fec_ultcon][_contains]=$dmy&filter[_or][1][fec_ultcon][_contains]=$iso',
|
||||
),
|
||||
|
||||
// 4: Total de pacientes
|
||||
apiClient.dio.get('/items/pacientes?aggregate[count]=*'),
|
||||
]);
|
||||
|
||||
int masc = 11078;
|
||||
int fem = 2634;
|
||||
int citasHoy = 0;
|
||||
int nuevosHoy = 0;
|
||||
int consultadosHoy = 0;
|
||||
int total = 13712;
|
||||
|
||||
// 0. Desglose por sexo
|
||||
if (results[0].statusCode == 200 && results[0].data != null) {
|
||||
final sexoData = results[0].data['data'] as List<dynamic>? ?? [];
|
||||
for (final item in sexoData) {
|
||||
final s = item['sexo']?.toString().toLowerCase() ?? '';
|
||||
final count = int.tryParse(item['count'].toString()) ?? 0;
|
||||
if (s.contains('masc')) masc = count;
|
||||
if (s.contains('fem')) fem = count;
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Citas para hoy
|
||||
if (results[1].statusCode == 200 && results[1].data != null) {
|
||||
final data = results[1].data['data'] as List<dynamic>? ?? [];
|
||||
if (data.isNotEmpty) {
|
||||
citasHoy = int.tryParse(data[0]['count'].toString()) ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Nuevos hoy
|
||||
if (results[2].statusCode == 200 && results[2].data != null) {
|
||||
final data = results[2].data['data'] as List<dynamic>? ?? [];
|
||||
if (data.isNotEmpty) {
|
||||
nuevosHoy = int.tryParse(data[0]['count'].toString()) ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Consultados hoy
|
||||
if (results[3].statusCode == 200 && results[3].data != null) {
|
||||
final data = results[3].data['data'] as List<dynamic>? ?? [];
|
||||
if (data.isNotEmpty) {
|
||||
consultadosHoy = int.tryParse(data[0]['count'].toString()) ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Total pacientes
|
||||
if (results[4].statusCode == 200 && results[4].data != null) {
|
||||
final data = results[4].data['data'] as List<dynamic>? ?? [];
|
||||
if (data.isNotEmpty) {
|
||||
total = int.tryParse(data[0]['count'].toString()) ?? 13712;
|
||||
}
|
||||
}
|
||||
|
||||
return DashboardMetricsModel(
|
||||
nuevosPacientes: nuevosHoy,
|
||||
consultadosHoy: consultadosHoy,
|
||||
citasHoy: citasHoy,
|
||||
pacientesMasculinos: masc,
|
||||
pacientesFemeninos: fem,
|
||||
totalPacientes: total,
|
||||
);
|
||||
} catch (_) {
|
||||
return const DashboardMetricsModel();
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<PatientSummaryModel>> getRecentPatients({int limit = 5}) async {
|
||||
try {
|
||||
final response = await apiClient.dio.get(
|
||||
'/items/pacientes?sort=-id&limit=$limit&fields=id,nombre,apellido,cedula,telefono,fec_reg,fec_proxcita,seguro,mot_cons,estado_espera',
|
||||
);
|
||||
if (response.statusCode == 200 && response.data != null) {
|
||||
final data = response.data['data'] as List<dynamic>? ?? [];
|
||||
return data
|
||||
.map((item) => PatientSummaryModel.fromJson(item as Map<String, dynamic>))
|
||||
.toList();
|
||||
}
|
||||
return [];
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<PatientSummaryModel>> getAppointmentsByDate(DateTime date, {int limit = 100}) async {
|
||||
try {
|
||||
final day = _formatTwoDigits(date.day);
|
||||
final month = _formatTwoDigits(date.month);
|
||||
final year = date.year.toString();
|
||||
final dmy = '$day/$month/$year';
|
||||
final iso = '$year-$month-$day';
|
||||
|
||||
final response = await apiClient.dio.get(
|
||||
'/items/pacientes?filter[_or][0][fec_proxcita][_eq]=$dmy&filter[_or][1][fec_proxcita][_starts_with]=$dmy&filter[_or][2][fec_proxcita][_starts_with]=$iso&sort=-id&limit=$limit&fields=id,nombre,apellido,cedula,telefono,fec_proxcita,mot_cons,estado_espera',
|
||||
);
|
||||
|
||||
final data = response.data?['data'] as List<dynamic>? ?? [];
|
||||
|
||||
return data
|
||||
.map((item) => PatientSummaryModel.fromJson(item as Map<String, dynamic>))
|
||||
.toList();
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<PatientSummaryModel>> getTodayAppointments({int limit = 50}) async {
|
||||
return getAppointmentsByDate(DateTime.now(), limit: limit);
|
||||
}
|
||||
|
||||
Future<bool> updatePatientWaitingStatus(int patientId, String nuevoEstado) async {
|
||||
try {
|
||||
final response = await apiClient.dio.patch(
|
||||
'/items/pacientes/$patientId',
|
||||
data: {'estado_espera': nuevoEstado},
|
||||
);
|
||||
return response.statusCode == 200;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user