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

635 lines
20 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:intl/intl.dart';
/// Enhanced monthly schedule with traditional chit fund calculations
/// Shows varying lift amounts (early lifters get less, late lifters get more)
class EnhancedMonthlyScheduleTable extends StatelessWidget {
final double totalValue; // Fixed target value
final int durationMonths;
final double monthlyContribution; // Principal amount
final double monthlyCommission; // Fee/Commission
final bool showAdvantageExplanation;
const EnhancedMonthlyScheduleTable({
super.key,
required this.totalValue,
required this.durationMonths,
required this.monthlyContribution,
required this.monthlyCommission,
this.showAdvantageExplanation = false,
});
@override
Widget build(BuildContext context) {
if (totalValue <= 0 || durationMonths <= 0) {
return _buildEmptyState();
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Explanation card
if (showAdvantageExplanation) ...[
_buildExplanationCard(),
SizedBox(height: 16.h),
],
// Schedule table
Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(12.r),
),
child: Column(
children: [
// Table header
_buildTableHeader(),
// Month rows (scrollable)
Container(
constraints: BoxConstraints(
maxHeight: 350.h,
),
child: ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.zero,
itemCount: durationMonths,
itemBuilder: (context, index) {
return _buildMonthRow(index + 1);
},
),
),
],
),
),
SizedBox(height: 16.h),
// Summary
_buildSummaryCard(),
],
);
}
Widget _buildTableHeader() {
return Container(
padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 12.w),
decoration: BoxDecoration(
color: Colors.green.shade600,
borderRadius: BorderRadius.vertical(top: Radius.circular(12.r)),
),
child: Row(
children: [
SizedBox(
width: 50.w,
child: Text(
'Month',
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
textAlign: TextAlign.center,
),
),
Expanded(
flex: 2,
child: Text(
'Payment',
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
textAlign: TextAlign.center,
),
),
Expanded(
flex: 3,
child: Text(
'Lift Amount',
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
textAlign: TextAlign.center,
),
),
],
),
);
}
Widget _buildMonthRow(int month) {
final monthlyPayment = monthlyContribution + monthlyCommission;
final liftAmount = _calculateLiftAmount(month);
final monthDate = _getMonthDate(month);
final isEarly = month <= (durationMonths * 0.3).ceil();
final isLast = month == durationMonths;
final bgColor = isLast
? Colors.purple.shade50
: isEarly
? Colors.green.shade50
: Colors.white;
return Container(
decoration: BoxDecoration(
color: bgColor,
border: Border(
bottom: BorderSide(color: Colors.grey.shade200),
),
),
padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 12.w),
child: Row(
children: [
// Month
SizedBox(
width: 50.w,
child: Column(
children: [
Text(
'$month',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: isLast
? Colors.purple.shade700
: isEarly
? Colors.green.shade700
: Colors.grey.shade800,
),
),
Text(
monthDate,
style: TextStyle(
fontSize: 9.sp,
color: Colors.grey.shade600,
),
),
],
),
),
// Payment
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'${monthlyPayment.toStringAsFixed(0)}',
style: TextStyle(
fontSize: 13.sp,
fontWeight: FontWeight.w600,
color: Colors.grey.shade800,
),
),
Text(
'Fixed',
style: TextStyle(
fontSize: 10.sp,
color: Colors.grey.shade600,
),
),
],
),
),
// Lift Amount
Expanded(
flex: 3,
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Icon(
Icons.emoji_events_rounded,
size: 14.w,
color: Colors.amber.shade700,
),
SizedBox(width: 4.w),
Text(
'${liftAmount.toStringAsFixed(0)}',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.bold,
color: Colors.green.shade700,
),
),
],
),
SizedBox(height: 2.h),
Text(
'${_getLiftPercentage(month)}% of target',
style: TextStyle(
fontSize: 10.sp,
color: isLast
? Colors.purple.shade700
: isEarly
? Colors.green.shade700
: Colors.grey.shade600,
fontWeight: FontWeight.w500,
),
),
],
),
),
],
),
);
}
/// Calculate lift amount for a specific month
/// Formula: Early months get less, late months get more
/// Based on traditional chit fund bidding system
double _calculateLiftAmount(int month) {
// Starting percentage (first month lifter gets ~87.65% of target)
const double startingPercentage = 0.8765;
// Ending percentage (last month lifter gets ~99.35% of target)
const double endingPercentage = 0.9935;
// Calculate increment per month
final double incrementPerMonth = (endingPercentage - startingPercentage) / (durationMonths - 1);
// Calculate lift amount for this month
final double percentage = startingPercentage + (incrementPerMonth * (month - 1));
final double liftAmount = totalValue * percentage;
return liftAmount;
}
String _getLiftPercentage(int month) {
final liftAmount = _calculateLiftAmount(month);
final percentage = (liftAmount / totalValue) * 100;
return percentage.toStringAsFixed(1);
}
String _getMonthDate(int monthNumber) {
final now = DateTime.now();
final futureMonth = DateTime(now.year, now.month + monthNumber - 1);
return DateFormat('MMM-yy').format(futureMonth);
}
Widget _buildExplanationCard() {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue.shade50, Colors.blue.shade100],
),
borderRadius: BorderRadius.circular(12.r),
border: Border.all(color: Colors.blue.shade200),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.lightbulb_rounded, color: Colors.blue.shade700, size: 20.w),
SizedBox(width: 8.w),
Text(
'How Chit Fund Works',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.bold,
color: Colors.blue.shade800,
),
),
],
),
SizedBox(height: 12.h),
_buildExplanationPoint(
'🏆 Early Lifters',
'Get money quickly but receive less (around 88% of target)',
Colors.green.shade700,
),
SizedBox(height: 8.h),
_buildExplanationPoint(
'⏳ Late Lifters',
'Wait longer but receive more (around 99% of target)',
Colors.purple.shade700,
),
SizedBox(height: 8.h),
_buildExplanationPoint(
'💰 Everyone Pays',
'Fixed monthly payment of ₹${(monthlyContribution + monthlyCommission).toStringAsFixed(0)}',
Colors.orange.shade700,
),
],
),
);
}
Widget _buildExplanationPoint(String title, String description, Color color) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 4.w,
height: 40.h,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(2.r),
),
),
SizedBox(width: 8.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 13.sp,
fontWeight: FontWeight.w600,
color: color,
),
),
Text(
description,
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey.shade700,
),
),
],
),
),
],
);
}
Widget _buildSummaryCard() {
final monthlyPayment = monthlyContribution + monthlyCommission;
final totalPayments = monthlyPayment * durationMonths;
final totalCommission = monthlyCommission * durationMonths;
final firstMonthLift = _calculateLiftAmount(1);
final lastMonthLift = _calculateLiftAmount(durationMonths);
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(12.r),
border: Border.all(color: Colors.orange.shade200),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.summarize_rounded, color: Colors.orange.shade700, size: 18.w),
SizedBox(width: 8.w),
Text(
'Financial Summary',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.bold,
color: Colors.orange.shade800,
),
),
],
),
SizedBox(height: 12.h),
_buildSummaryRow('Fixed Target Value', '${totalValue.toStringAsFixed(0)}'),
_buildSummaryRow('Monthly Payment (Fixed)', '${monthlyPayment.toStringAsFixed(0)}'),
_buildSummaryRow('Duration', '$durationMonths months'),
Divider(height: 16.h, color: Colors.orange.shade300),
_buildSummaryRow(
'First Month Lifter Gets',
'${firstMonthLift.toStringAsFixed(0)} (${_getLiftPercentage(1)}%)',
color: Colors.green.shade700,
),
_buildSummaryRow(
'Last Month Lifter Gets',
'${lastMonthLift.toStringAsFixed(0)} (${_getLiftPercentage(durationMonths)}%)',
color: Colors.purple.shade700,
),
Divider(height: 16.h, color: Colors.orange.shade300),
_buildSummaryRow('Total per Member', '${totalPayments.toStringAsFixed(0)}'),
_buildSummaryRow('Total Commission', '${totalCommission.toStringAsFixed(0)}'),
],
),
);
}
Widget _buildSummaryRow(String label, String value, {Color? color}) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 4.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
label,
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey.shade700,
),
),
),
Text(
value,
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w600,
color: color ?? Colors.grey.shade800,
),
),
],
),
);
}
Widget _buildEmptyState() {
return Container(
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(12.r),
border: Border.all(color: Colors.orange.shade200),
),
child: Row(
children: [
Icon(Icons.info_outline, color: Colors.orange.shade600, size: 24.w),
SizedBox(width: 12.w),
Expanded(
child: Text(
'Enter financial details to see the month-wise schedule',
style: TextStyle(
fontSize: 14.sp,
color: Colors.orange.shade700,
),
),
),
],
),
);
}
}
/// Compact version with Date | Target | Lift Amount | Payment | Fee | Total columns
class DetailedMonthlyScheduleTable extends StatelessWidget {
final double totalValue;
final int durationMonths;
final double monthlyContribution;
final double monthlyCommission;
const DetailedMonthlyScheduleTable({
super.key,
required this.totalValue,
required this.durationMonths,
required this.monthlyContribution,
required this.monthlyCommission,
});
@override
Widget build(BuildContext context) {
if (totalValue <= 0 || durationMonths <= 0) {
return const SizedBox.shrink();
}
final monthlyPayment = monthlyContribution + monthlyCommission;
return Card(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
child: DataTable(
headingRowColor: MaterialStateProperty.all(Colors.green.shade50),
columnSpacing: 20.w,
dataRowMinHeight: 50.h,
dataRowMaxHeight: 60.h,
columns: [
DataColumn(
label: Text(
'Month',
style: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.bold),
),
),
DataColumn(
label: Text(
'Date',
style: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.bold),
),
),
DataColumn(
label: Text(
'Target Value',
style: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.bold),
),
numeric: true,
),
DataColumn(
label: Text(
'Lifter Gets',
style: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.bold),
),
numeric: true,
),
DataColumn(
label: Text(
'Contribution',
style: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.bold),
),
numeric: true,
),
DataColumn(
label: Text(
'Fee',
style: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.bold),
),
numeric: true,
),
DataColumn(
label: Text(
'Total Payment',
style: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.bold),
),
numeric: true,
),
],
rows: List.generate(durationMonths, (index) {
final month = index + 1;
final liftAmount = _calculateLiftAmount(month);
final monthDate = _getMonthDate(month);
final isEarly = month <= (durationMonths * 0.3).ceil();
final isLast = month == durationMonths;
return DataRow(
color: MaterialStateProperty.all(
isLast
? Colors.purple.shade50
: isEarly
? Colors.green.shade50
: null,
),
cells: [
DataCell(Text(
'$month',
style: TextStyle(fontSize: 13.sp, fontWeight: FontWeight.w600),
)),
DataCell(Text(
monthDate,
style: TextStyle(fontSize: 12.sp),
)),
DataCell(Text(
'${totalValue.toStringAsFixed(0)}',
style: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.w500),
)),
DataCell(Text(
'${liftAmount.toStringAsFixed(0)}',
style: TextStyle(
fontSize: 13.sp,
fontWeight: FontWeight.bold,
color: Colors.green.shade700,
),
)),
DataCell(Text(
'${monthlyContribution.toStringAsFixed(0)}',
style: TextStyle(fontSize: 12.sp),
)),
DataCell(Text(
'${monthlyCommission.toStringAsFixed(0)}',
style: TextStyle(fontSize: 12.sp),
)),
DataCell(Text(
'${monthlyPayment.toStringAsFixed(0)}',
style: TextStyle(fontSize: 13.sp, fontWeight: FontWeight.w600),
)),
],
);
}),
),
),
),
);
}
double _calculateLiftAmount(int month) {
const double startingPercentage = 0.8765;
const double endingPercentage = 0.9935;
final double incrementPerMonth = (endingPercentage - startingPercentage) / (durationMonths - 1);
final double percentage = startingPercentage + (incrementPerMonth * (month - 1));
return totalValue * percentage;
}
String _getMonthDate(int monthNumber) {
final now = DateTime.now();
final futureMonth = DateTime(now.year, now.month + monthNumber - 1);
return DateFormat('MMM-yy').format(futureMonth);
}
}