chitfund/luckychit/lib/shared/widgets/payment_charts.dart

519 lines
17 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:fl_chart/fl_chart.dart';
/// Bar chart for monthly payment overview
class MonthlyPaymentChart extends StatelessWidget {
final List<PaymentData> data;
final Color barColor;
const MonthlyPaymentChart({
super.key,
required this.data,
this.barColor = const Color(0xFF2E7D32),
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: EdgeInsets.all(20.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Monthly Payments',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 20.h),
SizedBox(
height: 200.h,
child: BarChart(
BarChartData(
alignment: BarChartAlignment.spaceAround,
maxY: data.isNotEmpty
? data.map((e) => e.amount).reduce((a, b) => a > b ? a : b) * 1.2
: 100,
barTouchData: BarTouchData(
enabled: true,
touchTooltipData: BarTouchTooltipData(
getTooltipItem: (group, groupIndex, rod, rodIndex) {
return BarTooltipItem(
'${rod.toY.toStringAsFixed(0)}',
TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 14.sp,
),
);
},
),
),
titlesData: FlTitlesData(
show: true,
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
if (value.toInt() < 0 || value.toInt() >= data.length) {
return const SizedBox();
}
return Padding(
padding: EdgeInsets.only(top: 8.h),
child: Text(
data[value.toInt()].month,
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w500,
),
),
);
},
reservedSize: 28.h,
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
return Text(
'${(value / 1000).toStringAsFixed(0)}k',
style: TextStyle(
fontSize: 10.sp,
fontWeight: FontWeight.w500,
),
);
},
reservedSize: 42.w,
),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
gridData: FlGridData(
show: true,
drawVerticalLine: false,
horizontalInterval: 5000,
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey.shade200,
strokeWidth: 1,
);
},
),
borderData: FlBorderData(show: false),
barGroups: data.asMap().entries.map((entry) {
return BarChartGroupData(
x: entry.key,
barRods: [
BarChartRodData(
toY: entry.value.amount,
color: barColor,
width: 20.w,
borderRadius: BorderRadius.vertical(
top: Radius.circular(4.r),
),
),
],
);
}).toList(),
),
),
),
],
),
),
);
}
}
/// Pie chart for payment distribution
class PaymentDistributionChart extends StatelessWidget {
final List<PaymentCategory> categories;
const PaymentDistributionChart({
super.key,
required this.categories,
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: EdgeInsets.all(20.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Payment Distribution',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 20.h),
Row(
children: [
// Pie Chart
SizedBox(
width: 150.w,
height: 150.h,
child: PieChart(
PieChartData(
sectionsSpace: 2,
centerSpaceRadius: 40.r,
sections: categories.map((category) {
return PieChartSectionData(
value: category.percentage,
title: '${category.percentage.toStringAsFixed(0)}%',
color: category.color,
radius: 50.r,
titleStyle: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
);
}).toList(),
pieTouchData: PieTouchData(
touchCallback: (FlTouchEvent event, pieTouchResponse) {},
),
),
),
),
SizedBox(width: 24.w),
// Legend
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: categories.map((category) {
return Padding(
padding: EdgeInsets.only(bottom: 12.h),
child: Row(
children: [
Container(
width: 16.w,
height: 16.h,
decoration: BoxDecoration(
color: category.color,
borderRadius: BorderRadius.circular(4.r),
),
),
SizedBox(width: 8.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
category.label,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
),
Text(
'${category.amount.toStringAsFixed(0)}',
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey.shade600,
),
),
],
),
),
],
),
);
}).toList(),
),
),
],
),
],
),
),
);
}
}
/// Line chart for payment trends
class PaymentTrendChart extends StatelessWidget {
final List<TrendData> data;
final Color lineColor;
const PaymentTrendChart({
super.key,
required this.data,
this.lineColor = const Color(0xFF2E7D32),
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: EdgeInsets.all(20.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Payment Trends',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 20.h),
SizedBox(
height: 200.h,
child: LineChart(
LineChartData(
gridData: FlGridData(
show: true,
drawVerticalLine: false,
horizontalInterval: 5000,
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey.shade200,
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
if (value.toInt() < 0 || value.toInt() >= data.length) {
return const SizedBox();
}
return Padding(
padding: EdgeInsets.only(top: 8.h),
child: Text(
data[value.toInt()].label,
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w500,
),
),
);
},
reservedSize: 28.h,
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
return Text(
'${(value / 1000).toStringAsFixed(0)}k',
style: TextStyle(
fontSize: 10.sp,
fontWeight: FontWeight.w500,
),
);
},
reservedSize: 42.w,
),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
),
borderData: FlBorderData(show: false),
minX: 0,
maxX: (data.length - 1).toDouble(),
minY: 0,
maxY: data.isNotEmpty
? data.map((e) => e.value).reduce((a, b) => a > b ? a : b) * 1.2
: 100,
lineBarsData: [
LineChartBarData(
spots: data.asMap().entries.map((entry) {
return FlSpot(entry.key.toDouble(), entry.value.value);
}).toList(),
isCurved: true,
color: lineColor,
barWidth: 3,
isStrokeCapRound: true,
dotData: FlDotData(
show: true,
getDotPainter: (spot, percent, barData, index) {
return FlDotCirclePainter(
radius: 4,
color: lineColor,
strokeWidth: 2,
strokeColor: Colors.white,
);
},
),
belowBarData: BarAreaData(
show: true,
color: lineColor.withOpacity(0.1),
),
),
],
lineTouchData: LineTouchData(
touchTooltipData: LineTouchTooltipData(
getTooltipItems: (touchedSpots) {
return touchedSpots.map((spot) {
return LineTooltipItem(
'${spot.y.toStringAsFixed(0)}',
TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 14.sp,
),
);
}).toList();
},
),
),
),
),
),
],
),
),
);
}
}
/// Payment status overview widget
class PaymentStatusWidget extends StatelessWidget {
final int totalPayments;
final int successfulPayments;
final int pendingPayments;
final int failedPayments;
const PaymentStatusWidget({
super.key,
required this.totalPayments,
required this.successfulPayments,
required this.pendingPayments,
required this.failedPayments,
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: EdgeInsets.all(20.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Payment Status',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 20.h),
_buildStatusRow(
'Successful',
successfulPayments,
totalPayments,
Colors.green.shade600,
),
SizedBox(height: 12.h),
_buildStatusRow(
'Pending',
pendingPayments,
totalPayments,
Colors.orange.shade600,
),
SizedBox(height: 12.h),
_buildStatusRow(
'Failed',
failedPayments,
totalPayments,
Colors.red.shade600,
),
],
),
),
);
}
Widget _buildStatusRow(String label, int count, int total, Color color) {
final percentage = total > 0 ? (count / total * 100) : 0.0;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
),
Text(
'$count (${percentage.toStringAsFixed(0)}%)',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: color,
),
),
],
),
SizedBox(height: 8.h),
ClipRRect(
borderRadius: BorderRadius.circular(4.r),
child: LinearProgressIndicator(
value: percentage / 100,
backgroundColor: Colors.grey.shade200,
valueColor: AlwaysStoppedAnimation<Color>(color),
minHeight: 8.h,
),
),
],
);
}
}
// Data classes
class PaymentData {
final String month;
final double amount;
PaymentData({required this.month, required this.amount});
}
class PaymentCategory {
final String label;
final double amount;
final double percentage;
final Color color;
PaymentCategory({
required this.label,
required this.amount,
required this.percentage,
required this.color,
});
}
class TrendData {
final String label;
final double value;
TrendData({required this.label, required this.value});
}