482 lines
14 KiB
Dart
482 lines
14 KiB
Dart
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/src/widgets/framework.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:nhance_partner/presentation/providers/manager_provider.dart';
|
|
|
|
import '../../../../core/services/api_service.dart';
|
|
import '../../../../data/models/bar_data.dart';
|
|
import '../../../providers/userRoleProvider.dart';
|
|
import '../../../themes/charts/barChart.dart';
|
|
import '../../../themes/charts/stackedBarChart.dart';
|
|
|
|
class ProductivityDashboard extends ConsumerStatefulWidget {
|
|
const ProductivityDashboard({super.key});
|
|
|
|
@override
|
|
ConsumerState<ProductivityDashboard> createState() =>
|
|
_ProductivityDashboardState();
|
|
}
|
|
|
|
class _ProductivityDashboardState extends ConsumerState<ProductivityDashboard> {
|
|
dynamic managerId;
|
|
late ApiService apiService;
|
|
|
|
// List<Map<String, dynamic>> dataVal = [];
|
|
List<Map<String, dynamic>> getProductivityDshBdData = [];
|
|
List<Map<String, dynamic>> originalData = [];
|
|
List<Map<String, dynamic>> filteredData = [];
|
|
|
|
List<Map<String, dynamic>> productWise = [];
|
|
List<Map<String, dynamic>> insurerWise = [];
|
|
|
|
List<Map<String, dynamic>> originalProductWise = [];
|
|
List<Map<String, dynamic>> filteredProductWise = [];
|
|
|
|
List<Map<String, dynamic>> originalStaffWise = [];
|
|
List<Map<String, dynamic>> filteredStaffWise = [];
|
|
|
|
bool isLoading = false;
|
|
bool isInsureWisePolicy = true;
|
|
bool isProductWisePolicy = true;
|
|
|
|
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<String> staffLabelsId = [];
|
|
// List<List<List<dynamic>>> staffChartData = [];
|
|
List<List<List<dynamic>>> staffChartData = [];
|
|
|
|
double maxPremium = 0;
|
|
|
|
int selectedView = 0;
|
|
// 0 = Staff
|
|
// 1 = Broker
|
|
// 2 = Product
|
|
|
|
String? SelctdProductivityproduct;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
apiService = ApiService();
|
|
|
|
Future.microtask(() {
|
|
// final id = ref.read(managerIdProvider);
|
|
final roleId = ref.read(userRoleProvider);
|
|
managerId = ref.read(managerIdProvider);
|
|
|
|
// print("ENQroleId - $roleId");
|
|
|
|
if (managerId != null) {
|
|
getProductivityDashBrdList(managerId);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 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 {
|
|
setState(() {
|
|
isLoading = true;
|
|
});
|
|
|
|
try {
|
|
final response = await apiService.findProductivityDashboardData(
|
|
id,
|
|
SelctdProductivityproduct,
|
|
);
|
|
|
|
if (response['status'] != 'success') {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
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<String> newLabelsId = [];
|
|
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 id = (staff['sales_executive_id'])?.toString() ?? '';
|
|
newLabelsId.add(id);
|
|
|
|
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;
|
|
staffLabelsId = newLabelsId;
|
|
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 {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return SelectionArea(
|
|
child: Container(
|
|
margin: EdgeInsets.only(left: 10, right: 10, bottom: 10),
|
|
// 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,
|
|
// // ),
|
|
// // ],
|
|
// // ),
|
|
// ],
|
|
// ),
|
|
SizedBox(height: 10),
|
|
|
|
Expanded(
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
showStaff ? Expanded(child: insurerContainer()) : SizedBox(),
|
|
// showProduct ? Expanded(child: productContainer()) : SizedBox(),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
);
|
|
}
|
|
|
|
Widget insurerContainer() {
|
|
return Card(
|
|
margin: const EdgeInsets.only(right: 10),
|
|
color: Colors.white,
|
|
|
|
shadowColor: Colors.black,
|
|
elevation: 1, // card shadow
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
|
|
clipBehavior: Clip.antiAlias,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(10.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
'Sales Executive Product Wise (Previous / Current Month)',
|
|
style: _chartHeader,
|
|
),
|
|
),
|
|
Text(
|
|
isInsureWisePolicy ? "Policies" : "Premium",
|
|
style: _styleSmall1,
|
|
),
|
|
Transform.scale(
|
|
scale: 0.5,
|
|
child: Switch(
|
|
activeColor: Colors.teal,
|
|
activeTrackColor: Colors.grey.shade200,
|
|
inactiveThumbColor: Colors.orange,
|
|
inactiveTrackColor: Colors.white,
|
|
value: isInsureWisePolicy,
|
|
onChanged: (val) {
|
|
setState(() {
|
|
isInsureWisePolicy = val;
|
|
});
|
|
// 👇 ADD THIS: Rebuild chart data with new toggle value
|
|
getProductivityDashBrdList(managerId);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 10),
|
|
Container(
|
|
// color: Colors.amber.shade100,
|
|
child: Expanded(
|
|
child: CustomStackedBarChart(
|
|
labels: staffLabels,
|
|
labelsId: staffLabelsId,
|
|
// maxY: (maxPremium * 1.1).ceilToDouble(),
|
|
maxY: _getSmartMaxValue(),
|
|
initialRotation: 1,
|
|
dataList: [],
|
|
productsList: staffChartData,
|
|
vehicleTypeNames:
|
|
staffVehicleTypeNames, // 👈 NEW: Pass vehicle names
|
|
),
|
|
),
|
|
),
|
|
Row(children: [Expanded(child: legendWidget())]),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget legendWidget() {
|
|
return Wrap(
|
|
spacing: 8,
|
|
runSpacing: 5,
|
|
alignment: WrapAlignment.center,
|
|
children: [
|
|
// 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),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
final _headerStyle2 = GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
color: Colors.black,
|
|
fontWeight: FontWeight.w500,
|
|
);
|
|
final _chartHeader = GoogleFonts.poppins(
|
|
fontSize: 11.5,
|
|
color: Colors.black,
|
|
fontWeight: FontWeight.w500,
|
|
);
|
|
final _styleSmall1 = GoogleFonts.poppins(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.w400,
|
|
);
|
|
|
|
final _styleSmall2 = GoogleFonts.poppins(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.w400,
|
|
color: Colors.blueGrey,
|
|
);
|