635 lines
20 KiB
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);
|
|
}
|
|
}
|
|
|