Files

293 lines
7.3 KiB
Dart

import 'package:flutter/material.dart';
/// Widget réutilisable pour afficher une carte de statistique
///
/// Composant générique utilisé dans tous les dashboards pour afficher
/// des métriques avec icône, valeur, titre et sous-titre.
class StatCard extends StatelessWidget {
/// Titre principal de la statistique
final String title;
/// Valeur numérique ou textuelle à afficher
final String value;
/// Sous-titre ou description complémentaire
final String subtitle;
/// Icône représentative de la métrique
final IconData icon;
/// Couleur thématique de la carte
final Color color;
/// Callback optionnel lors du tap sur la carte
final VoidCallback? onTap;
/// Taille de la carte (compact, normal, large)
final StatCardSize size;
/// Style de la carte (minimal, elevated, outlined)
final StatCardStyle style;
const StatCard({
super.key,
required this.title,
required this.value,
required this.subtitle,
required this.icon,
required this.color,
this.onTap,
this.size = StatCardSize.normal,
this.style = StatCardStyle.elevated,
});
/// Constructeur pour une carte KPI simplifiée
const StatCard.kpi({
super.key,
required this.title,
required this.value,
required this.subtitle,
required this.icon,
required this.color,
this.onTap,
}) : size = StatCardSize.compact,
style = StatCardStyle.elevated;
/// Constructeur pour une carte de métrique système
const StatCard.metric({
super.key,
required this.title,
required this.value,
required this.subtitle,
required this.icon,
required this.color,
this.onTap,
}) : size = StatCardSize.normal,
style = StatCardStyle.minimal;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: _getPadding(),
decoration: _getDecoration(),
child: _buildContent(),
),
);
}
/// Contenu principal de la carte
Widget _buildContent() {
switch (size) {
case StatCardSize.compact:
return _buildCompactContent();
case StatCardSize.normal:
return _buildNormalContent();
case StatCardSize.large:
return _buildLargeContent();
}
}
/// Contenu compact pour les KPIs
Widget _buildCompactContent() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(icon, color: color, size: 20),
const Spacer(),
Text(
value,
style: TextStyle(
fontWeight: FontWeight.bold,
color: color,
fontSize: 18,
),
),
],
),
const SizedBox(height: 4),
Text(
title,
style: const TextStyle(
fontWeight: FontWeight.w600,
color: Colors.black87,
fontSize: 12,
),
),
Text(
subtitle,
style: const TextStyle(
color: Colors.grey,
fontSize: 10,
),
),
],
);
}
/// Contenu normal pour les métriques
Widget _buildNormalContent() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, color: color, size: 20),
),
const Spacer(),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
value,
style: TextStyle(
fontWeight: FontWeight.bold,
color: color,
fontSize: 20,
),
),
if (subtitle.isNotEmpty)
Text(
subtitle,
style: TextStyle(
color: Colors.grey[600],
fontSize: 10,
),
),
],
),
],
),
const SizedBox(height: 12),
Text(
title,
style: const TextStyle(
fontWeight: FontWeight.w600,
color: Color(0xFF1F2937),
fontSize: 14,
),
),
],
);
}
/// Contenu large pour les dashboards principaux
Widget _buildLargeContent() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 24),
),
const Spacer(),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
value,
style: TextStyle(
fontWeight: FontWeight.bold,
color: color,
fontSize: 24,
),
),
if (subtitle.isNotEmpty)
Text(
subtitle,
style: TextStyle(
color: Colors.grey[600],
fontSize: 12,
),
),
],
),
],
),
const SizedBox(height: 16),
Text(
title,
style: const TextStyle(
fontWeight: FontWeight.w600,
color: Color(0xFF1F2937),
fontSize: 16,
),
),
],
);
}
/// Padding selon la taille
EdgeInsets _getPadding() {
switch (size) {
case StatCardSize.compact:
return const EdgeInsets.all(8);
case StatCardSize.normal:
return const EdgeInsets.all(12);
case StatCardSize.large:
return const EdgeInsets.all(16);
}
}
/// Décoration selon le style
BoxDecoration _getDecoration() {
switch (style) {
case StatCardStyle.minimal:
return BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
);
case StatCardStyle.elevated:
return BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
);
case StatCardStyle.outlined:
return BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: color.withOpacity(0.2),
width: 1,
),
);
}
}
}
/// Énumération des tailles de carte
enum StatCardSize {
compact,
normal,
large,
}
/// Énumération des styles de carte
enum StatCardStyle {
minimal,
elevated,
outlined,
}