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

624 lines
21 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:intl/intl.dart';
import '../../core/utils/snackbar_util.dart';
/// Dynamic Chit Fund Calculator with adjustable parameters
class DynamicChitCalculator extends StatefulWidget {
final double chitValue;
final int durationMonths;
final double subscriptionAmount;
final double commissionAmount;
final ValueChanged<Map<String, dynamic>>? onCalculationsChanged;
const DynamicChitCalculator({
super.key,
required this.chitValue,
required this.durationMonths,
required this.subscriptionAmount,
required this.commissionAmount,
this.onCalculationsChanged,
});
@override
State<DynamicChitCalculator> createState() => _DynamicChitCalculatorState();
}
class _DynamicChitCalculatorState extends State<DynamicChitCalculator> {
// Adjustable parameters
double _startingPercentage = 87.65; // First month lifter gets 87.65%
double _endingPercentage = 112.35; // Last month lifter gets 112.35% (more than chit value!)
String _calculationMode = 'linear'; // linear, custom, accelerated
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Calculator controls
_buildCalculatorControls(),
SizedBox(height: 16.h),
// Month-wise table
_buildCompleteScheduleTable(),
SizedBox(height: 16.h),
// Summary & insights
_buildSummaryCard(),
],
);
}
Widget _buildCalculatorControls() {
return Card(
elevation: 0,
color: Colors.blue.shade50,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
side: BorderSide(color: Colors.blue.shade200),
),
child: ExpansionTile(
leading: Icon(Icons.tune, color: Colors.blue.shade700),
title: Text(
'Adjust Lift Amount Calculations',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: Colors.blue.shade800,
),
),
subtitle: Text(
'Customize how much lifters get each month',
style: TextStyle(fontSize: 12.sp),
),
children: [
Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Starting percentage
Text(
'First Month Lifter Gets:',
style: TextStyle(
fontSize: 13.sp,
fontWeight: FontWeight.w600,
color: Colors.grey.shade800,
),
),
SizedBox(height: 8.h),
Row(
children: [
Expanded(
child: Slider(
value: _startingPercentage,
min: 70,
max: 95,
divisions: 50,
label: '${_startingPercentage.toStringAsFixed(1)}%',
activeColor: Colors.green.shade600,
onChanged: (value) {
setState(() {
_startingPercentage = value;
_notifyChanges();
});
},
),
),
SizedBox(
width: 100.w,
child: Text(
'${_startingPercentage.toStringAsFixed(1)}% = ₹${_calculateLiftAmount(1).toStringAsFixed(0)}',
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w600,
color: Colors.green.shade700,
),
textAlign: TextAlign.right,
),
),
],
),
SizedBox(height: 16.h),
// Ending percentage
Text(
'Last Month Lifter Gets:',
style: TextStyle(
fontSize: 13.sp,
fontWeight: FontWeight.w600,
color: Colors.grey.shade800,
),
),
SizedBox(height: 8.h),
Row(
children: [
Expanded(
child: Slider(
value: _endingPercentage,
min: 95,
max: 120,
divisions: 50,
label: '${_endingPercentage.toStringAsFixed(1)}%',
activeColor: Colors.purple.shade600,
onChanged: (value) {
setState(() {
_endingPercentage = value;
_notifyChanges();
});
},
),
),
SizedBox(
width: 100.w,
child: Text(
'${_endingPercentage.toStringAsFixed(1)}% = ₹${_calculateLiftAmount(widget.durationMonths).toStringAsFixed(0)}',
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w600,
color: Colors.purple.shade700,
),
textAlign: TextAlign.right,
),
),
],
),
SizedBox(height: 16.h),
// Calculation mode
Text(
'Progression Mode:',
style: TextStyle(
fontSize: 13.sp,
fontWeight: FontWeight.w600,
color: Colors.grey.shade800,
),
),
SizedBox(height: 8.h),
Wrap(
spacing: 8.w,
children: [
ChoiceChip(
label: const Text('Linear'),
selected: _calculationMode == 'linear',
onSelected: (selected) {
if (selected) {
setState(() {
_calculationMode = 'linear';
_notifyChanges();
});
}
},
selectedColor: Colors.green.shade200,
),
ChoiceChip(
label: const Text('Accelerated'),
selected: _calculationMode == 'accelerated',
onSelected: (selected) {
if (selected) {
setState(() {
_calculationMode = 'accelerated';
_notifyChanges();
});
}
},
selectedColor: Colors.orange.shade200,
),
ChoiceChip(
label: const Text('Custom'),
selected: _calculationMode == 'custom',
onSelected: (selected) {
if (selected) {
setState(() {
_calculationMode = 'custom';
_notifyChanges();
});
}
},
selectedColor: Colors.purple.shade200,
),
],
),
SizedBox(height: 12.h),
// Preset buttons
Wrap(
spacing: 8.w,
runSpacing: 8.h,
children: [
OutlinedButton.icon(
onPressed: () => _applyPreset('conservative'),
icon: Icon(Icons.shield, size: 16.w),
label: const Text('Conservative'),
style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
),
),
OutlinedButton.icon(
onPressed: () => _applyPreset('balanced'),
icon: Icon(Icons.balance, size: 16.w),
label: const Text('Balanced'),
style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
),
),
OutlinedButton.icon(
onPressed: () => _applyPreset('aggressive'),
icon: Icon(Icons.trending_up, size: 16.w),
label: const Text('Aggressive'),
style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
),
),
OutlinedButton.icon(
onPressed: () => _applyPreset('sample'),
icon: Icon(Icons.file_copy, size: 16.w),
label: const Text('Your Sample'),
style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h),
),
),
],
),
],
),
),
],
),
);
}
Widget _buildCompleteScheduleTable() {
return Card(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Container(
constraints: BoxConstraints(minWidth: MediaQuery.of(context).size.width - 32.w),
child: SingleChildScrollView(
child: DataTable(
headingRowHeight: 56.h,
headingRowColor: MaterialStateProperty.all(Colors.green.shade600),
columnSpacing: 12.w,
horizontalMargin: 12.w,
dataRowMinHeight: 48.h,
dataRowMaxHeight: 56.h,
columns: [
_buildColumn('Month'),
_buildColumn('Chit\nValue'),
_buildColumn('Lifter\nGets', tooltip: 'Net amount paid to lifter'),
_buildColumn('Sub.', tooltip: 'Monthly subscription'),
_buildColumn('Fee'),
_buildColumn('Total\nPayment'),
_buildColumn('Dividend', tooltip: 'Chit Value - Lift Amount'),
],
rows: List.generate(widget.durationMonths, (index) {
return _buildDataRow(index + 1);
}),
),
),
),
),
);
}
DataColumn _buildColumn(String label, {String? tooltip}) {
return DataColumn(
label: Tooltip(
message: tooltip ?? label,
child: Text(
label,
style: TextStyle(
fontSize: 11.sp,
fontWeight: FontWeight.bold,
color: Colors.white,
),
textAlign: TextAlign.center,
),
),
);
}
DataRow _buildDataRow(int month) {
final monthDate = _getMonthDate(month);
final liftAmount = _calculateLiftAmount(month);
final dividendAmount = widget.chitValue - liftAmount;
final totalPayment = widget.subscriptionAmount + widget.commissionAmount;
final isEarly = month <= (widget.durationMonths * 0.3).ceil();
final isLast = month == widget.durationMonths;
final rowColor = isLast
? Colors.purple.shade50
: isEarly
? Colors.green.shade50
: null;
return DataRow(
color: MaterialStateProperty.all(rowColor),
cells: [
_buildCell('$month\n$monthDate', isHeader: true),
_buildCell('${widget.chitValue.toStringAsFixed(0)}'),
_buildCell(
'${liftAmount.toStringAsFixed(0)}',
color: Colors.green.shade700,
bold: true,
),
_buildCell('${widget.subscriptionAmount.toStringAsFixed(0)}'),
_buildCell('${widget.commissionAmount.toStringAsFixed(0)}'),
_buildCell(
'${totalPayment.toStringAsFixed(0)}',
bold: true,
),
_buildCell(
'${dividendAmount >= 0 ? "+" : ""}${dividendAmount.toStringAsFixed(0)}',
color: dividendAmount >= 0 ? Colors.blue.shade700 : Colors.red.shade700,
bold: true,
),
],
);
}
DataCell _buildCell(String text, {bool isHeader = false, Color? color, bool bold = false}) {
return DataCell(
Text(
text,
style: TextStyle(
fontSize: isHeader ? 11.sp : 12.sp,
fontWeight: bold || isHeader ? FontWeight.w600 : FontWeight.normal,
color: color ?? Colors.grey.shade800,
),
textAlign: TextAlign.center,
),
);
}
Widget _buildSummaryCard() {
final totalPayment = widget.subscriptionAmount + widget.commissionAmount;
final totalPerMember = totalPayment * widget.durationMonths;
final firstMonthLift = _calculateLiftAmount(1);
final lastMonthLift = _calculateLiftAmount(widget.durationMonths);
final firstMonthDividend = widget.chitValue - firstMonthLift;
final lastMonthDividend = widget.chitValue - lastMonthLift;
return Card(
elevation: 0,
color: Colors.orange.shade50,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
side: BorderSide(color: Colors.orange.shade200),
),
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.analytics, color: Colors.orange.shade700, size: 18.w),
SizedBox(width: 8.w),
Text(
'Chitfund Analysis',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.bold,
color: Colors.orange.shade800,
),
),
],
),
SizedBox(height: 12.h),
// Key metrics
Row(
children: [
Expanded(
child: _buildMetricCard(
'First Month',
'Lifter: ₹${firstMonthLift.toStringAsFixed(0)}',
'Dividend: +₹${firstMonthDividend.toStringAsFixed(0)}',
Colors.green.shade600,
Icons.rocket_launch,
),
),
SizedBox(width: 12.w),
Expanded(
child: _buildMetricCard(
'Last Month',
'Lifter: ₹${lastMonthLift.toStringAsFixed(0)}',
'Dividend: ${lastMonthDividend >= 0 ? "+" : ""}${lastMonthDividend.toStringAsFixed(0)}',
Colors.purple.shade600,
Icons.flag,
),
),
],
),
SizedBox(height: 12.h),
Divider(color: Colors.orange.shade300),
SizedBox(height: 8.h),
_buildSummaryRow('Chit Amount', '${widget.chitValue.toStringAsFixed(0)}'),
_buildSummaryRow('Monthly Installment (Fixed)', '${totalPayment.toStringAsFixed(0)}'),
_buildSummaryRow('Total per Member', '${totalPerMember.toStringAsFixed(0)}'),
_buildSummaryRow(
'Lift Amount Range',
'${firstMonthLift.toStringAsFixed(0)} → ₹${lastMonthLift.toStringAsFixed(0)}',
),
_buildSummaryRow(
'Monthly Increment',
'${_calculateMonthlyIncrement().toStringAsFixed(0)}',
),
],
),
),
);
}
Widget _buildMetricCard(String title, String value1, String value2, Color color, IconData icon) {
return Container(
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: color.withOpacity(0.3)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(icon, color: color, size: 16.w),
SizedBox(width: 6.w),
Expanded(
child: Text(
title,
style: TextStyle(
fontSize: 11.sp,
fontWeight: FontWeight.w600,
color: color,
),
),
),
],
),
SizedBox(height: 6.h),
Text(
value1,
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.bold,
color: Colors.grey.shade800,
),
),
Text(
value2,
style: TextStyle(
fontSize: 11.sp,
color: Colors.grey.shade700,
),
),
],
),
);
}
Widget _buildSummaryRow(String label, String value) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 4.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey.shade700,
),
),
Text(
value,
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w600,
color: Colors.grey.shade800,
),
),
],
),
);
}
/// Calculate lift amount for a specific month
double _calculateLiftAmount(int month) {
if (widget.durationMonths <= 1) return widget.chitValue;
final double startPercentage = _startingPercentage / 100;
final double endPercentage = _endingPercentage / 100;
double percentage;
switch (_calculationMode) {
case 'linear':
// Linear progression
final double incrementPerMonth = (endPercentage - startPercentage) / (widget.durationMonths - 1);
percentage = startPercentage + (incrementPerMonth * (month - 1));
break;
case 'accelerated':
// Accelerated - grows faster in later months
final double progress = (month - 1) / (widget.durationMonths - 1);
final double accelerationFactor = progress * progress; // Quadratic
percentage = startPercentage + ((endPercentage - startPercentage) * accelerationFactor);
break;
case 'custom':
// Custom - could add more complex formulas here
final double incrementPerMonth = (endPercentage - startPercentage) / (widget.durationMonths - 1);
percentage = startPercentage + (incrementPerMonth * (month - 1));
break;
default:
percentage = startPercentage;
}
return widget.chitValue * percentage;
}
double _calculateMonthlyIncrement() {
if (widget.durationMonths <= 1) return 0;
final first = _calculateLiftAmount(1);
final second = _calculateLiftAmount(2);
return second - first;
}
String _getMonthDate(int monthNumber) {
final now = DateTime.now();
final futureMonth = DateTime(now.year, now.month + monthNumber - 1);
return DateFormat('MMM-yy').format(futureMonth);
}
void _applyPreset(String preset) {
setState(() {
switch (preset) {
case 'conservative':
_startingPercentage = 90.0;
_endingPercentage = 100.0;
_calculationMode = 'linear';
break;
case 'balanced':
_startingPercentage = 85.0;
_endingPercentage = 105.0;
_calculationMode = 'linear';
break;
case 'aggressive':
_startingPercentage = 80.0;
_endingPercentage = 115.0;
_calculationMode = 'accelerated';
break;
case 'sample':
// Match your exact sample data
_startingPercentage = 87.65;
_endingPercentage = 112.35;
_calculationMode = 'linear';
break;
}
_notifyChanges();
});
SnackbarUtil.showSuccess('Preset "$preset" applied!');
}
void _notifyChanges() {
if (widget.onCalculationsChanged != null) {
widget.onCalculationsChanged!({
'startingPercentage': _startingPercentage,
'endingPercentage': _endingPercentage,
'calculationMode': _calculationMode,
'firstMonthLift': _calculateLiftAmount(1),
'lastMonthLift': _calculateLiftAmount(widget.durationMonths),
});
}
}
}