667 lines
21 KiB
Dart
667 lines
21 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
|
|
/// Monthly payment schedule table
|
|
/// Shows month-wise breakdown of payments and chit win amounts
|
|
class MonthlyScheduleTable extends StatelessWidget {
|
|
final double totalValue;
|
|
final int durationMonths;
|
|
final double monthlyInstallment;
|
|
final double commission;
|
|
final double maxDividend;
|
|
|
|
const MonthlyScheduleTable({
|
|
super.key,
|
|
required this.totalValue,
|
|
required this.durationMonths,
|
|
required this.monthlyInstallment,
|
|
required this.commission,
|
|
required this.maxDividend,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (totalValue <= 0 || durationMonths <= 0) {
|
|
return _buildEmptyState();
|
|
}
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Header
|
|
_buildHeader(),
|
|
SizedBox(height: 12.h),
|
|
|
|
// Month list
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: Colors.grey.shade300),
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
constraints: BoxConstraints(
|
|
maxHeight: 350.h, // Scrollable if too many months
|
|
),
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
child: ListView.builder(
|
|
shrinkWrap: true,
|
|
padding: EdgeInsets.zero,
|
|
itemCount: durationMonths,
|
|
itemBuilder: (context, index) {
|
|
return _buildMonthRow(index + 1);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
SizedBox(height: 12.h),
|
|
|
|
// Summary footer
|
|
_buildSummaryFooter(),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildHeader() {
|
|
return Row(
|
|
children: [
|
|
Icon(Icons.calendar_month_rounded, color: Colors.green.shade600, size: 20.w),
|
|
SizedBox(width: 8.w),
|
|
Text(
|
|
'Month-wise Payment Schedule',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
),
|
|
const Spacer(),
|
|
Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.h),
|
|
decoration: BoxDecoration(
|
|
color: Colors.green.shade50,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
border: Border.all(color: Colors.green.shade300),
|
|
),
|
|
child: Text(
|
|
'$durationMonths Months',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.green.shade700,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildMonthRow(int month) {
|
|
final subscriptionAmount = totalValue / durationMonths;
|
|
final chitWinAmount = _calculateChitWinAmount(month);
|
|
final isEarlyMonth = month <= (durationMonths * 0.3).ceil();
|
|
final isLastMonth = month == durationMonths;
|
|
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
color: isLastMonth
|
|
? Colors.purple.shade50
|
|
: isEarlyMonth
|
|
? Colors.green.shade50
|
|
: Colors.white,
|
|
border: Border(
|
|
bottom: BorderSide(
|
|
color: Colors.grey.shade200,
|
|
width: 1,
|
|
),
|
|
),
|
|
),
|
|
child: IntrinsicHeight(
|
|
child: Row(
|
|
children: [
|
|
// Month number
|
|
Container(
|
|
width: 55.w,
|
|
padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 8.w),
|
|
decoration: BoxDecoration(
|
|
color: isLastMonth
|
|
? Colors.purple.shade100
|
|
: isEarlyMonth
|
|
? Colors.green.shade100
|
|
: Colors.grey.shade100,
|
|
border: Border(
|
|
right: BorderSide(
|
|
color: Colors.grey.shade300,
|
|
width: 1,
|
|
),
|
|
),
|
|
),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Text(
|
|
'M',
|
|
style: TextStyle(
|
|
fontSize: 9.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
Text(
|
|
'$month',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: isLastMonth
|
|
? Colors.purple.shade700
|
|
: isEarlyMonth
|
|
? Colors.green.shade700
|
|
: Colors.grey.shade800,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Payment details
|
|
Expanded(
|
|
child: Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 10.h),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
// Payment amount
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
flex: 3,
|
|
child: Text(
|
|
'Payment:',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
flex: 2,
|
|
child: Text(
|
|
'₹${monthlyInstallment.toStringAsFixed(0)}',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
textAlign: TextAlign.right,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 4.h),
|
|
|
|
// Chit win amount
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
Icons.emoji_events_rounded,
|
|
size: 12.w,
|
|
color: Colors.amber.shade700,
|
|
),
|
|
SizedBox(width: 4.w),
|
|
Expanded(
|
|
flex: 3,
|
|
child: Text(
|
|
'Win:',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.grey.shade600,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
flex: 2,
|
|
child: Text(
|
|
'₹${chitWinAmount.toStringAsFixed(0)}',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.green.shade700,
|
|
),
|
|
textAlign: TextAlign.right,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
// Show special badge for first and last month
|
|
if (isEarlyMonth || isLastMonth) ...[
|
|
SizedBox(height: 4.h),
|
|
Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 2.h),
|
|
decoration: BoxDecoration(
|
|
color: isLastMonth
|
|
? Colors.purple.shade200
|
|
: Colors.green.shade200,
|
|
borderRadius: BorderRadius.circular(6.r),
|
|
),
|
|
child: Text(
|
|
isLastMonth ? 'Last' : 'Early Adv.',
|
|
style: TextStyle(
|
|
fontSize: 9.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: isLastMonth
|
|
? Colors.purple.shade900
|
|
: Colors.green.shade900,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
double _calculateChitWinAmount(int month) {
|
|
// Calculate chit win amount based on month
|
|
// Formula: Total Value - Total Commission
|
|
// This is the pool amount that winner receives
|
|
|
|
final totalCommission = commission * durationMonths;
|
|
final chitWinAmount = totalValue - totalCommission;
|
|
|
|
// Winner receives the full chit value minus commission
|
|
return chitWinAmount;
|
|
}
|
|
|
|
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 above to see the month-wise payment schedule',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: Colors.orange.shade700,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSummaryFooter() {
|
|
final totalCollection = monthlyInstallment * durationMonths;
|
|
final totalCommission = commission * durationMonths;
|
|
final netDistribution = totalValue;
|
|
|
|
return Container(
|
|
padding: EdgeInsets.all(12.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade50,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
border: Border.all(color: Colors.blue.shade200),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(Icons.summarize_rounded, color: Colors.blue.shade700, size: 16.w),
|
|
SizedBox(width: 6.w),
|
|
Text(
|
|
'Summary',
|
|
style: TextStyle(
|
|
fontSize: 13.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.blue.shade800,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 10.h),
|
|
_buildSummaryRow('Total Collected', '₹${totalCollection.toStringAsFixed(0)}'),
|
|
_buildSummaryRow('Commission', '₹${totalCommission.toStringAsFixed(0)}'),
|
|
_buildSummaryRow('Distributed', '₹${netDistribution.toStringAsFixed(0)}'),
|
|
Divider(height: 12.h, color: Colors.blue.shade300),
|
|
_buildSummaryRow(
|
|
'Per Month',
|
|
'₹${subscriptionAmount.toStringAsFixed(0)}',
|
|
isHighlight: true,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
double get subscriptionAmount => totalValue / durationMonths;
|
|
|
|
Widget _buildSummaryRow(String label, String value, {bool isHighlight = false}) {
|
|
return Padding(
|
|
padding: EdgeInsets.symmetric(vertical: 3.h),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
flex: 3,
|
|
child: Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.grey.shade700,
|
|
fontWeight: isHighlight ? FontWeight.w600 : FontWeight.normal,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
Expanded(
|
|
flex: 2,
|
|
child: Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: isHighlight ? Colors.blue.shade800 : Colors.grey.shade800,
|
|
),
|
|
textAlign: TextAlign.right,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Expandable monthly schedule widget
|
|
class ExpandableMonthlySchedule extends StatefulWidget {
|
|
final double totalValue;
|
|
final int durationMonths;
|
|
final double monthlyInstallment;
|
|
final double commission;
|
|
final double maxDividend;
|
|
|
|
const ExpandableMonthlySchedule({
|
|
super.key,
|
|
required this.totalValue,
|
|
required this.durationMonths,
|
|
required this.monthlyInstallment,
|
|
required this.commission,
|
|
required this.maxDividend,
|
|
});
|
|
|
|
@override
|
|
State<ExpandableMonthlySchedule> createState() => _ExpandableMonthlyScheduleState();
|
|
}
|
|
|
|
class _ExpandableMonthlyScheduleState extends State<ExpandableMonthlySchedule> {
|
|
bool _isExpanded = false;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Card(
|
|
elevation: 2,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
// Header (always visible)
|
|
InkWell(
|
|
onTap: () {
|
|
if (widget.totalValue > 0 && widget.durationMonths > 0) {
|
|
setState(() => _isExpanded = !_isExpanded);
|
|
}
|
|
},
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
child: Padding(
|
|
padding: EdgeInsets.all(16.w),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(10.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.blue.shade50,
|
|
borderRadius: BorderRadius.circular(10.r),
|
|
),
|
|
child: Icon(
|
|
Icons.event_note_rounded,
|
|
color: Colors.blue.shade600,
|
|
size: 24.w,
|
|
),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Monthly Payment Schedule',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
),
|
|
SizedBox(height: 2.h),
|
|
Text(
|
|
widget.totalValue > 0 && widget.durationMonths > 0
|
|
? 'Tap to ${_isExpanded ? 'hide' : 'view'} all ${widget.durationMonths} months'
|
|
: 'Enter details above to preview',
|
|
style: TextStyle(
|
|
fontSize: 13.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (widget.totalValue > 0 && widget.durationMonths > 0)
|
|
Icon(
|
|
_isExpanded ? Icons.expand_less : Icons.expand_more,
|
|
color: Colors.grey.shade600,
|
|
size: 24.w,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
// Expandable content
|
|
if (_isExpanded)
|
|
Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h),
|
|
child: MonthlyScheduleTable(
|
|
totalValue: widget.totalValue,
|
|
durationMonths: widget.durationMonths,
|
|
monthlyInstallment: widget.monthlyInstallment,
|
|
commission: widget.commission,
|
|
maxDividend: widget.maxDividend,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Compact monthly schedule preview (first 3 and last 2 months)
|
|
class CompactMonthlySchedule extends StatelessWidget {
|
|
final double totalValue;
|
|
final int durationMonths;
|
|
final double monthlyInstallment;
|
|
final double commission;
|
|
|
|
const CompactMonthlySchedule({
|
|
super.key,
|
|
required this.totalValue,
|
|
required this.durationMonths,
|
|
required this.monthlyInstallment,
|
|
required this.commission,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (totalValue <= 0 || durationMonths <= 0) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
|
|
final subscriptionAmount = totalValue / durationMonths;
|
|
final chitWinBase = totalValue - (commission * durationMonths);
|
|
|
|
return Container(
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.shade50,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
border: Border.all(color: Colors.grey.shade200),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Payment Schedule Preview',
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
),
|
|
SizedBox(height: 12.h),
|
|
|
|
// Table header
|
|
Row(
|
|
children: [
|
|
SizedBox(
|
|
width: 60.w,
|
|
child: Text(
|
|
'Month',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.grey.shade700,
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Text(
|
|
'Payment',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.grey.shade700,
|
|
),
|
|
textAlign: TextAlign.right,
|
|
),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: Text(
|
|
'Win Amount',
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.grey.shade700,
|
|
),
|
|
textAlign: TextAlign.right,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
Divider(height: 12.h, color: Colors.grey.shade300),
|
|
|
|
// Show first 3 months
|
|
..._buildCompactRows([1, 2, 3], subscriptionAmount, chitWinBase),
|
|
|
|
// Show separator if more months
|
|
if (durationMonths > 5) ...[
|
|
Padding(
|
|
padding: EdgeInsets.symmetric(vertical: 8.h),
|
|
child: Center(
|
|
child: Text(
|
|
'... ${durationMonths - 5} more months ...',
|
|
style: TextStyle(
|
|
fontSize: 11.sp,
|
|
color: Colors.grey.shade500,
|
|
fontStyle: FontStyle.italic,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
|
|
// Show last 2 months
|
|
if (durationMonths > 3)
|
|
..._buildCompactRows(
|
|
[durationMonths - 1, durationMonths],
|
|
subscriptionAmount,
|
|
chitWinBase,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
List<Widget> _buildCompactRows(List<int> months, double subscription, double chitWinAmount) {
|
|
return months.map((month) {
|
|
if (month > durationMonths) return const SizedBox.shrink();
|
|
|
|
return Padding(
|
|
padding: EdgeInsets.symmetric(vertical: 4.h),
|
|
child: Row(
|
|
children: [
|
|
SizedBox(
|
|
width: 60.w,
|
|
child: Text(
|
|
'$month',
|
|
style: TextStyle(
|
|
fontSize: 13.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.grey.shade800,
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Text(
|
|
'₹${monthlyInstallment.toStringAsFixed(0)}',
|
|
style: TextStyle(
|
|
fontSize: 13.sp,
|
|
color: Colors.grey.shade700,
|
|
),
|
|
textAlign: TextAlign.right,
|
|
),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: Text(
|
|
'₹${chitWinAmount.toStringAsFixed(0)}',
|
|
style: TextStyle(
|
|
fontSize: 13.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.green.shade700,
|
|
),
|
|
textAlign: TextAlign.right,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}).toList();
|
|
}
|
|
}
|
|
|