dashbaord login page changes

This commit is contained in:
venbaittech 2025-12-23 09:53:59 +05:30
parent a52b8d6c04
commit 9cbb1d8cd5
9 changed files with 1529 additions and 1021 deletions

File diff suppressed because one or more lines are too long

View File

@ -133,7 +133,6 @@ class ApiService {
return response;
}
Future<Map<String, dynamic>> logoutUsingAPI(agentId) async {
// print(_token);
if (_token == null) {
@ -177,8 +176,8 @@ class ApiService {
}
// 3. UNAUTHORIZED / FORBIDDEN (The 401/403 Fix)
if ((response.statusCode == 401 && body['status'] == 401) || (response.statusCode == 403 && body['status'] == 403)) {
if ((response.statusCode == 401 && body['status'] == 401) ||
(response.statusCode == 403 && body['status'] == 403)) {
// Perform cleanup
await AuthService.clearToken();
@ -196,10 +195,8 @@ class ApiService {
// 4. ALL OTHER ERRORS
throw Exception('Server Error: ${response.statusCode}');
}
Future<Map<String, dynamic>> CheckDuplicate(
BuildContext context,
String value,
@ -454,6 +451,8 @@ class ApiService {
} else if (path == 'PerformingTop50') {
pathVal =
'dashboard/downloadAgentMonthlyPoliciesExcel?manager_id=$managerId&agent_id=$id';
} else if (path == 'PerformingAgentAll50') {
pathVal = 'dashboard/downloadT50AgentPoliciesExcel?manager_id=$managerId';
} else if (path == 'NonPerformingBelow50K') {
pathVal = 'dashboard/downloadLowPremiumAgentExcel?manager_id=$managerId';
} else {
@ -1972,10 +1971,10 @@ class ApiService {
return response;
}
Future<void> generatePolicyExcel(managerId,fromDate,toDate) async {
final url = Uri.parse('${Env.apiUrl}reports/policy-excel?manager_id=$managerId&from_date=${fromDate ?? ''}&to_date=${toDate ?? ''}',);
Future<void> generatePolicyExcel(managerId, fromDate, toDate) async {
final url = Uri.parse(
'${Env.apiUrl}reports/policy-excel?manager_id=$managerId&from_date=${fromDate ?? ''}&to_date=${toDate ?? ''}',
);
print('getPdfDownload policy-excel - $url');
await _initializeToken();
@ -2042,14 +2041,15 @@ class ApiService {
}
Future<Map<String, dynamic>> fetchAuditHistory(id) async {
print( "fetchAuditHistory - $id " );
print("fetchAuditHistory - $id ");
if (_token == null) {
await _initializeToken();
}
// final url = Uri.parse('${Env.apiUrl}audit/history?table_name=partner_policy&pk=80',);
final url = Uri.parse('${Env.apiUrl}audit/history?table_name=partner_policy&pk=${id ?? ''}',);
final url = Uri.parse(
'${Env.apiUrl}audit/history?table_name=partner_policy&pk=${id ?? ''}',
);
final headers = {
'Authorization': 'Bearer $_token' ?? '',

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:easy_loading_button/easy_loading_button.dart';
import 'package:google_fonts/google_fonts.dart';
class CommonButton extends StatelessWidget {
final String text;
@ -25,10 +26,10 @@ class CommonButton extends StatelessWidget {
type: EasyButtonType.elevated,
idleStateWidget: Text(
text,
style: TextStyle(
style: GoogleFonts.poppins(
color: textColor,
fontSize: 16,
fontWeight: FontWeight.w600,
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
loadingStateWidget: const CircularProgressIndicator(

View File

@ -239,11 +239,7 @@ class ProfilePopUpState extends ConsumerState<ProfilePopUp> {
),
),
const SizedBox(height: 26),
if (profileData?['mobile'] != null ||
profileData!['mobile']
.toString()
.trim()
.isNotEmpty) ...[
if (hasValidValue(profileData?['mobile'])) ...[
Row(
children: [
const Icon(
@ -260,11 +256,7 @@ class ProfilePopUpState extends ConsumerState<ProfilePopUp> {
),
const SizedBox(height: 8),
],
if (profileData?['email'] != null ||
profileData!['email']
.toString()
.trim()
.isNotEmpty) ...[
if (hasValidValue(profileData?['email'])) ...[
Row(
children: [
const Icon(
@ -282,11 +274,7 @@ class ProfilePopUpState extends ConsumerState<ProfilePopUp> {
const SizedBox(height: 16),
],
if (profileData?['address'] != null &&
profileData!['address']
.toString()
.trim()
.isNotEmpty) ...[
if (hasValidValue(profileData?['address'])) ...[
if ((roleId != 1) &&
(roleId != 2) &&
(roleId != 3)) ...[
@ -431,6 +419,12 @@ class ProfilePopUpState extends ConsumerState<ProfilePopUp> {
);
}
bool hasValidValue(dynamic value) {
if (value == null) return false;
if (value is String) return value.trim().isNotEmpty;
return true; // for numbers, bool, etc.
}
static final _dataBold = TextStyle(
fontSize: 11.8,
fontWeight: FontWeight.w400,

View File

@ -10,6 +10,7 @@ import '../../../../core/services/api_service.dart';
import '../../../../data/models/bar_data.dart';
import '../../../providers/userRoleProvider.dart';
import '../../../themes/charts/barChart.dart';
import '../../../themes/charts/barChart_horizontal.dart';
import '../../../themes/indicators/input_field_decoration.dart';
class BussinessDashboard extends ConsumerStatefulWidget {
@ -163,7 +164,7 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
}
}
List<BarData> buildInsurerBarData() {
List<Map<String, dynamic>> getSortedInsurerWise() {
double parseDouble(dynamic value) {
if (value == null) return 0.0;
if (value is num) return value.toDouble();
@ -171,12 +172,45 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
return 0.0;
}
return filteredInsurerWise.map((item) {
print('policies_current - ${item['total_policies_current_month']}');
print('policies_PREV - ${item['total_policies_pre_month']}');
final List<Map<String, dynamic>> sorted = List<Map<String, dynamic>>.from(
filteredInsurerWise,
);
print('premium_current - ${item['total_premium_current_month']}');
print('premium_PREV - ${item['total_premium_pre_month']}');
sorted.sort((a, b) {
final aCurrent = isInsureWisePolicy
? parseDouble(a['total_policies_current_month'])
: parseDouble(a['total_premium_current_month']);
final aPrev = isInsureWisePolicy
? parseDouble(a['total_policies_pre_month'])
: parseDouble(a['total_premium_pre_month']);
final bCurrent = isInsureWisePolicy
? parseDouble(b['total_policies_current_month'])
: parseDouble(b['total_premium_current_month']);
final bPrev = isInsureWisePolicy
? parseDouble(b['total_policies_pre_month'])
: parseDouble(b['total_premium_pre_month']);
final aMax = aCurrent > aPrev ? aCurrent : aPrev;
final bMax = bCurrent > bPrev ? bCurrent : bPrev;
return bMax.compareTo(aMax); // DESC
});
return sorted;
}
List<BarData> buildInsurerBarData(List<Map<String, dynamic>> sortedList) {
double parseDouble(dynamic value) {
if (value == null) return 0.0;
if (value is num) return value.toDouble();
if (value is String) return double.tryParse(value) ?? 0.0;
return 0.0;
}
return sortedList.map((item) {
return BarData(
Colors.blue,
isInsureWisePolicy
@ -185,25 +219,23 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
isInsureWisePolicy
? parseDouble(item['total_policies_pre_month'])
: parseDouble(item['total_premium_pre_month']),
item['insurer_id'] != null ? item['insurer_id'] : '0',
item['insurer_id'] ?? '0',
'Insurer',
5,
);
}).toList();
}
List<String> buildInsurerLabels() {
return filteredInsurerWise.map<String>((item) {
List<String> buildInsurerLabels(List<Map<String, dynamic>> sortedList) {
return sortedList.map<String>((item) {
final name = item['short_name'];
if (name != null && name.toString().trim().isNotEmpty) {
return name.toString();
} else {
return item['short_name']?.toString() ?? '';
}
return (name != null && name.toString().trim().isNotEmpty)
? name.toString()
: '';
}).toList();
}
List<BarData> buildBrokerBarData() {
List<Map<String, dynamic>> getSortedBrokerWise() {
double parseDouble(dynamic value) {
if (value == null) return 0.0;
if (value is num) return value.toDouble();
@ -211,12 +243,45 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
return 0.0;
}
return filteredBrokerWise.map((item) {
print('policies_current - ${item['total_policies_current_month']}');
print('policies_PREV - ${item['total_policies_pre_month']}');
final List<Map<String, dynamic>> sorted = List<Map<String, dynamic>>.from(
filteredBrokerWise,
);
print('premium_current - ${item['total_premium_current_month']}');
print('premium_PREV - ${item['total_premium_pre_month']}');
sorted.sort((a, b) {
final aCurrent = isBrokerWisePolicy
? parseDouble(a['total_policies_current_month'])
: parseDouble(a['total_premium_current_month']);
final aPrev = isBrokerWisePolicy
? parseDouble(a['total_policies_pre_month'])
: parseDouble(a['total_premium_pre_month']);
final bCurrent = isBrokerWisePolicy
? parseDouble(b['total_policies_current_month'])
: parseDouble(b['total_premium_current_month']);
final bPrev = isBrokerWisePolicy
? parseDouble(b['total_policies_pre_month'])
: parseDouble(b['total_premium_pre_month']);
final aMax = aCurrent > aPrev ? aCurrent : aPrev;
final bMax = bCurrent > bPrev ? bCurrent : bPrev;
return bMax.compareTo(aMax); // 🔥 DESC
});
return sorted;
}
List<BarData> buildBrokerBarData(List<Map<String, dynamic>> sortedList) {
double parseDouble(dynamic value) {
if (value == null) return 0.0;
if (value is num) return value.toDouble();
if (value is String) return double.tryParse(value) ?? 0.0;
return 0.0;
}
return sortedList.map((item) {
return BarData(
Colors.blue,
isBrokerWisePolicy
@ -225,31 +290,97 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
isBrokerWisePolicy
? parseDouble(item['total_policies_pre_month'])
: parseDouble(item['total_premium_pre_month']),
item['broker_id'] != null ? item['broker_id'] : '0',
item['broker_id'] ?? '0',
'Broker',
5,
);
}).toList();
}
List<String> buildBrokerLabels() {
return filteredBrokerWise.map<String>((item) {
List<String> buildBrokerLabels(List<Map<String, dynamic>> sortedList) {
return sortedList.map<String>((item) {
final name = item['broker_name'];
if (name != null && name.toString().trim().isNotEmpty) {
return name.toString();
} else {
return item['broker_name']?.toString() ?? '';
}
return (name != null && name.toString().trim().isNotEmpty)
? name.toString()
: '';
}).toList();
}
List<String> buildBrokerShortNames() {
return filteredBrokerWise.map<String>((item) {
List<String> buildBrokerShortNames(List<Map<String, dynamic>> sortedList) {
return sortedList.map<String>((item) {
return item['broker_short_name']?.toString() ?? '';
}).toList();
}
List<BarData> buildProductBarData() {
List<Map<String, dynamic>> getSortedProductWise() {
double parseDouble(dynamic value) {
if (value == null) return 0.0;
if (value is num) return value.toDouble();
if (value is String) return double.tryParse(value) ?? 0.0;
return 0.0;
}
final List<Map<String, dynamic>> sorted = List<Map<String, dynamic>>.from(
filteredProductWise,
);
sorted.sort((a, b) {
final aCurrent = isProductWisePolicy
? parseDouble(a['total_policies_current_month'])
: parseDouble(a['total_premium_current_month']);
final aPrev = isProductWisePolicy
? parseDouble(a['total_policies_pre_month'])
: parseDouble(a['total_premium_pre_month']);
final bCurrent = isProductWisePolicy
? parseDouble(b['total_policies_current_month'])
: parseDouble(b['total_premium_current_month']);
final bPrev = isProductWisePolicy
? parseDouble(b['total_policies_pre_month'])
: parseDouble(b['total_premium_pre_month']);
final aMax = aCurrent > aPrev ? aCurrent : aPrev;
final bMax = bCurrent > bPrev ? bCurrent : bPrev;
return bMax.compareTo(aMax); // DESC
});
return sorted;
}
List<BarData> buildProductBarData(List<Map<String, dynamic>> sortedList) {
double parseDouble(dynamic value) {
if (value == null) return 0.0;
if (value is num) return value.toDouble();
if (value is String) return double.tryParse(value) ?? 0.0;
return 0.0;
}
return sortedList.map((item) {
return BarData(
Colors.blue,
isProductWisePolicy
? parseDouble(item['total_policies_current_month'])
: parseDouble(item['total_premium_current_month']),
isProductWisePolicy
? parseDouble(item['total_policies_pre_month'])
: parseDouble(item['total_premium_pre_month']),
item['vehicle_type']?.toString() ?? '',
'Product',
10,
);
}).toList();
}
List<String> buildProductLabels(List<Map<String, dynamic>> sortedList) {
return sortedList.map<String>((item) {
return item['vehicle_type']?.toString() ?? '';
}).toList();
}
List<BarData> buildProductBarData1() {
double parseDouble(dynamic value) {
if (value == null) return 0.0;
if (value is num) return value.toDouble();
@ -278,7 +409,7 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
}).toList();
}
List<String> buildProductLabels() {
List<String> buildProductLabels1() {
return filteredProductWise.map<String>((item) {
final name = item['vehicle_type'];
if (name != null && name.toString().trim().isNotEmpty) {
@ -366,47 +497,96 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
legendWidget(),
],
),
(showInsurer || showBroker)
? Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// if (showInsurer) Expanded(child: insurerContainer()),
// if (showBroker) Expanded(child: brokerContainer()),
showInsurer
? Expanded(child: insurerContainer())
: SizedBox(),
showBroker
? Expanded(child: brokerContainer())
: SizedBox(),
// showProduct ? Expanded(child: productContainer()) : SizedBox(),
],
),
)
: SizedBox(),
if (showProduct) ...[
SizedBox(height: 10),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
showProduct
? Expanded(child: productContainer())
: SizedBox(),
],
),
Expanded(
child: Row(
children: [
(showInsurer || showBroker)
? Expanded(
flex: 2,
child: Column(
children: [
showInsurer
? Expanded(child: insurerContainer())
: SizedBox(),
showInsurer ? SizedBox(height: 10) : SizedBox(),
showBroker
? Expanded(child: brokerContainer())
: SizedBox(),
],
),
)
: SizedBox(),
showProduct
? Expanded(flex: 1, child: productContainer())
: SizedBox(),
],
),
] else ...[
SizedBox.shrink(),
],
),
// (showInsurer || showBroker)
// ? Expanded(
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// // if (showInsurer) Expanded(child: insurerContainer()),
// // if (showBroker) Expanded(child: brokerContainer()),
// showInsurer
// ? Expanded(child: insurerContainer())
// : SizedBox(),
// showBroker
// ? Expanded(child: brokerContainer())
// : SizedBox(),
// // showProduct ? Expanded(child: productContainer()) : SizedBox(),
// ],
// ),
// )
// : SizedBox(),
// if (showBroker) ...[
// SizedBox(height: 10),
// Expanded(
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// showBroker ? Expanded(child: brokerContainer()) : SizedBox(),
// ],
// ),
// ),
// ] else ...[
// SizedBox.shrink(),
// ],
// if (showProduct) ...[
// SizedBox(height: 10),
// Expanded(
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// showProduct
// ? Expanded(child: productContainer())
// : SizedBox(),
// ],
// ),
// ),
// ] else ...[
// SizedBox.shrink(),
// ],
],
),
);
}
Widget insurerContainer() {
final chartInsurerData = buildInsurerBarData();
final chartInsurerLabels = buildInsurerLabels();
// final chartInsurerData = buildInsurerBarData();
// final chartInsurerLabels = buildInsurerLabels();
final sortedInsurer = getSortedInsurerWise();
final chartInsurerData = buildInsurerBarData(sortedInsurer);
final chartInsurerLabels = buildInsurerLabels(sortedInsurer);
print('chartInsurerData - $chartInsurerData');
print('chartInsurerLabels - $chartInsurerLabels');
return Card(
@ -460,9 +640,15 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
}
Widget brokerContainer() {
final chartBrokerData = buildBrokerBarData();
final chartBrokerLabels = buildBrokerLabels();
final chartBrokerShortName = buildBrokerShortNames();
// final chartBrokerData = buildBrokerBarData();
// final chartBrokerLabels = buildBrokerLabels();
// final chartBrokerShortName = buildBrokerShortNames();
final sortedBroker = getSortedBrokerWise();
final chartBrokerData = buildBrokerBarData(sortedBroker);
final chartBrokerLabels = buildBrokerLabels(sortedBroker);
final chartBrokerShortNames = buildBrokerShortNames(sortedBroker);
print('chartBrokerData - $chartBrokerData');
print('chartBrokerLabels - $chartBrokerLabels');
return Card(
@ -498,7 +684,7 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
Expanded(
child: CustomBarChart(
dataList: chartBrokerData,
labels: chartBrokerShortName,
labels: chartBrokerShortNames,
shortName: chartBrokerLabels,
// labels: chartBrokerLabels,
@ -513,8 +699,12 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
}
Widget productContainer() {
final chartProductData = buildProductBarData();
final chartProductLabels = buildProductLabels();
final sortedList = getSortedProductWise();
final chartProductData = buildProductBarData(sortedList);
final chartProductLabels = buildProductLabels(sortedList);
// final chartProductData = buildProductBarData();
// final chartProductLabels = buildProductLabels();
print('chartProductData - $chartProductData');
print('chartProductLabels - $chartProductLabels');
return Card(
@ -547,7 +737,7 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
),
SizedBox(height: 10),
Expanded(
child: CustomBarChart(
child: CustomBarChartHorizontal(
dataList: chartProductData,
labels: chartProductLabels,
),
@ -563,11 +753,21 @@ class _BussinessDashboardState extends ConsumerState<BussinessDashboard> {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.circle, size: 10, color: Colors.cyan.shade300),
Icon(
Icons.circle,
size: 10,
color: Color(0xFF34a9a8),
// color: Colors.cyan.shade300
),
SizedBox(width: 2),
Text('Current Month', style: _styleSmall1),
SizedBox(width: 5),
Icon(Icons.circle, size: 10, color: Colors.orange.shade300),
Icon(
Icons.circle,
size: 10,
// color: Colors.orange.shade300
color: const Color(0xFFA5E4E1),
),
SizedBox(width: 2),
Text('Previous Month', style: _styleSmall1),
],

View File

@ -332,9 +332,57 @@ class _DashboardState extends ConsumerState<PartnerDashboard> {
child: Row(
children: [
Expanded(
child: Text(
'Performing Partner ( Top 50 )',
style: _chartHeader,
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'Performing Partner ( Top 50 )',
style: _chartHeader,
),
SizedBox(width: 10),
InkWell(
onTap: () async {
final path =
"PerformingAgentAll50";
final id = '';
final month = 'current';
await apiService
.generateChartExcel(
path,
id,
month,
managerId,
);
},
child: Tooltip(
message: 'Export Excel',
child: Container(
padding: const EdgeInsets.all(
5.0,
),
decoration: BoxDecoration(
color: const Color(
0xFF2E7D6E,
),
// color: const Color(0xFF425B5B),
borderRadius:
BorderRadius.circular(
2.0,
),
),
child: Image.asset(
"assets/miscellaneous/export.png",
height: 11,
width: 11,
// color: Colors.green,
),
),
),
),
],
),
),
@ -406,6 +454,8 @@ class _DashboardState extends ConsumerState<PartnerDashboard> {
),
),
SizedBox(width: 10),
Text(
isNonPerfomingAgntPolicy
? "No Business"
@ -429,6 +479,42 @@ class _DashboardState extends ConsumerState<PartnerDashboard> {
},
),
),
SizedBox(width: 5),
InkWell(
onTap: () async {
final path = isNonPerfomingAgntPolicy
? "NoBusiness"
: "NonPerformingBelow50K";
final id = '';
final month = 'current';
await apiService.generateChartExcel(
path,
id,
month,
managerId,
);
},
child: Tooltip(
message: 'Export Excel',
child: Container(
padding: const EdgeInsets.all(5.0),
decoration: BoxDecoration(
color: const Color(0xFF2E7D6E),
// color: const Color(0xFF425B5B),
borderRadius: BorderRadius.circular(
2.0,
),
),
child: Image.asset(
"assets/miscellaneous/export.png",
height: 11,
width: 11,
// color: Colors.green,
),
),
),
),
],
),
Expanded(
@ -624,39 +710,43 @@ class _PerformanceState extends State<Performance> {
),
],
SizedBox(width: 10),
InkWell(
onTap: () async {
final path1 = 'PerformingTop50';
final path2 = widget.isPerfomingAgntPolicy
? "NoBusiness"
: "NonPerformingBelow50K";
final path = (widget.title == 'Performing Partner')
? path1
: path2;
final id =
((widget.title == 'Performing Partner') &&
(row['agent_id'] != null))
? row['agent_id']
: '';
final month = 'current';
if (widget.title == 'Performing Partner') ...[
InkWell(
onTap: () async {
final path1 = 'PerformingTop50';
final path2 = widget.isPerfomingAgntPolicy
? "NoBusiness"
: "NonPerformingBelow50K";
final path =
(widget.title == 'Performing Partner')
? path1
: path2;
await widget.apiService.generateChartExcel(
path,
id,
month,
widget.managerId,
);
},
child: Tooltip(
message: 'Click To Download Excel',
child: Icon(
Icons.download,
size: 14,
color: Colors.green.shade300,
final id =
((widget.title == 'Performing Partner') &&
(row['agent_id'] != null))
? row['agent_id']
: '';
final month = 'current';
await widget.apiService.generateChartExcel(
path,
id,
month,
widget.managerId,
);
},
child: Tooltip(
message: 'Click To Download Excel',
child: Icon(
Icons.download,
size: 14,
color: Colors.green.shade300,
),
),
),
),
],
],
),
);

File diff suppressed because it is too large Load Diff

View File

@ -64,266 +64,347 @@ class _CustomBarChartState extends ConsumerState<CustomBarChart> {
x: x,
groupVertically: false,
// showingTooltipIndicators: [0],
// showingTooltipIndicators: [x],
showingTooltipIndicators: const [1, 2],
// barsSpace: 30,
barRods: [
BarChartRodData(toY: 0, color: Colors.transparent, width: 10),
BarChartRodData(
toY: value1,
color: Colors.cyan.shade300,
color: const Color(0xFF34a9a8),
// color: Color(0xFF0FB9B1),
width: 20,
borderRadius: BorderRadius.circular(0),
),
// BarChartRodData(toY: 2, color: Colors.orange.shade300, width: 6),
BarChartRodData(
toY: value2,
color: Colors.orange.shade300,
color: const Color(0xFFA5E4E1),
// color: const Color(0xFF64748B),
// color: Colors.orange.shade300,
// color: Color(0xFF2F3640),
width: 20,
borderRadius: BorderRadius.circular(0),
),
],
showingTooltipIndicators: touchedGroupIndex == x ? [0] : [],
// showingTooltipIndicators: touchedGroupIndex == x ? [0] : [],
);
}
@override
String compactNumber(num value) {
if (value >= 10000000) {
return '${(value / 10000000).toStringAsFixed(1).replaceAll('.0', '')}Cr';
} else if (value >= 1000000) {
return '${(value / 1000000).toStringAsFixed(1).replaceAll('.0', '')}M';
} else if (value >= 1000) {
return '${(value / 1000).toStringAsFixed(1).replaceAll('.0', '')}K';
} else {
return value.toInt().toString();
}
}
// Widget build(BuildContext context) {
// return AspectRatio(
// aspectRatio: 1.4,
//
// );
// }
double getChartWidth() {
const double barWidth = 40; // bar + spacing
double minWidth =
MediaQuery.of(context).size.width * 0.9; // minimum chart width
final calculatedWidth = widget.dataList.length * barWidth;
return calculatedWidth < minWidth ? minWidth : calculatedWidth;
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return SizedBox(
width: constraints.maxWidth,
height: constraints.maxHeight,
child: BarChart(
BarChartData(
// alignment: BarChartAlignment.spaceBetween,
alignment: BarChartAlignment.start,
// verticalAxis: Axis.horizontal,
// rotationQuarterTurns: rotationTurns,
barTouchData: BarTouchData(
enabled: true,
handleBuiltInTouches: true,
touchTooltipData: BarTouchTooltipData(
tooltipMargin: 6,
tooltipRoundedRadius: 6,
tooltipPadding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
// ask fl_chart to fit tooltip inside available space
fitInsideVertically:
true, // if your fl_chart version supports it
fitInsideHorizontally:
true, // if your fl_chart version supports it
getTooltipItem:
(
BarChartGroupData group,
int groupIndex,
BarChartRodData rod,
int rodIndex,
) {
final textColor = rodIndex == 1
? Colors.cyan.shade300
: Colors.orange.shade300;
final textStyle = TextStyle(
color: textColor,
fontSize: 12,
fontWeight: FontWeight.w600,
);
final formattedValue = indianFormatter.format(
rod.toY.round(),
);
return BarTooltipItem(formattedValue, textStyle);
// return BarTooltipItem('${rod.toY.round()}', textStyle);
},
),
touchCallback: (FlTouchEvent e, BarTouchResponse? r) async {
// optional: change state to highlight tapped group
if (r == null || r.spot == null) {
setState(() => touchedGroupIndex = -1);
return;
}
setState(
() => touchedGroupIndex = r.spot!.touchedBarGroupIndex,
);
final spot = r.spot!;
final groupIndex = spot.touchedBarGroupIndex;
final rodIndex = spot.touchedRodDataIndex;
final barData = widget.dataList[groupIndex];
final name = barData.chartName;
final val = barData.id;
final month = rodIndex == 1 ? 'current' : 'previous';
print('name - $name');
print('val - $val');
print('month - $month');
print('managerId - $managerId');
if (e is FlTapUpEvent) {
debugPrint(
'Tapped value → ${rodIndex == 1 ? barData.value1 : barData.value2} -> ${barData.id}-'
' ${rodIndex == 1 ? 'current' : 'previous'} -> ${barData.chartName}',
);
if (managerId != null) {
print('managerId - $managerId');
await apiService.generateChartExcel(
name,
val,
month,
managerId,
);
}
}
},
),
borderData: FlBorderData(
show: true,
border: Border(
bottom: BorderSide(
color: AppColors.contentColorBlack,
width: 0.1,
),
left: BorderSide(
color: AppColors.contentColorBlack,
width: 0.1,
),
),
),
gridData: FlGridData(
show: true,
// drawVerticalLine: true,
drawVerticalLine: false,
getDrawingHorizontalLine: (value) => FlLine(
// color: AppColors.gridLinesColor.withValues(alpha: 0.2),
color: Colors.blueGrey.shade100,
// color: Colors.black,
// color: AppColors.borderColor,
strokeWidth: 0.3,
),
),
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
axisNameWidget: widget.btnName != null
? Text(widget.btnName!)
: Text(''),
axisNameSize: 10,
sideTitles: SideTitles(
reservedSize: 30,
showTitles: true,
getTitlesWidget: (value, meta) {
final i = value.toInt();
if (i >= widget.labels.length) return const SizedBox();
final hasShortName =
widget.shortName != null &&
widget.shortName!.length > i &&
widget.shortName![i].trim().isNotEmpty;
return SideTitleWidget(
meta: meta,
child: hasShortName
? Tooltip(
message: widget.shortName![i],
child: Text(
widget.labels[i],
style: GoogleFonts.poppins(
fontSize: 9.5,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.end,
),
)
: Text(
widget.labels[i],
style: GoogleFonts.poppins(
fontSize: 9.5,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.end,
),
);
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
// width: constraints.maxWidth,
// height: constraints.maxHeight,
width: getChartWidth(), // 👈 IMPORTANT
height: constraints.maxHeight,
child: BarChart(
BarChartData(
// alignment: BarChartAlignment.spaceBetween,
alignment: BarChartAlignment.start,
// verticalAxis: Axis.horizontal,
// rotationQuarterTurns: rotationTurns,
barTouchData: BarTouchData(
enabled: true,
handleBuiltInTouches: true,
touchTooltipData: BarTouchTooltipData(
tooltipMargin: 20,
tooltipRoundedRadius: 6,
getTooltipColor: (group) {
return Colors.transparent; // 👈 tooltip background color
},
tooltipPadding: const EdgeInsets.symmetric(
horizontal: 7,
vertical: 2,
),
// rotateAngle: -85,
// ask fl_chart to fit tooltip inside available space
fitInsideVertically:
true, // if your fl_chart version supports it
fitInsideHorizontally:
true, // if your fl_chart version supports it
getTooltipItem:
(
BarChartGroupData group,
int groupIndex,
BarChartRodData rod,
int rodIndex,
) {
if (rod.toY == 0) {
return null;
}
final textColor = rodIndex == 1
? Color(0xFF34a9a8)
: Color(0xFF020050);
// final textColor = Colors.black87;
// final textColor = Color(0xFF020050);
// final textColor =;
final textStyle = GoogleFonts.poppins(
color: textColor,
fontSize: 8,
fontWeight: rodIndex == 1
? FontWeight.w600
: FontWeight.w400,
);
// final formattedValue = indianFormatter.format(
// rod.toY.round(),
// );
final formattedValue = compactNumber(rod.toY);
return BarTooltipItem(formattedValue, textStyle);
// return BarTooltipItem('${rod.toY.round()}', textStyle);
},
),
touchCallback: (FlTouchEvent e, BarTouchResponse? r) async {
// optional: change state to highlight tapped group
if (r == null || r.spot == null) {
setState(() => touchedGroupIndex = -1);
return;
}
setState(
() => touchedGroupIndex = r.spot!.touchedBarGroupIndex,
);
final spot = r.spot!;
final groupIndex = spot.touchedBarGroupIndex;
final rodIndex = spot.touchedRodDataIndex;
final barData = widget.dataList[groupIndex];
final name = barData.chartName;
final val = barData.id;
final month = rodIndex == 1 ? 'current' : 'previous';
print('name - $name');
print('val - $val');
print('month - $month');
print('managerId - $managerId');
if (e is FlTapUpEvent) {
debugPrint(
'Tapped value → ${rodIndex == 1 ? barData.value1 : barData.value2} -> ${barData.id}-'
' ${rodIndex == 1 ? 'current' : 'previous'} -> ${barData.chartName}',
);
if (managerId != null) {
print('managerId - $managerId');
await apiService.generateChartExcel(
name,
val,
month,
managerId,
);
}
}
},
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
// interval: 10,
getTitlesWidget: (value, meta) {
// print("valuevalue - $value");
borderData: FlBorderData(
show: true,
border: Border(
bottom: BorderSide(
color: AppColors.contentColorBlack,
width: 0.1,
),
left: BorderSide(
color: AppColors.contentColorBlack,
width: 0.1,
),
),
),
gridData: FlGridData(
show: true,
// drawVerticalLine: true,
drawVerticalLine: false,
getDrawingHorizontalLine: (value) => FlLine(
// color: AppColors.gridLinesColor.withValues(alpha: 0.2),
color: Colors.blueGrey.shade100,
// color: Colors.black,
// color: AppColors.borderColor,
strokeWidth: 0.3,
),
),
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
axisNameWidget: widget.btnName != null
? Text(widget.btnName!)
: Text(''),
axisNameSize: 10,
sideTitles: SideTitles(
reservedSize: 30,
showTitles: true,
getTitlesWidget: (value, meta) {
if (value == 0) {
return const SizedBox.shrink(); // 👈 hide 0
}
final i = value.toInt();
if (i >= widget.labels.length) return const SizedBox();
final hasShortName =
widget.shortName != null &&
widget.shortName!.length > i &&
widget.shortName![i].trim().isNotEmpty;
final text = value.toInt().toString();
return SideTitleWidget(
meta: meta,
child: hasShortName
? Tooltip(
message: widget.shortName![i],
child: Transform.rotate(
angle: -1,
child: Padding(
padding: const EdgeInsets.only(
right: 3.0,
),
child: Text(
truncate(widget.labels[i], max: 5),
// widget.labels[i],
style: GoogleFonts.poppins(
fontSize: 9,
fontWeight: FontWeight.w500,
color: Color(0xFF1E293B),
),
textAlign: TextAlign.end,
),
),
),
)
: Tooltip(
message: widget.labels[i],
child: Transform.rotate(
angle: -1,
child: Padding(
padding: const EdgeInsets.only(
right: 3.0,
),
child: Text(
truncate(widget.labels[i], max: 5),
// widget.[i],
style: GoogleFonts.poppins(
fontSize: 9,
fontWeight: FontWeight.w400,
color: Color(0xFF1E293B),
),
textAlign: TextAlign.end,
),
),
),
),
);
},
),
),
final bool isLast = value == meta.max;
// if (isLast) {
// return const SizedBox.shrink(); // 👈 hide last label
// }
// print('value: $value');
// print('text: $text');
// print('length: ${text.length}');
// print('isLast: $isLast');
return Transform.rotate(
// angle: -3,
angle: 0,
child: Padding(
padding: const EdgeInsets.only(right: 3.0),
child: Text(
isLast ? '0' : value.toInt().toString(),
textAlign: TextAlign.end,
style: GoogleFonts.inter(
fontSize: 9,
color: isLast ? Colors.white : Colors.black,
fontWeight: isLast
? FontWeight.w100
: FontWeight.w400,
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
// interval: 10,
getTitlesWidget: (value, meta) {
// print("valuevalue - $value");
final text = value.toInt().toString();
final bool isLast = value == meta.max;
// if (isLast) {
// return const SizedBox.shrink(); // 👈 hide last label
// }
// print('value: $value');
// print('text: $text');
// print('length: ${text.length}');
// print('isLast: $isLast');
return Transform.rotate(
// angle: -3,
angle: 0,
child: Padding(
padding: const EdgeInsets.only(right: 3.0),
child: Text(
// isLast ? '0' : value.toInt().toString(),
isLast ? '0' : compactNumber(value),
textAlign: TextAlign.end,
style: GoogleFonts.inter(
fontSize: 9,
color: isLast ? Colors.white : Colors.black,
fontWeight: isLast
? FontWeight.w100
: FontWeight.w300,
),
),
),
),
);
},
reservedSize: 45,
);
},
reservedSize: 45,
),
),
rightTitles: const AxisTitles(),
topTitles: const AxisTitles(),
),
rightTitles: const AxisTitles(),
topTitles: const AxisTitles(),
barGroups: widget.dataList.asMap().entries.map((e) {
final index = e.key;
final data = e.value;
print('generateBarGroupBAR - $data');
return generateBarGroup(
index,
data.color,
data.value1,
data.value2,
data.shadowValue,
);
}).toList(),
groupsSpace: 12,
// maxY: widget.maxY,
maxY: (widget.dataList != null && widget.dataList!.isNotEmpty)
? widget.dataList!
.map(
(e) =>
e.value1 > e.value2 ? e.value1 : e.value2,
)
.reduce((a, b) => a > b ? a : b) *
1.15
: 20,
),
barGroups: widget.dataList.asMap().entries.map((e) {
final index = e.key;
final data = e.value;
print('generateBarGroupBAR - $data');
return generateBarGroup(
index,
data.color,
data.value1,
data.value2,
data.shadowValue,
);
}).toList(),
// maxY: widget.maxY,
maxY: (widget.dataList != null && widget.dataList!.isNotEmpty)
? widget.dataList!
.map(
(e) => e.value1 > e.value2 ? e.value1 : e.value2,
)
.reduce((a, b) => a > b ? a : b) +
15
: 20,
),
),
);
},
);
}
String truncate(String text, {int max = 6}) {
if (text.length <= max) return text;
return text.substring(0, max);
}
}

View File

@ -0,0 +1,401 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import '../../../core/services/api_service.dart';
import '../../../data/models/bar_data.dart';
import '../../providers/manager_provider.dart';
import 'appChartColors.dart';
class CustomBarChartHorizontal extends ConsumerStatefulWidget {
final List<BarData> dataList;
final List<String> labels;
final List<String>? shortName;
final String title;
final String? btnName;
final double maxY;
final int initialRotation;
const CustomBarChartHorizontal({
super.key,
required this.dataList,
required this.labels,
this.shortName,
this.btnName,
this.title = "Horizontal Bar Chart",
this.maxY = 20,
this.initialRotation = 1,
});
final shadowColor = const Color(0xFFCCCCCC);
@override
// State<CustomBarChartHorizontal> createState() => _CustomBarChartState();
ConsumerState<CustomBarChartHorizontal> createState() =>
_CustomBarChartState();
}
class _CustomBarChartState extends ConsumerState<CustomBarChartHorizontal> {
int touchedGroupIndex = -1;
late int rotationTurns;
dynamic managerId;
late ApiService apiService;
@override
void initState() {
super.initState();
apiService = ApiService();
rotationTurns = widget.initialRotation;
Future.microtask(() {
managerId = ref.read(managerIdProvider);
});
}
final indianFormatter = NumberFormat('#,##,##0', 'en_IN');
BarChartGroupData generateBarGroup(
int x,
Color color,
double value1,
double value2,
double shadowValue,
) {
return BarChartGroupData(
x: x,
groupVertically: false,
// showingTooltipIndicators: [x],
showingTooltipIndicators: const [1, 2],
// barsSpace: 30,
barRods: [
BarChartRodData(toY: 0, color: Colors.transparent, width: 10),
BarChartRodData(
toY: value1,
color: const Color(0xFF34a9a8),
// color: Colors.cyan.shade300,
width: 10,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(5),
topRight: Radius.circular(5),
),
// borderRadius: BorderRadius.circular(5),
),
// BarChartRodData(toY: 2, color: Colors.orange.shade300, width: 6),
BarChartRodData(
toY: value2,
color: const Color(0xFFA5E4E1),
// color: Colors.orange.shade300,
width: 10,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(5),
topRight: Radius.circular(5),
),
// borderRadius: BorderRadius.circular(5),
),
],
// showingTooltipIndicators: touchedGroupIndex == x ? [0] : [],
);
}
String compactNumber(num value) {
if (value >= 10000000) {
return '${(value / 10000000).toStringAsFixed(1).replaceAll('.0', '')}Cr';
} else if (value >= 1000000) {
return '${(value / 1000000).toStringAsFixed(1).replaceAll('.0', '')}M';
} else if (value >= 1000) {
return '${(value / 1000).toStringAsFixed(1).replaceAll('.0', '')}K';
} else {
return value.toInt().toString();
}
}
// Widget build(BuildContext context) {
// return AspectRatio(
// aspectRatio: 1.4,
//
// );
// }
double getChartWidth() {
const double barWidth = 40; // bar + spacing
double minWidth =
MediaQuery.of(context).size.width * 0.9; // minimum chart width
final calculatedWidth = widget.dataList.length * barWidth;
return calculatedWidth < minWidth ? minWidth : calculatedWidth;
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
return SizedBox(
// width: constraints.maxWidth,
// height: constraints.maxHeight,
width: getChartWidth(), // 👈 IMPORTANT
height: constraints.maxHeight,
child: BarChart(
BarChartData(
// alignment: BarChartAlignment.spaceBetween,
alignment: BarChartAlignment.start,
// verticalAxis: Axis.horizontal,
rotationQuarterTurns: rotationTurns,
barTouchData: BarTouchData(
enabled: true,
handleBuiltInTouches: true,
touchTooltipData: BarTouchTooltipData(
tooltipMargin: 6,
tooltipRoundedRadius: 6,
getTooltipColor: (group) {
return Colors.transparent; // 👈 tooltip background color
},
tooltipPadding: const EdgeInsets.symmetric(
horizontal: 7,
vertical: 2,
),
// rotateAngle: -85,
// ask fl_chart to fit tooltip inside available space
fitInsideVertically:
true, // if your fl_chart version supports it
fitInsideHorizontally:
true, // if your fl_chart version supports it
getTooltipItem:
(
BarChartGroupData group,
int groupIndex,
BarChartRodData rod,
int rodIndex,
) {
if (rod.toY == 0) {
return null;
}
// final textColor = rodIndex == 1
// ? Colors.cyan.shade500
// : Colors.orange.shade500;
// final textColor = Colors.black;
final textColor = rodIndex == 1
? Color(0xFF34a9a8)
: Color(0xFF020050);
final textStyle = GoogleFonts.poppins(
color: textColor,
fontSize: 8,
fontWeight: rodIndex == 1
? FontWeight.w600
: FontWeight.w400,
);
// final formattedValue = indianFormatter.format(
// rod.toY.round(),
// );
final formattedValue = compactNumber(rod.toY);
return BarTooltipItem(formattedValue, textStyle);
// return BarTooltipItem('${rod.toY.round()}', textStyle);
},
),
touchCallback: (FlTouchEvent e, BarTouchResponse? r) async {
// optional: change state to highlight tapped group
if (r == null || r.spot == null) {
setState(() => touchedGroupIndex = -1);
return;
}
setState(
() => touchedGroupIndex = r.spot!.touchedBarGroupIndex,
);
final spot = r.spot!;
final groupIndex = spot.touchedBarGroupIndex;
final rodIndex = spot.touchedRodDataIndex;
final barData = widget.dataList[groupIndex];
final name = barData.chartName;
final val = barData.id;
final month = rodIndex == 1 ? 'current' : 'previous';
print('name - $name');
print('val - $val');
print('month - $month');
print('managerId - $managerId');
if (e is FlTapUpEvent) {
debugPrint(
'Tapped value → ${rodIndex == 1 ? barData.value1 : barData.value2} -> ${barData.id}-'
' ${rodIndex == 1 ? 'current' : 'previous'} -> ${barData.chartName}',
);
if (managerId != null) {
print('managerId - $managerId');
await apiService.generateChartExcel(
name,
val,
month,
managerId,
);
}
}
},
),
borderData: FlBorderData(
show: true,
border: Border(
bottom: BorderSide(
color: AppColors.gridLinesColor,
// color: AppColors.contentColorBlack,
width: 0.1,
),
right: BorderSide(
color: AppColors.gridLinesColor,
// color: AppColors.contentColorBlack,
width: 0.1,
),
),
),
gridData: FlGridData(
show: true,
// drawVerticalLine: true,
drawVerticalLine: false,
getDrawingHorizontalLine: (value) => FlLine(
// color: AppColors.gridLinesColor.withValues(alpha: 0.2),
color: Colors.blueGrey.shade100,
// color: Colors.black,
// color: AppColors.borderColor,
strokeWidth: 0.3,
),
),
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
axisNameWidget: widget.btnName != null
? Text(widget.btnName!)
: Text(''),
axisNameSize: 10,
sideTitles: SideTitles(
// reservedSize: 90,
reservedSize: MediaQuery.of(context).size.width * 0.1,
showTitles: true,
getTitlesWidget: (value, meta) {
final i = value.toInt();
if (i >= widget.labels.length) return const SizedBox();
final hasShortName =
widget.shortName != null &&
widget.shortName!.length > i &&
widget.shortName![i].trim().isNotEmpty;
return SideTitleWidget(
meta: meta,
child: hasShortName
? Tooltip(
message: widget.shortName![i],
child: Transform.rotate(
angle: 0,
child: Text(
// widget.labels[i],
truncate(widget.labels[i], max: 5),
style: GoogleFonts.poppins(
fontSize: 9,
fontWeight: FontWeight.w600,
color: Color(0xFF1E293B),
),
textAlign: TextAlign.end,
),
),
)
: Transform.rotate(
angle: 0,
child: Text(
widget.labels[i],
style: GoogleFonts.poppins(
fontSize: 9,
fontWeight: FontWeight.w600,
color: Color(0xFF1E293B),
),
textAlign: TextAlign.end,
),
),
);
},
),
),
rightTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
// interval: 10,
getTitlesWidget: (value, meta) {
// print("valuevalue - $value");
final text = value.toInt().toString();
final bool isLast = value == meta.max;
// if (isLast) {
// return const SizedBox.shrink(); // 👈 hide last label
// }
// print('value: $value');
// print('text: $text');
// print('length: ${text.length}');
// print('isLast: $isLast');
return Transform.rotate(
angle: -3,
// angle: 0,
child: Padding(
padding: const EdgeInsets.only(right: 3.0),
child: Text(
// isLast ? '0' : value.toInt().toString(),
isLast ? '0' : compactNumber(value),
textAlign: TextAlign.end,
style: GoogleFonts.inter(
fontSize: 9,
// color: isLast ? Colors.white : Colors.black,
color: Colors.white,
fontWeight: isLast
? FontWeight.w100
: FontWeight.w400,
),
),
),
);
},
reservedSize: 5,
),
),
leftTitles: const AxisTitles(),
topTitles: const AxisTitles(),
),
barGroups: widget.dataList.asMap().entries.map((e) {
final index = e.key;
final data = e.value;
print('generateBarGroupBAR - $data');
return generateBarGroup(
index,
data.color,
data.value1,
data.value2,
data.shadowValue,
);
}).toList(),
groupsSpace: 12,
// maxY: widget.maxY,
maxY: (widget.dataList != null && widget.dataList!.isNotEmpty)
? widget.dataList!
.map(
(e) => e.value1 > e.value2 ? e.value1 : e.value2,
)
.reduce((a, b) => a > b ? a : b) *
1.15
: 20,
),
),
);
},
);
}
String truncate(String text, {int max = 6}) {
if (text.length <= max) return text;
return text.substring(0, max);
}
}