first commit
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../../data/models/dashboard_metrics_model.dart';
|
||||
|
||||
class DashboardMetricsCards extends StatelessWidget {
|
||||
final DashboardMetricsModel metrics;
|
||||
|
||||
const DashboardMetricsCards({super.key, required this.metrics});
|
||||
|
||||
String _formatNumber(int n) {
|
||||
return n.toString().replaceAllMapped(
|
||||
RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
|
||||
(Match m) => '${m[1]},',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cards = [
|
||||
// 1. Total de Nuevos Pacientes
|
||||
_buildMetricCard(
|
||||
title: 'Total Nuevos Pacientes',
|
||||
value: _formatNumber(metrics.nuevosPacientes),
|
||||
subtitle: 'Registrados en la plataforma',
|
||||
icon: Icons.person_add_alt_1_rounded,
|
||||
iconColor: AppColors.primary,
|
||||
bgColor: AppColors.primaryContainer,
|
||||
),
|
||||
|
||||
// 2. Total de Consultados Hoy
|
||||
_buildMetricCard(
|
||||
title: 'Total Consultados Hoy',
|
||||
value: _formatNumber(metrics.consultadosHoy),
|
||||
subtitle: 'Atenciones médicas realizadas',
|
||||
icon: Icons.assignment_turned_in_rounded,
|
||||
iconColor: AppColors.teal,
|
||||
bgColor: AppColors.teal.withValues(alpha: 0.12),
|
||||
),
|
||||
|
||||
// 3. Total de Citas para Hoy
|
||||
_buildMetricCard(
|
||||
title: 'Total Citas para Hoy',
|
||||
value: _formatNumber(metrics.citasHoy),
|
||||
subtitle: 'Pacientes agendados',
|
||||
icon: Icons.event_available_rounded,
|
||||
iconColor: AppColors.warning,
|
||||
bgColor: AppColors.warning.withValues(alpha: 0.12),
|
||||
),
|
||||
|
||||
// 4. Total de Pacientes por Sexo
|
||||
_buildGenderCard(),
|
||||
];
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
const spacing = 16.0;
|
||||
|
||||
// Distribución equitativa en 4 columnas (pantallas amplias)
|
||||
if (constraints.maxWidth >= 1000) {
|
||||
return IntrinsicHeight(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(child: cards[0]),
|
||||
const SizedBox(width: spacing),
|
||||
Expanded(child: cards[1]),
|
||||
const SizedBox(width: spacing),
|
||||
Expanded(child: cards[2]),
|
||||
const SizedBox(width: spacing),
|
||||
Expanded(child: cards[3]),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Distribución equitativa en 2 columnas (pantallas medianas)
|
||||
if (constraints.maxWidth >= 550) {
|
||||
return Column(
|
||||
children: [
|
||||
IntrinsicHeight(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(child: cards[0]),
|
||||
const SizedBox(width: spacing),
|
||||
Expanded(child: cards[1]),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: spacing),
|
||||
IntrinsicHeight(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(child: cards[2]),
|
||||
const SizedBox(width: spacing),
|
||||
Expanded(child: cards[3]),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 1 columna ocupando 100% del ancho (pantallas móviles / compactas)
|
||||
return Column(
|
||||
children: [
|
||||
cards[0],
|
||||
const SizedBox(height: spacing),
|
||||
cards[1],
|
||||
const SizedBox(height: spacing),
|
||||
cards[2],
|
||||
const SizedBox(height: spacing),
|
||||
cards[3],
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMetricCard({
|
||||
required String title,
|
||||
String? value,
|
||||
Widget? valueWidget,
|
||||
required String subtitle,
|
||||
required IconData icon,
|
||||
required Color iconColor,
|
||||
required Color bgColor,
|
||||
}) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: AppColors.border),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.02),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(icon, color: iconColor, size: 24),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
valueWidget ??
|
||||
Text(
|
||||
value ?? '',
|
||||
style: const TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
subtitle,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 11,
|
||||
color: AppColors.textMuted,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget _buildGenderCard() {
|
||||
final total = metrics.pacientesMasculinos + metrics.pacientesFemeninos;
|
||||
final mascPct = total > 0 ? ((metrics.pacientesMasculinos / total) * 100).toInt() : 81;
|
||||
final femPct = total > 0 ? ((metrics.pacientesFemeninos / total) * 100).toInt() : 19;
|
||||
|
||||
return _buildMetricCard(
|
||||
title: 'Pacientes por Sexo',
|
||||
valueWidget: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.baseline,
|
||||
textBaseline: TextBaseline.alphabetic,
|
||||
children: [
|
||||
Text(
|
||||
_formatNumber(metrics.pacientesMasculinos),
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
' M',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text(
|
||||
'/',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.border,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
_formatNumber(metrics.pacientesFemeninos),
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Color(0xFFEC4899),
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
' F',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Color(0xFFEC4899),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
subtitle: '$mascPct% Masc · $femPct% Fem',
|
||||
icon: Icons.people_alt_rounded,
|
||||
iconColor: AppColors.secondary,
|
||||
bgColor: AppColors.secondary.withValues(alpha: 0.12),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user