claims/endorsment salesExecChart

This commit is contained in:
venbaittech 2025-12-11 09:36:39 +05:30
parent 3477cd99f3
commit 8cc66eb233
11 changed files with 1061 additions and 505 deletions

File diff suppressed because one or more lines are too long

View File

@ -1166,6 +1166,47 @@ class ApiService {
// ----------------------------------- CLAIMS -------------------------------------------------
Future<Map<String, dynamic>> fetchPolicySearch(val) async {
// print(_token);
// print('rolemanagerId - $managerId');
if (_token == null) {
await _initializeToken();
}
dynamic url;
url = Uri.parse('${Env.apiUrl}policy/searchThePolicies?policy_number=$val');
final headers = {
'Authorization': 'Bearer $_token' ?? '',
'app-signature': Env.App_Signature,
};
final response = await _makeGetRequest(url, headers);
return response;
}
Future<Map<String, dynamic>> fetchVehicleSearch(val) async {
// print(_token);
// print('rolemanagerId - $managerId');
if (_token == null) {
await _initializeToken();
}
dynamic url;
url = Uri.parse(
'${Env.apiUrl}policy/searchThePolicies?vehicle_number=$val',
);
final headers = {
'Authorization': 'Bearer $_token' ?? '',
'app-signature': Env.App_Signature,
};
final response = await _makeGetRequest(url, headers);
return response;
}
Future<Map<String, dynamic>> fetchClaimList(int id, role) async {
// print(_token);
if (_token == null) {

View File

@ -26,7 +26,7 @@ class AddDialog extends StatefulWidget {
final dynamic managerId;
final dynamic role;
final void Function(String value) onSubmit;
final dynamic policyNumber;
// final dynamic policyNumber;
const AddDialog({
super.key,
required this.title,
@ -34,7 +34,7 @@ class AddDialog extends StatefulWidget {
required this.userId,
required this.managerId,
required this.role,
this.policyNumber,
// this.policyNumber,
});
@override
@ -69,6 +69,10 @@ class _AddDialogState extends State<AddDialog> {
List<String> tabHeader = ['policyNum', 'claimsDesc', 'remarks'];
final TextEditingController _vehicleSearchController =
TextEditingController();
final TextEditingController _policySearchController = TextEditingController();
final TextEditingController _searchStaffController = TextEditingController();
List<Map<String, dynamic>> getClaimsTypeData = [];
@ -86,6 +90,14 @@ class _AddDialogState extends State<AddDialog> {
List<Map<String, dynamic>> originalData = [];
List<Map<String, dynamic>> filteredData = [];
List<Map<String, dynamic>> getPolicyListData = [];
List<Map<String, dynamic>> originalPolicyData = [];
List<Map<String, dynamic>> filteredPolicyData = [];
List<Map<String, dynamic>> getVehicleListData = [];
List<Map<String, dynamic>> originalVehicleData = [];
List<Map<String, dynamic>> filteredVehicleData = [];
Map<String, dynamic> claimsDetails() {
final data = {
"policy_number": controllers["policyNum"]?.text,
@ -116,7 +128,7 @@ class _AddDialogState extends State<AddDialog> {
controllers[field] = TextEditingController();
}
controllers["policyNum"]?.text = widget.policyNumber;
// controllers["policyNum"]?.text = widget.policyNumber;
print('userIFF - ${widget.userId}');
print('roleIFF - ${widget.role}');
@ -136,6 +148,107 @@ class _AddDialogState extends State<AddDialog> {
getEnroementType();
}
Future<void> getPolicyListSearchData(String val) async {
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchPolicySearch(val);
if (response['status'] == 'success' && response['data'] != null) {
final dataList = List<Map<String, dynamic>>.from(response['data']);
setState(() {
getPolicyListData = dataList;
originalPolicyData = dataList;
filteredPolicyData = List.from(originalPolicyData);
});
} else {
setState(() {
getPolicyListData = [];
originalPolicyData = [];
filteredPolicyData = [];
});
}
} catch (e) {
print('Exception occurred: $e');
setState(() {
getPolicyListData = [];
originalPolicyData = [];
filteredPolicyData = [];
});
} finally {
setState(() {
isLoading = false;
});
}
}
Future<void> getPolicyListSearchData1(val) async {
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchPolicySearch(val);
if (response['status'] == 'success') {
print('E113 => getStaffListData => ${response['data']}');
setState(() {
getPolicyListData = List<Map<String, dynamic>>.from(response['data']);
// filteredData = getPolicyData.where((item) {
// return (item['agent_name'] ?? '-').toLowerCase() ||
// (item['reg_no'] ?? '-').toLowerCase;
// }).toList();
originalPolicyData = getPolicyListData;
filteredPolicyData = List.from(originalPolicyData);
// print('originalData - $getClaimPolicies');
});
} else {
getPolicyListData = [];
originalPolicyData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
Future<void> getVehicleListSearchData(String val) async {
setState(() {
isLoading = true;
});
try {
final response = await apiService.fetchVehicleSearch(val);
if (response['status'] == 'success') {
print('E113 => getStaffListData => ${response['data']}');
setState(() {
getVehicleListData = List<Map<String, dynamic>>.from(
response['data'],
);
originalVehicleData = getVehicleListData;
filteredVehicleData = List.from(originalVehicleData);
// print('originalData - $getClaimPolicies');
});
} else {
getVehicleListData = [];
originalVehicleData = [];
}
} catch (e) {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
});
}
}
Future<void> getStaffList(
dynamic managerId,
role, {
@ -473,20 +586,160 @@ class _AddDialogState extends State<AddDialog> {
),
const SizedBox(height: 16),
ThemedSearchField(
hintText: 'Search',
// backgroundColor: Color(0xFFF6F8F8),
backgroundColor: Color(0xFFFFFFFF),
txtHeight: 30,
onChanged: filterData,
controller: _searchStaffController,
txtwidth: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.26,
// txtwidth: ResponsiveLayout.isMobile(context)
// ? MediaQuery.of(context).size.width * 0.7
// : MediaQuery.of(context).size.width * 0.15,
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
children: [
ThemedSearchField(
hintText: 'Search By Vehicle Number',
backgroundColor: Color(0xFFFFFFFF),
txtHeight: 30,
controller: _vehicleSearchController,
onChanged: (val) async {
if (val.isNotEmpty) {
await getVehicleListSearchData(val);
} else {
setState(() {
// filteredVehicleData = List.from(originalVehicleData);
filteredVehicleData.clear();
});
}
},
txtwidth: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.26,
),
// Vehicle Search Results
if (filteredVehicleData.isNotEmpty)
Container(
width:
MediaQuery.of(context).size.width * 0.26, // optional
height: 150,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.blueGrey.shade100),
borderRadius: BorderRadius.circular(5),
),
child: ListView.builder(
itemCount: filteredVehicleData.length,
itemBuilder: (context, index) {
final vehicle = filteredVehicleData[index];
return InkWell(
onTap: () {
setState(() {
controllers['policyNum']?.text =
vehicle['policy_number'];
filteredVehicleData.clear();
});
},
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 3,
horizontal: 8,
),
// color: Colors.amber,
child: Text(
vehicle['policy_number'] ?? '-',
style: _textStyle1,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
);
},
),
),
],
),
SizedBox(width: 15),
Column(
children: [
ThemedSearchField(
hintText: 'Search By Policy Number',
backgroundColor: Color(0xFFFFFFFF),
txtHeight: 30,
controller: _policySearchController,
onChanged: (val) async {
if (val.isNotEmpty) {
await getPolicyListSearchData(val);
} else {
setState(() {
// filteredPolicyData = List.from(originalPolicyData);
filteredPolicyData.clear();
});
}
},
txtwidth: ResponsiveLayout.isMobile(context)
? null
: MediaQuery.of(context).size.width * 0.26,
),
// Policy Search Results
if (filteredPolicyData.isNotEmpty)
Container(
width:
MediaQuery.of(context).size.width * 0.26, // optional
height: 150,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.blueGrey.shade100),
borderRadius: BorderRadius.circular(5),
),
child: ListView.builder(
itemCount: filteredPolicyData.length,
itemBuilder: (context, index) {
final policy = filteredPolicyData[index];
return InkWell(
onTap: () {
setState(() {
controllers['policyNum']?.text =
policy['policy_number'];
filteredPolicyData.clear();
});
},
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 3,
horizontal: 8,
),
// color: Colors.amber,
child: Text(
policy['policy_number'] ?? '-',
style: _textStyle1,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
);
// ListTile(
// dense: true, // reduces row height
// contentPadding: EdgeInsets.symmetric(
// horizontal: 10,
// vertical: 2,
// ),
// subtitle: Text(
//
// style: _textStyle1,
// ),
// onTap: () {
// setState(() {
//
// // populate other fields as needed
// });
// },
// );
},
),
),
],
),
],
),
const SizedBox(height: 10),
const SizedBox(height: 16),
// 🔹 Switch content dynamically
@ -519,10 +772,24 @@ class _AddDialogState extends State<AddDialog> {
key: _formKeyClaims,
child: Column(
children: [
buildClaimType(context),
buildPolicyNumber(context),
buildUploadDocumentClaims(context),
buildClaimsDesc(context),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
buildClaimType(context),
SizedBox(width: 15),
buildPolicyNumber(context),
],
),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
buildUploadDocumentClaims(context),
SizedBox(width: 15),
buildClaimsDesc(context),
],
),
],
),
);
@ -534,9 +801,24 @@ class _AddDialogState extends State<AddDialog> {
key: _formKeyEndrosment,
child: Column(
children: [
buildEndrosType(context),
buildUploadDocumentEndorsment(context),
buildEndrosRemarks(context),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
buildEndrosType(context),
SizedBox(width: 15),
buildPolicyNumber(context),
],
),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
buildUploadDocumentClaims(context),
SizedBox(width: 15),
buildEndrosRemarks(context),
],
),
],
),
);
@ -547,7 +829,7 @@ class _AddDialogState extends State<AddDialog> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Policy Number", style: _textStyle),
Text("Policy Number *", style: _textStyle),
SizedBox(height: 10),
ThemedFormField(
controller: controllers['policyNum']!,
@ -566,11 +848,12 @@ class _AddDialogState extends State<AddDialog> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Claims Description *", style: _textStyle),
SizedBox(height: 10),
Text("Claims Description ", style: _textStyle),
// SizedBox(height: 10),
ThemedFormField(
backgroundColor: Color(0xFFEDF6F5),
controller: controllers['claimsDesc']!,
txtheight: 45,
validator: (value) => Validators.requiredField(value, "claimsDesc"),
txtwidth: ResponsiveLayout.isMobile(context)
? null
@ -860,6 +1143,7 @@ class _AddDialogState extends State<AddDialog> {
? null
: MediaQuery.of(context).size.width * 0.26,
keyboardType: TextInputType.multiline,
txtheight: 45,
),
],
);
@ -952,4 +1236,8 @@ class _AddDialogState extends State<AddDialog> {
fontSize: 12,
fontWeight: FontWeight.w600,
);
static final TextStyle _textStyle1 = TextStyle(
fontSize: 9,
fontWeight: FontWeight.w400,
);
}

View File

@ -581,7 +581,7 @@ class PolicyTabState extends ConsumerState<PolicyTab> {
role: null,
managerId: null,
title: "Endorsement",
policyNumber: selectedPolicyNumber,
// policyNumber: selectedPolicyNumber,
onSubmit: (value) {
debugPrint("New Endorsement: $value");
enrollKey.currentState?.getEndrosmentList(selectedPolicyNumber);
@ -608,7 +608,7 @@ class PolicyTabState extends ConsumerState<PolicyTab> {
title: "Claims",
role: null,
managerId: null,
policyNumber: selectedPolicyNumber,
// policyNumber: selectedPolicyNumber,
onSubmit: (value) {
debugPrint("New Claim: $value");
claimsKey.currentState?.getClaimsList(selectedPolicyNumber);

View File

@ -175,6 +175,10 @@ class claimListState extends ConsumerState<claimList> {
];
}
void refresh() {
getStaffList(managerId, userId);
}
@override
Widget build(BuildContext context) {
return MainLayout(
@ -303,10 +307,12 @@ class claimListState extends ConsumerState<claimList> {
userId: userId,
role: roleId,
title: "Claims",
policyNumber: 'POC_n1',
// policyNumber: 'POC_n1',
// policyNumber: selectedPolicyNumber,
onSubmit: (value) {
debugPrint("New Claims: $value");
refresh();
// enrollKey.currentState?.getEndrosmentList(selectedPolicyNumber);
// Update claim list or call API

View File

@ -111,6 +111,10 @@ class endosementState extends ConsumerState<endosement> {
return sortedData.sublist(startIndex, endIndex);
}
void refresh() {
getStaffList(managerId, roleId);
}
void filterData(String query) {
print("FilterDAta - $query");
setState(() {
@ -293,12 +297,12 @@ class endosementState extends ConsumerState<endosement> {
userId: userId,
role: roleId,
title: "Endorsement",
policyNumber: '1',
// policyNumber: '1',
// policyNumber: selectedPolicyNumber,
onSubmit: (value) {
debugPrint("New Endorsement: $value");
// enrollKey.currentState?.getEndrosmentList(selectedPolicyNumber);
refresh();
// Update claim list or call API
},
),

View File

@ -324,39 +324,46 @@ class _DashboardState extends ConsumerState<PartnerDashboard> {
),
child: Column(
children: [
Row(
children: [
Expanded(
child: Text(
'Performing Partner ( Top 50 )',
style: _chartHeader,
Padding(
padding: const EdgeInsets.only(
top: 8.0,
bottom: 16.0,
),
child: Row(
children: [
Expanded(
child: Text(
'Performing Partner ( Top 50 )',
style: _chartHeader,
),
),
),
Text(
isPerfomingAgntPolicy
? "Policies"
: "Premium",
style: _styleSmall1,
),
Transform.scale(
scale: 0.5,
child: Switch(
value: isPerfomingAgntPolicy,
activeColor: Colors.teal,
activeTrackColor: Colors.grey.shade200,
inactiveThumbColor: Colors.orange,
inactiveTrackColor: Colors.white,
onChanged: (val) {
setState(
() => isPerfomingAgntPolicy = val,
);
},
),
),
],
// Text(
// isPerfomingAgntPolicy
// ? "Policies"
// : "Premium",
// style: _styleSmall1,
// ),
//
// Transform.scale(
// scale: 0.5,
// child: Switch(
// value: isPerfomingAgntPolicy,
// activeColor: Colors.teal,
// activeTrackColor: Colors.grey.shade200,
// inactiveThumbColor: Colors.orange,
// inactiveTrackColor: Colors.white,
// onChanged: (val) {
// setState(
// () => isPerfomingAgntPolicy = val,
// );
// },
// ),
// ),
],
),
),
Expanded(
child: Performance(
title: "Performing Partner",
@ -518,20 +525,24 @@ class _PerformanceState extends State<Performance> {
style: _headerStyle,
),
),
(widget.title == 'Performing Partner')
? Expanded(
flex: 1,
child: Text(
widget.isPerfomingAgntPolicy
? "Policy Count"
: "Premium Count",
textAlign: TextAlign.center,
style: _headerStyle,
),
)
: SizedBox(),
if (widget.title == 'Performing Partner') ...[
Expanded(
flex: 1,
child: Text(
"Policy Count",
textAlign: TextAlign.center,
style: _headerStyle,
),
),
Expanded(
flex: 1,
child: Text(
"Premium Amount",
textAlign: TextAlign.center,
style: _headerStyle,
),
),
],
],
),
),
@ -586,30 +597,47 @@ class _PerformanceState extends State<Performance> {
),
),
(widget.title == 'Performing Partner')
? Expanded(
flex: 1,
child: Text(
isPerfomingAgntPolicy
? (row['policy_count']?.toString() ?? "0")
: (row['total_premium_amount']
?.toString() ??
"0"),
textAlign: TextAlign.center,
style: GoogleFonts.inter(
fontSize: 11,
fontWeight: FontWeight.w500,
// color: isPerfomingAgntPolicy
// ? Colors.black
// : (row['status']?.toString().toLowerCase() ==
// "active"
// ? Colors.green
// : Colors.red),
),
),
)
: SizedBox(),
if (widget.title == 'Performing Partner') ...[
Expanded(
flex: 1,
child: Text(
row['policy_count'] ?? "",
textAlign: TextAlign.center,
style: _tableDataStyle,
),
),
Expanded(
flex: 1,
child: Text(
row['total_premium_amount'] ?? "",
textAlign: TextAlign.center,
style: _tableDataStyle,
),
),
],
// (widget.title == 'Performing Partner')
// ? Expanded(
// flex: 1,
// child: Text(
// isPerfomingAgntPolicy
// ? (row['policy_count']?.toString() ?? "0")
// : (row['total_premium_amount']
// ?.toString() ??
// "0"),
// textAlign: TextAlign.center,
// style: GoogleFonts.inter(
// fontSize: 11,
// fontWeight: FontWeight.w500,
// // color: isPerfomingAgntPolicy
// // ? Colors.black
// // : (row['status']?.toString().toLowerCase() ==
// // "active"
// // ? Colors.green
// // : Colors.red),
// ),
// ),
// )
// : SizedBox(),
],
),
);

View File

@ -43,9 +43,14 @@ class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
bool showStaff = true;
bool showProduct = true;
int selectedStaffIndex = 0;
List<List<String>> staffVehicleTypeNames =
[]; // 👈 Add this at the top with other state variables
Map<String, Color> vehicleColors = {};
List<String> staffLabels = [];
// List<List<List<dynamic>>> staffChartData = [];
List<List<List<dynamic>>> staffChartData = [];
double maxPremium = 0;
int selectedView = 0;
@ -74,137 +79,171 @@ class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
});
}
// Add this helper method:
double _getSmartMaxValue() {
if (maxPremium <= 0) return 10;
// For policies (small numbers), ensure minimum scale
if (isInsureWisePolicy) {
if (maxPremium < 10) {
return 12; // Minimum scale of 10 for policies
}
// Round up to nearest 5
return ((maxPremium / 5).ceil() * 5).toDouble();
}
// For premium (large numbers)
return (maxPremium * 1.15).ceilToDouble(); // 15% headroom
}
Color _generateColorForVehicle(String vehicleType, int index) {
// Predefined colors for common vehicle types
const predefinedColors = [
Colors.blue,
Colors.green,
Colors.orange,
Colors.purple,
Colors.red,
Colors.teal,
Colors.pink,
Colors.indigo,
Colors.amber,
Colors.cyan,
Colors.lime,
Colors.deepOrange,
];
// If within predefined range, use those
if (index < predefinedColors.length) {
return predefinedColors[index];
}
// Otherwise generate using HSL for infinite colors
final hue = (index * 137.5) % 360; // Golden angle for distribution
return HSLColor.fromAHSL(1.0, hue, 0.6, 0.5).toColor();
}
Future<void> getProductivityDashBrdList(id) async {
// print('getClaimList called MagId - $managerId - $role');
setState(() {
isLoading = true;
});
// dynamic id = widget.policyNumber;
print('getProductivityDshID - $id');
try {
final response = await apiService.findProductivityDashboardData(
id,
SelctdProductivityproduct,
);
if (response['status'] == 'success') {
final data = response['data'];
print('getProductivityDshBdData - ${response['data']}');
if (response['status'] != 'success') {
setState(() {
if (data is Map<String, dynamic>) {
staffLabels.clear();
staffChartData.clear();
maxPremium = 0;
final staffWise = (data['StaffWise'] ?? []) as List<dynamic>;
staffLabels.clear();
staffChartData.clear();
maxPremium = 0.0;
double _toDouble(dynamic v) {
if (v == null) return 0.0;
if (v is double) return v;
if (v is int) return v.toDouble();
return double.tryParse(v.toString()) ?? 0.0;
}
for (var staff in staffWise) {
final name = staff['sales_executive_name']?.toString() ?? '';
staffLabels.add(name);
double totalPre = 0.0;
double totalCur = 0.0;
final products = (staff['products_list'] ?? []) as List<dynamic>;
for (var p in products) {
totalPre += _toDouble(p['total_premium_pre_month']);
totalCur += _toDouble(p['total_premium_current_month']);
}
// build 3 stacks: preMonth, currentMonth (stacked after pre), extra (difference optional)
final preStart = 0.0;
final preEnd = totalPre;
final curStart = preEnd;
final curEnd = preEnd + totalCur;
// optional extra stack (here zero, but kept for structure)
final extraStart = curEnd;
final extraEnd = curEnd; // no extra by default
// update chart max
maxPremium = [maxPremium, curEnd].reduce((a, b) => a > b ? a : b);
staffChartData.add([
[preStart, preEnd, Colors.cyan],
[curStart, curEnd, Colors.blueAccent],
// include extra only if needed >0
if ((extraEnd - extraStart) > 0)
[extraStart, extraEnd, Colors.grey],
]);
}
// use setState to save and pass to chart
setState(() {
originalStaffWise = List.from(staffWise); // if you need
staffLabels = staffLabels;
staffChartData = staffChartData;
// choose a little padding for maxY
final chartMax = (maxPremium * 1.1).ceilToDouble();
// store chartMax somewhere e.g. chartMaxY
});
setState(() {});
}
// if (data is List) {
// // Already a list of maps
// getProductivityDshBdData = List<Map<String, dynamic>>.from(data);
// } else if (data is Map) {
// // Single object, wrap in a list
// getProductivityDshBdData = [Map<String, dynamic>.from(data)];
// } else {
// getProductivityDshBdData = [];
// }
// // getProductivityDshBdData = List<Map<String, dynamic>>.from(response['data']);
// originalData = getProductivityDshBdData;
// filteredData = List.from(originalData);
// // print('originalData - $getClaimPolicies');
isLoading = false;
});
if (data is Map<String, dynamic>) {
setState(() {
// Store BrokerWise
productWise = List<Map<String, dynamic>>.from(
data['productWise'] ?? [],
);
// Store ProductWise
productWise = List<Map<String, dynamic>>.from(
data['productWise'] ?? [],
);
print('productWise - ${productWise}');
originalProductWise = List.from(productWise);
filteredProductWise = List.from(productWise);
// Store StaffWise
insurerWise = List<Map<String, dynamic>>.from(
data['insurerWise'] ?? [],
);
print('insurerWise - ${insurerWise}');
originalStaffWise = List.from(insurerWise);
filteredStaffWise = List.from(insurerWise);
});
} else {
// fallback
productWise = [];
productWise = [];
insurerWise = [];
}
} else {
getProductivityDshBdData = [];
originalData = [];
return;
}
final data = response['data'];
final staffWise = (data['StaffWise'] ?? []) as List<dynamic>;
print('data - $data');
print('staffWise - $staffWise');
// 🎨 STEP 1: Collect ALL unique vehicle types across all staff
Set<String> allVehicleTypes = {};
for (var staff in staffWise) {
final products = (staff['products_list'] ?? []) as List<dynamic>;
for (var p in products) {
final vehicleType = p['vechile_type']?.toString() ?? 'Unknown';
allVehicleTypes.add(vehicleType);
}
}
// 🎨 STEP 2: Generate dynamic colors for all vehicle types
Map<String, Color> dynamicVehicleColors = {};
int colorIndex = 0;
for (var vehicleType in allVehicleTypes) {
dynamicVehicleColors[vehicleType] = _generateColorForVehicle(
vehicleType,
colorIndex++,
);
}
print('Dynamic Vehicle Colors: $dynamicVehicleColors');
List<String> newLabels = [];
List<List<List<dynamic>>> newChartData = [];
List<List<String>> vehicleNames = [];
double newMax = 0.0;
double _toDouble(dynamic v) {
if (v == null) return 0.0;
if (v is double) return v;
if (v is int) return v.toDouble();
return double.tryParse(v.toString()) ?? 0.0;
}
for (var staff in staffWise) {
final name =
(staff['sales_executive_name'] ?? staff['short_name'])
?.toString() ??
'';
newLabels.add(name);
final products = (staff['products_list'] ?? []) as List<dynamic>;
List<List<dynamic>> currentRod = [];
List<List<dynamic>> previousRod = [];
List<String> staffVehicleNames = [];
double curStart = 0.0;
double preStart = 0.0;
for (var p in products) {
final cur = isInsureWisePolicy
? _toDouble(p['total_policies_current_month'])
: _toDouble(p['total_premium_current_month']);
final pre = isInsureWisePolicy
? _toDouble(p['total_policies_pre_month'])
: _toDouble(p['total_premium_pre_month']);
// Get vehicle type and its DYNAMIC color
final vehicleType = p['vechile_type']?.toString() ?? 'Unknown';
final color = dynamicVehicleColors[vehicleType] ?? Colors.grey;
staffVehicleNames.add(vehicleType);
// Add to current month rod with vehicle-specific color
currentRod.add([curStart, curStart + cur, color]);
curStart += cur;
// Add to previous month rod with lighter shade (60% opacity)
previousRod.add([preStart, preStart + pre, color.withOpacity(0.6)]);
preStart += pre;
}
newChartData.add([currentRod, previousRod]);
vehicleNames.add(staffVehicleNames);
newMax = [newMax, curStart, preStart].reduce((a, b) => a > b ? a : b);
}
setState(() {
originalStaffWise = staffWise
.map((e) => Map<String, dynamic>.from(e))
.toList();
filteredStaffWise = List<Map<String, dynamic>>.from(originalStaffWise);
staffLabels = newLabels;
staffChartData = newChartData;
staffVehicleTypeNames = vehicleNames;
vehicleColors = dynamicVehicleColors; // Save for legend
maxPremium = newMax;
productWise = List<Map<String, dynamic>>.from(
data['productWise'] ?? [],
);
insurerWise = List<Map<String, dynamic>>.from(
data['insurerWise'] ?? [],
);
});
} catch (e) {
print('Exception occurred: $e');
} finally {
@ -214,6 +253,31 @@ class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
}
}
List<String> buildStaffLabels() {
return filteredStaffWise.map<String>((item) {
final name = item['sales_executive_name'] ?? item['short_name'];
if (name != null && name.toString().trim().isNotEmpty) {
return name.toString();
} else {
return item['sales_executive_name']?.toString() ?? '';
}
}).toList();
}
List<Map<String, dynamic>> convertToProductList(List<dynamic> rawList) {
return rawList.map<Map<String, dynamic>>((item) {
return {
"vehicle_type": item[0],
"current_month": double.tryParse(item[1].toString()) ?? 0.0,
"previous_month": double.tryParse(item[2].toString()) ?? 0.0,
};
}).toList();
}
// In insurerContainer() you are passing staffLabels (which we now populate).
// Also set the chart maxY to some headroom:
// CustomStackedBarChart(..., maxY: (maxPremium * 1.1).ceilToDouble(), ...)
List<BarData> buildStaffBarData() {
double parseDouble(dynamic value) {
if (value == null) return 0.0;
@ -241,17 +305,6 @@ class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
}).toList();
}
List<String> buildStaffLabels() {
return filteredStaffWise.map<String>((item) {
final name = item['short_name'];
if (name != null && name.toString().trim().isNotEmpty) {
return name.toString();
} else {
return item['short_name']?.toString() ?? '';
}
}).toList();
}
List<BarData> buildProductBarData() {
double parseDouble(dynamic value) {
if (value == null) return 0.0;
@ -297,52 +350,53 @@ class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
// color: Colors.amber.shade100,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Hide and Show Charts : ', style: _styleSmall1),
Row(
children: [
Transform.scale(
scale: 0.75,
child: Checkbox(
value: showStaff,
onChanged: (val) {
setState(() => showStaff = val!);
},
),
),
Text(
// showStaff ? "Staff" : "Staff Hidden",
"Staff",
style: _styleSmall2,
),
],
),
// SizedBox(width: 10),
//
// Row(
// children: [
// Transform.scale(
// scale: 0.75,
// child: Checkbox(
// value: showProduct,
// onChanged: (val) {
// setState(() => showProduct = val!);
// },
// ),
// ),
// Text(
// // showBroker ? "Broker" : "Broker Hidden",
// "Product",
// style: _styleSmall2,
// ),
// ],
// ),
],
),
// Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Text('Hide and Show Charts : ', style: _styleSmall1),
//
// Row(
// children: [
// Transform.scale(
// scale: 0.75,
// child: Checkbox(
// value: showStaff,
// onChanged: (val) {
// setState(() => showStaff = val!);
// },
// ),
// ),
// Text(
// // showStaff ? "Staff" : "Staff Hidden",
// "Staff",
// style: _styleSmall2,
// ),
// ],
// ),
//
// // SizedBox(width: 10),
// //
// // Row(
// // children: [
// // Transform.scale(
// // scale: 0.75,
// // child: Checkbox(
// // value: showProduct,
// // onChanged: (val) {
// // setState(() => showProduct = val!);
// // },
// // ),
// // ),
// // Text(
// // // showBroker ? "Broker" : "Broker Hidden",
// // "Product",
// // style: _styleSmall2,
// // ),
// // ],
// // ),
// ],
// ),
SizedBox(height: 10),
Expanded(
child: Row(
@ -361,8 +415,7 @@ class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
Widget insurerContainer() {
final chartStaffData = buildStaffBarData();
final chartStaffLabels = buildStaffLabels();
print('chartStaffData - $chartStaffData');
print('chartStaffLabels - $chartStaffLabels');
return Container(
padding: EdgeInsets.all(10),
margin: EdgeInsets.only(right: 20),
@ -378,7 +431,7 @@ class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
children: [
Expanded(
child: Text(
'Staff Wise (Previous / Current Month)',
'Sales Executive Product Wise (Previous / Current Month)',
style: _chartHeader,
),
),
@ -390,99 +443,119 @@ class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
scale: 0.5,
child: Switch(
value: isInsureWisePolicy,
onChanged: (val) => setState(() => isInsureWisePolicy = val),
onChanged: (val) {
setState(() {
isInsureWisePolicy = val;
});
// 👇 ADD THIS: Rebuild chart data with new toggle value
getProductivityDashBrdList(managerId);
},
),
),
],
),
SizedBox(height: 10),
// Expanded(
// child: Container(
// clipBehavior: Clip.none,
// // color: Colors.red.shade100,
// child: CustomStackedBarChart(
// labels: staffLabels, // 👈 staff names
// maxY: maxPremium + 5000, // avoid touching border
// data: staffChartData,
// ),
// ),
// ),
Expanded(
child: CustomStackedBarChart(
labels: staffLabels,
data: staffChartData,
maxY: (maxPremium * 1.1).ceilToDouble(), // a bit of headroom
// maxY: (maxPremium * 1.1).ceilToDouble(),
maxY: _getSmartMaxValue(),
initialRotation: 1,
dataList: [],
productsList: staffChartData,
vehicleTypeNames:
staffVehicleTypeNames, // 👈 NEW: Pass vehicle names
),
),
legendWidget(),
Row(children: [Expanded(child: legendWidget())]),
],
),
);
}
Widget productContainer() {
final chartProductData = buildProductBarData();
final chartProductLabels = buildProductLabels();
print('chartProductData - $chartProductData');
print('chartProductLabels - $chartProductLabels');
return Container(
padding: EdgeInsets.all(10),
margin: EdgeInsets.only(right: 20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
'Staff Product Wise (Previous / Current Month)',
style: _chartHeader,
),
),
Text(
isProductWisePolicy ? "Policies" : "Premium",
style: _styleSmall1,
),
Transform.scale(
scale: 0.5,
child: Switch(
value: isProductWisePolicy,
onChanged: (val) => setState(() => isProductWisePolicy = val),
),
),
],
),
SizedBox(height: 10),
Expanded(
child: CustomBarChart(
dataList: chartProductData,
labels: chartProductLabels,
),
),
legendWidget(),
],
),
);
}
// Widget productContainer() {
// final chartProductData = buildProductBarData();
// final chartProductLabels = buildProductLabels();
// print('chartProductData - $chartProductData');
// print('chartProductLabels - $chartProductLabels');
// return Container(
// padding: EdgeInsets.all(10),
// margin: EdgeInsets.only(right: 20),
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(10),
// ),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Row(
// children: [
// Expanded(
// child: Text(
// 'Sales Executive Product Wise (Previous / Current Month)',
// style: _chartHeader,
// ),
// ),
// Text(
// isProductWisePolicy ? "Policies" : "Premium",
// style: _styleSmall1,
// ),
// Transform.scale(
// scale: 0.5,
// child: Switch(
// value: isProductWisePolicy,
// onChanged: (val) => setState(() => isProductWisePolicy = val),
// ),
// ),
// ],
// ),
// SizedBox(height: 10),
// Expanded(
// child: CustomBarChart(
// dataList: chartProductData,
// labels: chartProductLabels,
// ),
// ),
// legendWidget(),
// ],
// ),
// );
// }
Widget legendWidget() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
return Wrap(
spacing: 8,
runSpacing: 5,
alignment: WrapAlignment.center,
children: [
Icon(Icons.circle, size: 10, color: Colors.cyan.shade300),
SizedBox(width: 2),
Text('Current Month', style: _styleSmall1),
SizedBox(width: 5),
Icon(Icons.circle, size: 10, color: Colors.orange.shade300),
SizedBox(width: 2),
Text('Previous Month', style: _styleSmall1),
// Vehicle type legends (dynamically generated)
...vehicleColors.entries.map((entry) {
return _legendItem(entry.key, entry.value);
}),
// Separator
SizedBox(width: 10),
Container(height: 12, width: 1, color: Colors.grey.shade300),
SizedBox(width: 10),
// // Month legends
// _legendItem('Current (Solid)', Colors.black87),
// _legendItem('Previous (Light)', Colors.black38),
],
);
}
Widget _legendItem(String label, Color color) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 10,
height: 10,
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
),
SizedBox(width: 4),
Text(label, style: _styleSmall1),
],
);
}

View File

@ -639,6 +639,7 @@ class CreateProposal_QuickFormState
print("Response: ${response.body}");
// ToastHelper.showErrorToast(context, 'Proposal Created');
reset();
getQuickQuotationList(managerId);
// Navigator.of(context).pop();
} else if (response.statusCode == 403) {
@ -699,7 +700,8 @@ class CreateProposal_QuickFormState
print("tre1");
selectedId = null; // just update field
Navigator.of(context).pop();
final prefs = await SharedPreferences.getInstance();
await prefs.setString('QuoteKeyProvider', 'fromQuickQuote');
context.go('/enquiryForStaff');
print("tre11");
getQuickQuotationList(managerId);

View File

@ -59,6 +59,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
dynamic idPrimary;
dynamic managerId;
dynamic dashboardKey;
dynamic quickQuotationKey;
dynamic SelectedStatus;
dynamic SelectedStaffId;
String? selectedFileNames;
@ -79,6 +80,14 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
List<Map<String, dynamic>> originalData = [];
List<Map<String, dynamic>> filteredData = [];
bool isLoading = false;
bool isLoadingInsurers = false;
bool isLoadingInsuranceType = false;
bool isLoadingBroker = false;
bool isLoadingPaymentMode = false;
bool isLoadingStaffEnqAssign = false;
bool isLoadingStaffList = false;
bool isLoadingVehicleType = false;
bool isLoadingAgentList = false;
dynamic roleId;
bool _showFilterRow = false;
@ -139,6 +148,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
String? selectedId;
// int selectedIndex = 1;
int selectedIndex = 0;
int autoRefreshIndex = 0;
@ -238,11 +248,17 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
final prefs = await SharedPreferences.getInstance();
dashboardKey = prefs.getString('dashboardKeyProvider');
quickQuotationKey = prefs.getString('QuoteKeyProvider');
final dashboardStatus = prefs.getString('dashboardStatusProvider');
final dashboardStaffId = prefs.getString('dashboardStaffIdProvider');
print('handlerIdENQ - $handlerId');
print("C72 => r : $roleId | uId: $userId !mID : $managerId ");
print('quickQuotationKey');
if (quickQuotationKey == 'fromQuickQuote') {
print('quickQuotationKey - $quickQuotationKey');
callRefresh();
}
print('dashboardKey - $dashboardKey');
print('dashboardStatus - $dashboardStatus');
@ -421,7 +437,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
Future<void> getInsurers() async {
print('Insurers called');
setState(() {
isLoading = true;
isLoadingInsurers = true;
});
try {
@ -444,7 +460,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
isLoadingInsurers = false;
});
}
}
@ -452,7 +468,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
Future<void> getInsuranceType() async {
print('getClaimList called');
setState(() {
isLoading = true;
isLoadingInsuranceType = true;
});
try {
@ -477,7 +493,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
isLoadingInsuranceType = false;
});
}
}
@ -485,7 +501,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
Future<void> getBroker() async {
print('getBroker called');
setState(() {
isLoading = true;
isLoadingBroker = true;
});
try {
@ -508,7 +524,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
isLoadingBroker = false;
});
}
}
@ -516,7 +532,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
Future<void> getPaymentMode() async {
print('getPaymentMode called');
setState(() {
isLoading = true;
isLoadingPaymentMode = true;
});
try {
@ -541,7 +557,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
isLoadingPaymentMode = false;
});
}
}
@ -549,7 +565,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
Future<void> getStaffDetailsForEnquiryAssignment(int id) async {
print('getStaffDetailsForEnquiryAssignment called By handler');
setState(() {
isLoading = true;
isLoadingStaffEnqAssign = true;
});
try {
@ -578,7 +594,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
isLoadingStaffEnqAssign = false;
});
}
}
@ -593,7 +609,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
}) async {
print('DAJJshA72 => Fns called => $managerId | $role');
setState(() {
isLoading = true;
isLoadingStaffList = true;
});
dynamic fromDt;
@ -612,7 +628,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
final yesterday = today.subtract(Duration(days: 1));
fromDt = dateFormat.format(yesterday.subtract(Duration(days: 5)));
fromDt = dateFormat.format(yesterday.subtract(Duration(days: 2)));
toDt = dateFormat.format(yesterday);
print('PrevPending FROM TO - $fromDt - $toDt');
@ -635,7 +651,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
print("ENQ LIST PAGE");
final today = DateTime.now();
fromDt = dateFormat.format(today.subtract(Duration(days: 5)));
fromDt = dateFormat.format(today.subtract(Duration(days: 2)));
// fromDt = dateFormat.format(today);
toDt = dateFormat.format(today);
@ -694,13 +710,13 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
print('ToDate : $toDate');
setState(() {
if (SelectedStatus == 'Completed' || autoRefreshIndex == 1) {
selectedIndex = 1;
SelectedStatus = 'Completed';
} else {
selectedIndex = 0;
SelectedStatus = 'OtherThenCompleted';
}
// if (SelectedStatus == 'Completed' || autoRefreshIndex == 1) {
// selectedIndex = 1;
// SelectedStatus = 'Completed';
// } else {
// selectedIndex = 0;
// SelectedStatus = 'OtherThenCompleted';
// }
controllers['startDate']?.text = fromDate;
controllers['endDate']?.text = toDate;
if (data is List) {
@ -721,7 +737,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
isLoadingStaffList = false;
});
}
}
@ -1516,7 +1532,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
Future<void> getVehicleType() async {
print('getClaimList called');
setState(() {
isLoading = true;
isLoadingVehicleType = true;
});
try {
@ -1541,7 +1557,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
isLoadingVehicleType = false;
});
}
}
@ -1549,7 +1565,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
Future<void> getAgentList(id) async {
print('getAgentListData called');
setState(() {
isLoading = true;
isLoadingAgentList = true;
});
try {
@ -1573,7 +1589,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
print('Exception occurred: $e');
} finally {
setState(() {
isLoading = false;
isLoadingAgentList = false;
});
}
}
@ -3472,7 +3488,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
"created_on",
"agent_name",
"assigned_to_name",
"insurer_name",
"insurer_short_name",
"reg_no",
"insured_name",
"updated_on",
@ -4003,6 +4019,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
item['insurer_short_name'] != null
? item['insurer_short_name']
: '-',
style: _dataBoldthm2,
),
),
@ -4012,6 +4029,7 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
item['broker_name'] != null
? _truncateText(item['broker_name'], 8)
: '-',
style: _dataBoldthm4,
),
),
@ -4267,40 +4285,40 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
Material(
color: Colors.white,
child: InkWell(
onTap: null,
// hoverColor: Color(0xFFEAF6F4),
// onTap: (item['enquiry_status'] == 'Completed')
// ? () async {
// final prefs =
// await SharedPreferences.getInstance();
//
// // Remove old value (if any)
// await prefs.remove('enqStaffDataId');
//
// // Save the new id
// await prefs.setString(
// 'enqStaffDataId',
// id.toString(),
// );
//
// // Read it back if needed
// final dynamic? enqStaffDataId = prefs.getString(
// 'enqStaffDataId',
// );
//
// // Update provider too
// ref
// .read(quotationStaffIdProvider.notifier)
// .state =
// enqStaffDataId;
//
// buildPolicyCreatedStatusActions(
// context,
// status,
// item['id'],
// );
// }
// : null,
// onTap: null,
hoverColor: Color(0xFFEAF6F4),
onTap: (item['enquiry_status'] == 'Completed')
? () async {
final prefs =
await SharedPreferences.getInstance();
// Remove old value (if any)
await prefs.remove('enqStaffDataId');
// Save the new id
await prefs.setString(
'enqStaffDataId',
id.toString(),
);
// Read it back if needed
final dynamic? enqStaffDataId = prefs.getString(
'enqStaffDataId',
);
// Update provider too
ref
.read(quotationStaffIdProvider.notifier)
.state =
enqStaffDataId;
buildPolicyCreatedStatusActions(
context,
status,
item['id'],
);
}
: null,
child: Container(
padding: EdgeInsets.symmetric(vertical: 2.0),
margin: EdgeInsets.symmetric(vertical: 2),
@ -4963,6 +4981,10 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
print(
"❌ Cancel clicked. Ignoring old file.",
);
ToastHelper.showErrorToast(
context,
'Please upload a new file. No previous file exists.',
);
setState(() {
print('Clear Fields');
selectedFileNames =
@ -5257,15 +5279,15 @@ class EnquiryStaffState extends ConsumerState<EnquiryListStaffInline> {
setState(() {
selectedIndex = index;
if (selectedIndex == '1') {
getStaffList(
managerId,
roleId,
SelectedStatus: 'Completed',
);
} else {
getStaffList(managerId, roleId);
}
// if (selectedIndex == '1') {
// getStaffList(
// managerId,
// roleId,
// SelectedStatus: 'Completed',
// );
// } else {
// getStaffList(managerId, roleId);
// }
});
},
child: AnimatedContainer(

View File

@ -1,20 +1,38 @@
// custom_stacked_bar_chart.dart
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../../data/models/bar_data.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'appChartColors.dart';
class CustomStackedBarChart extends StatefulWidget {
final List<String> labels;
final List<List<List<dynamic>>>
data; // data[group][stackIndex] = [start, end, color]
final List<dynamic> dataList;
final double maxY;
final int initialRotation;
final List<List<List<dynamic>>> productsList;
final List<List<String>>? vehicleTypeNames;
final Map<String, Color>? vehicleColorMap; // Dynamic color mapping
const CustomStackedBarChart({
super.key,
required this.labels,
required this.data,
required this.dataList,
this.maxY = 100.0,
this.initialRotation = 1,
required this.productsList,
this.vehicleTypeNames,
this.vehicleColorMap,
});
@override
@ -30,89 +48,50 @@ class _CustomStackedBarChartState extends State<CustomStackedBarChart> {
rotationTurns = widget.initialRotation;
}
double _toDouble(dynamic v) {
if (v == null) return 0.0;
if (v is double) return v;
if (v is int) return v.toDouble();
return double.tryParse(v.toString()) ?? 0.0;
}
BarChartGroupData _generateGroup(int xIndex) {
// Each group is a single stacked rod (one staff)
final stacks = widget.data[xIndex];
final rodStackItems = <BarChartRodStackItem>[];
for (var s in stacks) {
// s expected: [start, end, color]
final start = _toDouble(s[0]);
final end = _toDouble(s[1]);
final color = (s.length > 2 && s[2] is Color)
? s[2] as Color
: Colors.grey;
rodStackItems.add(BarChartRodStackItem(start, end, color));
}
final maxEnd = rodStackItems.isNotEmpty
? rodStackItems.map((r) => r.toY).reduce((a, b) => a > b ? a : b)
: 0.0;
return BarChartGroupData(
x: xIndex,
groupVertically: false,
barRods: [
BarChartRodData(
toY: maxEnd,
width: 12,
borderRadius: BorderRadius.zero,
rodStackItems: rodStackItems,
),
BarChartRodData(
toY: maxEnd,
width: 12,
borderRadius: BorderRadius.zero,
rodStackItems: rodStackItems,
),
],
);
}
@override
Widget build(BuildContext context) {
final maxY = widget.maxY;
final maxY = widget.maxY <= 0 ? 100.0 : widget.maxY;
return AspectRatio(
aspectRatio: 1.6,
child: BarChart(
BarChartData(
alignment: BarChartAlignment.center,
alignment: BarChartAlignment.spaceAround,
rotationQuarterTurns: rotationTurns,
// gridData: FlGridData(
// show: true,
// checkToShowHorizontalLine: (value) {
// final step = maxY / 5;
// if (step == 0) return false;
// return (value % step).abs() < 0.0001;
// },
// getDrawingHorizontalLine: (value) =>
// FlLine(color: Colors.grey.withOpacity(0.12), strokeWidth: 0.8),
// drawVerticalLine: false,
//
// ),
gridData: FlGridData(
show: true,
checkToShowHorizontalLine: (value) => value % (maxY / 5) == 0,
getDrawingHorizontalLine: (value) =>
FlLine(color: Colors.grey.withOpacity(0.12), strokeWidth: 0.8),
// 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,
),
),
borderData: FlBorderData(show: false),
barTouchData: BarTouchData(
enabled: true,
touchTooltipData: BarTouchTooltipData(
tooltipRoundedRadius: 6,
tooltipPadding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 6,
// borderData: FlBorderData(show: false),
borderData: FlBorderData(
show: true,
border: Border(
bottom: BorderSide(
color: AppColors.contentColorBlack,
width: 0.1,
),
fitInsideHorizontally: true,
fitInsideVertically: true,
getTooltipItem: (group, groupIndex, rod, rodIndex) {
return BarTooltipItem(
'${rod.toY.toStringAsFixed(2)}',
GoogleFonts.poppins(
color: Colors.black,
fontSize: 12,
fontWeight: FontWeight.w600,
),
);
},
right: BorderSide(color: AppColors.contentColorBlack, width: 0.1),
),
),
titlesData: FlTitlesData(
@ -122,8 +101,9 @@ class _CustomStackedBarChartState extends State<CustomStackedBarChart> {
reservedSize: 56,
getTitlesWidget: (value, meta) {
final i = value.toInt();
if (i < 0 || i >= widget.labels.length)
if (i < 0 || i >= widget.labels.length) {
return const SizedBox();
}
return SideTitleWidget(
meta: meta,
child: Text(
@ -141,11 +121,18 @@ class _CustomStackedBarChartState extends State<CustomStackedBarChart> {
rightTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
reservedSize: 50,
getTitlesWidget: (value, meta) {
return Text(
value.toInt().toString(),
style: GoogleFonts.inter(fontSize: 9),
return Transform.rotate(
angle: -3,
child: Padding(
padding: const EdgeInsets.only(right: 3.0),
child: Text(
value.toInt().toString(),
textAlign: TextAlign.end,
style: GoogleFonts.inter(fontSize: 10),
),
),
);
},
),
@ -153,10 +140,115 @@ class _CustomStackedBarChartState extends State<CustomStackedBarChart> {
leftTitles: const AxisTitles(),
topTitles: const AxisTitles(),
),
barGroups: List.generate(
widget.data.length,
(i) => _generateGroup(i),
barTouchData: BarTouchData(
enabled: true,
touchTooltipData: BarTouchTooltipData(
tooltipRoundedRadius: 8,
tooltipPadding: const EdgeInsets.all(8),
tooltipMargin: 8,
fitInsideHorizontally: true,
fitInsideVertically: true,
getTooltipItem: (group, groupIndex, rod, rodIndex) {
// Determine if this is current or previous month
final isCurrentMonth = rodIndex == 0;
final monthLabel = isCurrentMonth ? 'Current' : 'Previous';
// Calculate total for this rod
final total = rod.rodStackItems.isNotEmpty
? rod.rodStackItems
.map((s) => s.toY - s.fromY)
.reduce((a, b) => a + b)
: 0.0;
// Build detailed breakdown
String breakdown = '';
if (rod.rodStackItems.isNotEmpty) {
for (int i = 0; i < rod.rodStackItems.length; i++) {
final item = rod.rodStackItems[i];
final value = item.toY - item.fromY;
if (value > 0) {
// Try to get vehicle type name if provided
String vehicleName = 'Item ${i + 1}';
if (widget.vehicleTypeNames != null &&
groupIndex < widget.vehicleTypeNames!.length &&
i < widget.vehicleTypeNames![groupIndex].length) {
vehicleName = widget.vehicleTypeNames![groupIndex][i];
}
breakdown +=
'\n$vehicleName: ${value.toStringAsFixed(0)}';
}
}
}
return BarTooltipItem(
'$monthLabel Month\nTotal: ${total.toStringAsFixed(0)}$breakdown',
textAlign: TextAlign.start,
GoogleFonts.poppins(
color: Colors.white,
fontSize: 11,
fontWeight: FontWeight.w500,
),
);
},
),
),
barGroups: widget.productsList.asMap().entries.map((entry) {
int x = entry.key;
List<List<dynamic>> rods = entry.value;
List<BarChartRodStackItem> currentStack = [];
List<BarChartRodStackItem> previousStack = [];
double cumCurrent = 0;
// double cumPrev = 25000;
double cumPrev = 0;
// Current month
for (var item in rods[0]) {
double value = (item[1] - item[0]).toDouble();
currentStack.add(
BarChartRodStackItem(
cumCurrent,
cumCurrent + value,
item[2] as Color? ?? Colors.grey,
),
);
cumCurrent += value;
}
// Previous month
for (var item in rods[1]) {
double value = (item[1] - item[0]).toDouble();
previousStack.add(
BarChartRodStackItem(
cumPrev,
cumPrev + value,
item[2] as Color? ?? Colors.grey,
),
);
cumPrev += value;
}
return BarChartGroupData(
x: x,
groupVertically: false,
barRods: [
BarChartRodData(
toY: cumCurrent,
width: 16,
rodStackItems: currentStack,
borderRadius: BorderRadius.zero,
),
BarChartRodData(
toY: cumPrev,
width: 16,
rodStackItems: previousStack,
borderRadius: BorderRadius.zero,
),
],
);
}).toList(),
maxY: maxY,
),
),