519 lines
17 KiB
Dart
519 lines
17 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import '../../core/models/financial_table_entry.dart';
|
|
|
|
String _formatIndianCurrency(double amount) {
|
|
// Convert to integer to avoid decimal places
|
|
int intAmount = amount.round();
|
|
|
|
// Format with Indian numbering system (commas every 2 digits after the first 3)
|
|
String amountStr = intAmount.toString();
|
|
String formatted = '';
|
|
|
|
if (amountStr.length <= 3) {
|
|
formatted = amountStr;
|
|
} else {
|
|
// For amounts > 999, use Indian comma system
|
|
int remaining = amountStr.length;
|
|
int start = 0;
|
|
|
|
// First group (rightmost 3 digits)
|
|
if (remaining > 3) {
|
|
formatted = amountStr.substring(amountStr.length - 3);
|
|
remaining -= 3;
|
|
start = amountStr.length - 3;
|
|
} else {
|
|
formatted = amountStr;
|
|
remaining = 0;
|
|
}
|
|
|
|
// Subsequent groups (2 digits each)
|
|
while (remaining > 0) {
|
|
int groupSize = remaining >= 2 ? 2 : remaining;
|
|
int groupStart = start - groupSize;
|
|
String group = amountStr.substring(groupStart, start);
|
|
formatted = group + ',' + formatted;
|
|
start = groupStart;
|
|
remaining -= groupSize;
|
|
}
|
|
}
|
|
|
|
return '₹$formatted';
|
|
}
|
|
|
|
class FinancialTable extends StatelessWidget {
|
|
final List<FinancialTableEntry> entries;
|
|
final bool isLoading;
|
|
final VoidCallback? onRefresh;
|
|
|
|
const FinancialTable({
|
|
super.key,
|
|
required this.entries,
|
|
this.isLoading = false,
|
|
this.onRefresh,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Card(
|
|
elevation: 2,
|
|
margin: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Header
|
|
Container(
|
|
padding: EdgeInsets.all(20.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.green.shade50,
|
|
borderRadius: BorderRadius.only(
|
|
topLeft: Radius.circular(8.r),
|
|
topRight: Radius.circular(8.r),
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
Icons.table_chart,
|
|
color: Colors.green.shade700,
|
|
size: 28.w,
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: Text(
|
|
'Financial Summary',
|
|
style: TextStyle(
|
|
fontSize: 22.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.green.shade700,
|
|
),
|
|
),
|
|
),
|
|
if (onRefresh != null)
|
|
Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(24.r),
|
|
onTap: onRefresh,
|
|
child: Padding(
|
|
padding: EdgeInsets.all(12.w),
|
|
child: Icon(
|
|
Icons.refresh,
|
|
size: 24.w,
|
|
color: Colors.green.shade700,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Table
|
|
if (isLoading)
|
|
Container(
|
|
height: 200.h,
|
|
alignment: Alignment.center,
|
|
child: const CircularProgressIndicator(),
|
|
)
|
|
else if (entries.isEmpty)
|
|
Container(
|
|
height: 200.h,
|
|
alignment: Alignment.center,
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.table_rows_outlined,
|
|
size: 48.w,
|
|
color: Colors.grey.shade400,
|
|
),
|
|
SizedBox(height: 16.h),
|
|
Text(
|
|
'No financial data available',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
else
|
|
SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: DataTable(
|
|
columnSpacing: 20.w,
|
|
headingRowColor: MaterialStateProperty.all(Colors.green.shade100),
|
|
columns: [
|
|
_buildHeaderColumn('Month/Year', 140.w),
|
|
_buildHeaderColumn('Chit Value', 140.w),
|
|
_buildHeaderColumn('Bid Amount', 140.w),
|
|
_buildHeaderColumn('Subscription', 140.w),
|
|
_buildHeaderColumn('Commission', 140.w),
|
|
_buildHeaderColumn('Total Payable', 140.w),
|
|
_buildHeaderColumn('Dividend', 140.w),
|
|
],
|
|
rows: entries.map((entry) {
|
|
return DataRow(
|
|
cells: [
|
|
_buildCell(entry.monthYear, isBold: entry.isTotal),
|
|
_buildCell('₹${entry.chitValue.toStringAsFixed(0)}', isBold: entry.isTotal),
|
|
_buildCell('₹${entry.bidAmount.toStringAsFixed(0)}', isBold: entry.isTotal),
|
|
_buildCell('₹${entry.subscriptionAmount.toStringAsFixed(0)}', isBold: entry.isTotal),
|
|
_buildCell('₹${entry.commissionInstallment.toStringAsFixed(0)}', isBold: entry.isTotal),
|
|
_buildCell('₹${entry.totalPayableInstallment.toStringAsFixed(0)}', isBold: entry.isTotal),
|
|
_buildCell(
|
|
'₹${entry.dividendAmount.toStringAsFixed(0)}',
|
|
isBold: entry.isTotal,
|
|
color: entry.isDividendNegative ? Colors.red : Colors.green,
|
|
),
|
|
],
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
DataColumn _buildHeaderColumn(String label, double width) {
|
|
return DataColumn(
|
|
label: Container(
|
|
width: width,
|
|
child: Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.green.shade800,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
DataCell _buildCell(String text, {bool isBold = false, Color? color}) {
|
|
return DataCell(
|
|
Container(
|
|
padding: EdgeInsets.symmetric(vertical: 12.h),
|
|
child: Text(
|
|
text,
|
|
style: TextStyle(
|
|
fontSize: 15.sp,
|
|
fontWeight: isBold ? FontWeight.w600 : FontWeight.normal,
|
|
color: color ?? (isBold ? Colors.green.shade800 : Colors.grey.shade800),
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class CompactFinancialTable extends StatelessWidget {
|
|
final List<FinancialTableEntry> entries;
|
|
final bool isLoading;
|
|
final VoidCallback? onRefresh;
|
|
|
|
const CompactFinancialTable({
|
|
super.key,
|
|
required this.entries,
|
|
this.isLoading = false,
|
|
this.onRefresh,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Card(
|
|
elevation: 2,
|
|
margin: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Header
|
|
Container(
|
|
padding: EdgeInsets.all(16.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.green.shade50,
|
|
borderRadius: BorderRadius.only(
|
|
topLeft: Radius.circular(12.r),
|
|
topRight: Radius.circular(12.r),
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(8.w),
|
|
decoration: BoxDecoration(
|
|
color: Colors.green.shade600.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
),
|
|
child: Icon(
|
|
Icons.table_chart,
|
|
color: Colors.green.shade700,
|
|
size: 20.w,
|
|
),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
Expanded(
|
|
child: Text(
|
|
'Financial Summary',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.green.shade700,
|
|
),
|
|
),
|
|
),
|
|
if (onRefresh != null)
|
|
Material(
|
|
color: Colors.transparent,
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(20.r),
|
|
onTap: onRefresh,
|
|
child: Padding(
|
|
padding: EdgeInsets.all(8.w),
|
|
child: Icon(
|
|
Icons.refresh,
|
|
size: 20.w,
|
|
color: Colors.green.shade700,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Mobile-optimized list view
|
|
if (isLoading)
|
|
Container(
|
|
height: 150.h,
|
|
alignment: Alignment.center,
|
|
child: const CircularProgressIndicator(),
|
|
)
|
|
else if (entries.isEmpty)
|
|
Container(
|
|
height: 150.h,
|
|
alignment: Alignment.center,
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.table_rows_outlined,
|
|
size: 32.w,
|
|
color: Colors.grey.shade400,
|
|
),
|
|
SizedBox(height: 8.h),
|
|
Text(
|
|
'No financial data',
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
color: Colors.grey.shade600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
else
|
|
ListView.separated(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
itemCount: entries.length,
|
|
separatorBuilder: (context, index) => Divider(height: 1.h, color: Colors.grey.shade200),
|
|
itemBuilder: (context, index) {
|
|
final entry = entries[index];
|
|
return _buildMobileRow(entry);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMobileRow(FinancialTableEntry entry) {
|
|
final isTotal = entry.isTotal;
|
|
|
|
return Container(
|
|
padding: EdgeInsets.all(16.w),
|
|
color: isTotal ? Colors.green.shade50 : Colors.transparent,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Month/Year header
|
|
Row(
|
|
children: [
|
|
Container(
|
|
padding: EdgeInsets.all(6.w),
|
|
decoration: BoxDecoration(
|
|
color: (isTotal ? Colors.green.shade600 : Colors.grey.shade600).withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(6.r),
|
|
),
|
|
child: Icon(
|
|
isTotal ? Icons.calculate : Icons.calendar_today,
|
|
size: 16.w,
|
|
color: isTotal ? Colors.green.shade700 : Colors.grey.shade600,
|
|
),
|
|
),
|
|
SizedBox(width: 8.w),
|
|
Expanded(
|
|
child: Text(
|
|
entry.monthYear,
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: isTotal ? FontWeight.w600 : FontWeight.w500,
|
|
color: isTotal ? Colors.green.shade800 : Colors.grey.shade800,
|
|
),
|
|
),
|
|
),
|
|
if (isTotal)
|
|
Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 3.h),
|
|
decoration: BoxDecoration(
|
|
color: Colors.green.shade100,
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
child: Text(
|
|
'TOTAL',
|
|
style: TextStyle(
|
|
fontSize: 10.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.green.shade700,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
|
|
// Financial data grid - optimized for mobile
|
|
Column(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildMobileCard('Chit Value', _formatIndianCurrency(entry.chitValue), Icons.currency_rupee, Colors.blue),
|
|
),
|
|
SizedBox(width: 8.w),
|
|
Expanded(
|
|
child: _buildMobileCard('Bid Amount', _formatIndianCurrency(entry.bidAmount), Icons.payment, Colors.green),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 6.h),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildMobileCard('Subscription', _formatIndianCurrency(entry.subscriptionAmount), Icons.account_balance_wallet, Colors.orange),
|
|
),
|
|
SizedBox(width: 8.w),
|
|
Expanded(
|
|
child: _buildMobileCard('Commission', _formatIndianCurrency(entry.commissionInstallment), Icons.percent, Colors.purple),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 6.h),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildMobileCard('Total Payable', _formatIndianCurrency(entry.totalPayableInstallment), Icons.calculate, Colors.indigo),
|
|
),
|
|
SizedBox(width: 8.w),
|
|
Expanded(
|
|
child: _buildMobileCard(
|
|
'Dividend',
|
|
_formatIndianCurrency(entry.dividendAmount),
|
|
Icons.trending_up,
|
|
entry.isDividendNegative ? Colors.red : Colors.green,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMobileCard(String label, String value, IconData icon, Color color) {
|
|
return Container(
|
|
padding: EdgeInsets.all(12.w),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.08),
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
border: Border.all(color: color.withOpacity(0.2), width: 1),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(icon, size: 16.w, color: color),
|
|
SizedBox(width: 6.w),
|
|
Expanded(
|
|
child: Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: color,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 2.h),
|
|
Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: color,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
DataColumn _buildCompactHeaderColumn(String label, double width) {
|
|
return DataColumn(
|
|
label: Container(
|
|
width: width,
|
|
child: Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.green.shade800,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
DataCell _buildCompactCell(String text, {bool isBold = false, Color? color}) {
|
|
return DataCell(
|
|
Container(
|
|
padding: EdgeInsets.symmetric(vertical: 6.h),
|
|
child: Text(
|
|
text,
|
|
style: TextStyle(
|
|
fontSize: 13.sp,
|
|
fontWeight: isBold ? FontWeight.w600 : FontWeight.normal,
|
|
color: color ?? (isBold ? Colors.green.shade800 : Colors.grey.shade800),
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|