// custom_stacked_bar_chart.dart import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; import '../../../core/services/api_service.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 '../../providers/manager_provider.dart'; import 'appChartColors.dart'; class CustomStackedBarChart extends ConsumerStatefulWidget { final List labels; final List labelsId; final List dataList; final double maxY; final int initialRotation; final List>> productsList; final List>? vehicleTypeNames; final Map? vehicleColorMap; // Dynamic color mapping const CustomStackedBarChart({ super.key, required this.labels, required this.labelsId, required this.dataList, this.maxY = 100.0, this.initialRotation = 1, required this.productsList, this.vehicleTypeNames, this.vehicleColorMap, }); @override ConsumerState createState() => _CustomStackedBarChartState(); } class _CustomStackedBarChartState extends ConsumerState { late int rotationTurns; dynamic managerId; late ApiService apiService; @override void initState() { super.initState(); apiService = ApiService(); rotationTurns = widget.initialRotation; Future.microtask(() { managerId = ref.read(managerIdProvider); }); } 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 if (value >= 1) { // 👇 remove .0 ONLY when it's an integer return value % 1 == 0 ? value.toInt().toString() : value.toStringAsFixed(1); } else { // 👇 keep decimals for small values return value == 0 ? '0' : value.toStringAsFixed(1); } } // else { // return value.toInt().toString(); // } @override Widget build(BuildContext context) { final maxY = widget.maxY <= 0 ? 100.0 : widget.maxY; return AspectRatio( aspectRatio: 4, child: BarChart( BarChartData( 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, // 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), borderData: FlBorderData( show: true, border: Border( bottom: BorderSide( color: AppColors.contentColorBlack, width: 0.1, ), right: BorderSide(color: AppColors.contentColorBlack, width: 0.1), ), ), titlesData: FlTitlesData( bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 130, getTitlesWidget: (value, meta) { final i = value.toInt(); if (i < 0 || i >= widget.labels.length) { return const SizedBox(); } return SideTitleWidget( meta: meta, child: Text( widget.labels[i], style: GoogleFonts.poppins( fontSize: 10, fontWeight: FontWeight.w500, ), textAlign: TextAlign.end, ), ); }, ), ), rightTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 50, getTitlesWidget: (value, meta) { return Transform.rotate( angle: -3, child: Padding( padding: const EdgeInsets.only(right: 3.0), child: Text( compactNumber(value), // value.toInt().toString(), textAlign: TextAlign.end, style: GoogleFonts.inter(fontSize: 10), ), ), ); }, ), ), leftTitles: const AxisTitles(), topTitles: const AxisTitles(), ), 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, ), ); }, ), touchCallback: (event, response) async { if (response == null || response.spot == null) return; // ✅ ONLY handle clicks if (event is! FlTapUpEvent) return; final spot = response.spot!; final int groupIndex = spot.touchedBarGroupIndex; final int rodIndex = spot.touchedRodDataIndex; final int stackIndex = spot.touchedStackItemIndex!; final label = widget.vehicleTypeNames?[groupIndex][stackIndex] ?? 'Unknown'; final isCurrentMonth = rodIndex == 0; final monthLabel = isCurrentMonth ? 'Current' : 'Previous'; final categoryLabel = widget.labels[groupIndex]; final categoryLabelID = widget.labelsId[groupIndex]; debugPrint( 'Clicked stack → $label -> $monthLabel ->$managerId ->$categoryLabel->$categoryLabelID', ); await apiService.generatePerformanceDashboardExcel( 'StaffProduct', label, categoryLabelID, monthLabel, managerId, ); }, ), barGroups: widget.productsList.asMap().entries.map((entry) { int x = entry.key; List> rods = entry.value; List currentStack = []; List 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, ), ), ); } }