diff --git a/lib/controller/dashboard/dashboard_controller.dart b/lib/controller/dashboard/dashboard_controller.dart index 3fe4e65..03e4d56 100644 --- a/lib/controller/dashboard/dashboard_controller.dart +++ b/lib/controller/dashboard/dashboard_controller.dart @@ -61,8 +61,10 @@ class DashboardController extends GetxController { final RxBool isExpenseTypeReportLoading = false.obs; final Rx expenseTypeReportData = Rx(null); - final Rx expenseReportStartDate = DateTime.now().obs; - final Rx expenseReportEndDate = DateTime.now().obs; + final Rx expenseReportStartDate = + DateTime.now().subtract(const Duration(days: 15)).obs; +final Rx expenseReportEndDate = DateTime.now().obs; + @override void onInit() { diff --git a/lib/helpers/utils/utils.dart b/lib/helpers/utils/utils.dart index 10ae5c5..8a72760 100644 --- a/lib/helpers/utils/utils.dart +++ b/lib/helpers/utils/utils.dart @@ -45,6 +45,10 @@ class Utils { return "$hour:$minute${showSecond ? ":" : ""}$second$meridian"; } + static String formatDate(DateTime date) { + return DateFormat('d MMM yyyy').format(date); + } + static String getDateTimeStringFromDateTime(DateTime dateTime, {bool showSecond = true, bool showDate = true, diff --git a/lib/helpers/widgets/dashbaord/expense_breakdown_chart.dart b/lib/helpers/widgets/dashbaord/expense_breakdown_chart.dart index 17a79b3..02ae458 100644 --- a/lib/helpers/widgets/dashbaord/expense_breakdown_chart.dart +++ b/lib/helpers/widgets/dashbaord/expense_breakdown_chart.dart @@ -4,14 +4,16 @@ import 'package:syncfusion_flutter_charts/charts.dart'; import 'package:marco/controller/dashboard/dashboard_controller.dart'; import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/model/dashboard/expense_type_report_model.dart'; - import 'package:marco/helpers/utils/utils.dart'; +import 'package:marco/helpers/widgets/my_custom_skeleton.dart'; + class ExpenseTypeReportChart extends StatelessWidget { ExpenseTypeReportChart({Key? key}) : super(key: key); final DashboardController _controller = Get.find(); + // Extended color palette for multiple projects static const List _flatColors = [ Color(0xFFE57373), // Red 300 Color(0xFF64B5F6), // Blue 300 @@ -48,35 +50,67 @@ class ExpenseTypeReportChart extends StatelessWidget { ]; Color _getSeriesColor(int index) => _flatColors[index % _flatColors.length]; + @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; + final isMobile = screenWidth < 600; return Obx(() { final isLoading = _controller.isExpenseTypeReportLoading.value; final data = _controller.expenseTypeReportData.value; return Container( - decoration: _containerDecoration, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(5), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.05), + blurRadius: 6, + spreadRadius: 1, + offset: const Offset(0, 2), + ), + ], + ), padding: EdgeInsets.symmetric( - vertical: 16, - horizontal: screenWidth < 600 ? 8 : 20, + vertical: isMobile ? 16 : 20, + horizontal: isMobile ? 12 : 20, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _Header(), + // Chart Header + isLoading + ? SkeletonLoaders.dateSkeletonLoader() + : _ChartHeader(controller: _controller), + const SizedBox(height: 12), - // 👇 replace Expanded with fixed height + + // Date Range Picker + isLoading + ? Row( + children: [ + Expanded(child: SkeletonLoaders.dateSkeletonLoader()), + const SizedBox(width: 8), + Expanded(child: SkeletonLoaders.dateSkeletonLoader()), + ], + ) + : _DateRangePicker(controller: _controller), + + const SizedBox(height: 16), + + // Chart Area SizedBox( - height: 350, // choose based on your design + height: isMobile ? 350 : 400, child: isLoading - ? const Center(child: CircularProgressIndicator()) - : data == null || data.report.isEmpty + ? SkeletonLoaders.chartSkeletonLoader() + : (data == null || data.report.isEmpty) ? const _NoDataMessage() - : _ExpenseChart( + : _ExpenseDonutChart( data: data, getSeriesColor: _getSeriesColor, + isMobile: isMobile, ), ), ], @@ -84,146 +118,510 @@ class ExpenseTypeReportChart extends StatelessWidget { ); }); } - - BoxDecoration get _containerDecoration => BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(5), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.05), - blurRadius: 6, - spreadRadius: 1, - offset: const Offset(0, 2), - ), - ], - ); } -class _Header extends StatelessWidget { - const _Header({Key? key}) : super(key: key); +// ----------------------------------------------------------------------------- +// Chart Header +// ----------------------------------------------------------------------------- +class _ChartHeader extends StatelessWidget { + const _ChartHeader({Key? key, required this.controller}) : super(key: key); + + final DashboardController controller; @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MyText.bodyMedium('Expense Type Overview', fontWeight: 700), - const SizedBox(height: 2), - MyText.bodySmall( - 'Project-wise approved, pending, rejected & processed expenses', - color: Colors.grey), - ], - ); + return Obx(() { + final data = controller.expenseTypeReportData.value; + // Calculate total from totalApprovedAmount only + final total = data?.report.fold( + 0, + (sum, e) => sum + e.totalApprovedAmount, + ) ?? + 0; + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MyText.bodyMedium('Project Expense Analytics', fontWeight: 700), + const SizedBox(height: 2), + MyText.bodySmall('Approved expenses by project', + color: Colors.grey), + ], + ), + ), + if (total > 0) + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.blueAccent.withOpacity(0.15), + borderRadius: BorderRadius.circular(5), + border: Border.all(color: Colors.blueAccent, width: 1), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + MyText.bodySmall( + 'Total Approved', + color: Colors.blueAccent, + fontSize: 10, + ), + MyText.bodyMedium( + Utils.formatCurrency(total), + color: Colors.blueAccent, + fontWeight: 700, + fontSize: 14, + ), + ], + ), + ), + ], + ); + }); } } -// No data -class _NoDataMessage extends StatelessWidget { - const _NoDataMessage({Key? key}) : super(key: key); +// ----------------------------------------------------------------------------- +// Date Range Picker +// ----------------------------------------------------------------------------- +class _DateRangePicker extends StatelessWidget { + const _DateRangePicker({Key? key, required this.controller}) + : super(key: key); + + final DashboardController controller; + + Future _selectDate( + BuildContext context, bool isStartDate, DateTime currentDate) async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: currentDate, + firstDate: DateTime(2020), + lastDate: DateTime.now(), + builder: (context, child) { + return Theme( + data: Theme.of(context).copyWith( + colorScheme: const ColorScheme.light( + primary: Colors.blueAccent, + onPrimary: Colors.white, + onSurface: Colors.black, + ), + ), + child: child!, + ); + }, + ); + + if (picked != null) { + if (isStartDate) { + controller.expenseReportStartDate.value = picked; + } else { + controller.expenseReportEndDate.value = picked; + } + } + } @override Widget build(BuildContext context) { - return SizedBox( - height: 200, - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.info_outline, color: Colors.grey.shade400, size: 48), - const SizedBox(height: 10), - MyText.bodyMedium( - 'No expense data available.', - textAlign: TextAlign.center, - color: Colors.grey.shade500, + return Obx(() { + final startDate = controller.expenseReportStartDate.value; + final endDate = controller.expenseReportEndDate.value; + + return Row( + children: [ + _DateBox( + label: 'Start Date', + date: startDate, + onTap: () => _selectDate(context, true, startDate), + icon: Icons.calendar_today_outlined, + ), + const SizedBox(width: 8), + _DateBox( + label: 'End Date', + date: endDate, + onTap: () => _selectDate(context, false, endDate), + icon: Icons.event_outlined, + ), + ], + ); + }); + } +} + +class _DateBox extends StatelessWidget { + final String label; + final DateTime date; + final VoidCallback onTap; + final IconData icon; + + const _DateBox({ + Key? key, + required this.label, + required this.date, + required this.onTap, + required this.icon, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Expanded( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(5), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + decoration: BoxDecoration( + color: Colors.blueAccent.withOpacity(0.08), + border: Border.all(color: Colors.blueAccent.withOpacity(0.3)), + borderRadius: BorderRadius.circular(5), ), - ], + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: Colors.blueAccent.withOpacity(0.15), + borderRadius: BorderRadius.circular(4), + ), + child: Icon( + icon, + size: 14, + color: Colors.blueAccent, + ), + ), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: TextStyle( + fontSize: 10, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 2), + Text( + Utils.formatDate(date), + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.blueAccent, + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + ), ), ), ); } } -// Chart -class _ExpenseChart extends StatelessWidget { - const _ExpenseChart({ +// ----------------------------------------------------------------------------- +// No Data Message +// ----------------------------------------------------------------------------- +class _NoDataMessage extends StatelessWidget { + const _NoDataMessage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.donut_large_outlined, + color: Colors.grey.shade400, size: 48), + const SizedBox(height: 10), + MyText.bodyMedium( + 'No expense data available for this range.', + textAlign: TextAlign.center, + color: Colors.grey.shade500, + ), + ], + ), + ); + } +} + +// ----------------------------------------------------------------------------- +// Donut Chart +// ----------------------------------------------------------------------------- +class _ExpenseDonutChart extends StatefulWidget { + const _ExpenseDonutChart({ Key? key, required this.data, required this.getSeriesColor, + required this.isMobile, }) : super(key: key); final ExpenseTypeReportData data; final Color Function(int index) getSeriesColor; + final bool isMobile; + + @override + State<_ExpenseDonutChart> createState() => _ExpenseDonutChartState(); +} + +class _ExpenseDonutChartState extends State<_ExpenseDonutChart> { + late TooltipBehavior _tooltipBehavior; + late SelectionBehavior _selectionBehavior; + + @override + void initState() { + super.initState(); + _tooltipBehavior = TooltipBehavior( + enable: true, + format: 'point.x: point.y', + color: Colors.blueAccent, + textStyle: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w600, + ), + elevation: 4, + animationDuration: 300, + ); + + _selectionBehavior = SelectionBehavior( + enable: true, + selectedColor: Colors.white, + selectedBorderColor: Colors.blueAccent, + selectedBorderWidth: 3, + unselectedOpacity: 0.5, + ); + } @override Widget build(BuildContext context) { - final List> chartSeries = [ - { - 'name': 'Approved', - 'color': getSeriesColor(0), - 'yValue': (ExpenseTypeReportItem e) => e.totalApprovedAmount, - }, - { - 'name': 'Pending', - 'color': getSeriesColor(1), - 'yValue': (ExpenseTypeReportItem e) => e.totalPendingAmount, - }, - { - 'name': 'Rejected', - 'color': getSeriesColor(2), - 'yValue': (ExpenseTypeReportItem e) => e.totalRejectedAmount, - }, - { - 'name': 'Processed', - 'color': getSeriesColor(3), - 'yValue': (ExpenseTypeReportItem e) => e.totalProcessedAmount, - }, - ]; + // Create donut data from project items using totalApprovedAmount + final List<_DonutData> donutData = widget.data.report + .asMap() + .entries + .map((entry) => _DonutData( + entry.value.projectName.isEmpty + ? 'Project ${entry.key + 1}' + : entry.value.projectName, + entry.value.totalApprovedAmount, + widget.getSeriesColor(entry.key), + Icons.folder_outlined, + )) + .toList(); - return SfCartesianChart( - tooltipBehavior: TooltipBehavior( - enable: true, - shared: true, - builder: (data, point, series, pointIndex, seriesIndex) { - final ExpenseTypeReportItem item = data; - final value = chartSeries[seriesIndex]['yValue'](item); - return Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.black87, - borderRadius: BorderRadius.circular(6), + // Filter out zero values for cleaner visualization + final filteredData = donutData.where((data) => data.value > 0).toList(); + + if (filteredData.isEmpty) { + return const Center( + child: Text( + 'No approved expense data for the selected range.', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 14, color: Colors.grey), + ), + ); + } + + // Calculate total for center display + final total = filteredData.fold(0, (sum, item) => sum + item.value); + + return Column( + children: [ + Expanded( + child: SfCircularChart( + margin: EdgeInsets.zero, + legend: Legend( + isVisible: true, + position: LegendPosition.bottom, + overflowMode: LegendItemOverflowMode.wrap, + textStyle: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + ), + iconHeight: 10, + iconWidth: 10, + itemPadding: widget.isMobile ? 6 : 10, + padding: widget.isMobile ? 10 : 14, ), - child: Text( - '${chartSeries[seriesIndex]['name']}: ${Utils.formatCurrency(value)}', - style: const TextStyle(color: Colors.white, fontSize: 12), + tooltipBehavior: _tooltipBehavior, + // Center annotation showing total approved amount + annotations: [ + CircularChartAnnotation( + widget: Container( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.check_circle_outline, + color: Colors.green.shade600, + size: widget.isMobile ? 28 : 32, + ), + const SizedBox(height: 6), + Text( + 'Total Approved', + style: TextStyle( + fontSize: widget.isMobile ? 11 : 12, + color: Colors.grey.shade600, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 4), + Text( + Utils.formatCurrency(total), + style: TextStyle( + fontSize: widget.isMobile ? 16 : 18, + color: Colors.green.shade700, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 2), + Text( + '${filteredData.length} ${filteredData.length == 1 ? 'Project' : 'Projects'}', + style: TextStyle( + fontSize: widget.isMobile ? 9 : 10, + color: Colors.grey.shade500, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ), + ], + series: >[ + DoughnutSeries<_DonutData, String>( + dataSource: filteredData, + xValueMapper: (datum, _) => datum.label, + yValueMapper: (datum, _) => datum.value, + pointColorMapper: (datum, _) => datum.color, + dataLabelMapper: (datum, _) { + final percentage = + (datum.value / total * 100).toStringAsFixed(1); + return widget.isMobile + ? '$percentage%' + : '${datum.label}\n$percentage%'; + }, + dataLabelSettings: DataLabelSettings( + isVisible: true, + labelPosition: ChartDataLabelPosition.outside, + connectorLineSettings: ConnectorLineSettings( + type: ConnectorType.curve, + length: widget.isMobile ? '15%' : '18%', + width: 1.5, + color: Colors.grey.shade400, + ), + textStyle: TextStyle( + fontSize: widget.isMobile ? 10 : 11, + fontWeight: FontWeight.w700, + color: Colors.black87, + ), + labelIntersectAction: LabelIntersectAction.shift, + ), + // Donut chart specific properties + innerRadius: widget.isMobile ? '60%' : '65%', + radius: widget.isMobile ? '75%' : '80%', + + // Reduced explode for cleaner donut look + explode: true, + explodeAll: false, + explodeIndex: 0, + explodeOffset: '5%', + explodeGesture: ActivationMode.singleTap, + + startAngle: 90, + endAngle: 450, + strokeColor: Colors.white, + strokeWidth: 2.5, + enableTooltip: true, + animationDuration: 1000, + selectionBehavior: _selectionBehavior, + opacity: 0.95, + ), + ], + ), + ), + if (!widget.isMobile) ...[ + const SizedBox(height: 12), + _ProjectSummary(donutData: filteredData), + ], + ], + ); + } +} + +// ----------------------------------------------------------------------------- +// Project Summary (Desktop only) +// ----------------------------------------------------------------------------- +class _ProjectSummary extends StatelessWidget { + const _ProjectSummary({Key? key, required this.donutData}) : super(key: key); + + final List<_DonutData> donutData; + + @override + Widget build(BuildContext context) { + return Wrap( + spacing: 8, + runSpacing: 8, + alignment: WrapAlignment.center, + children: donutData.map((data) { + return Container( + constraints: const BoxConstraints(minWidth: 120), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + decoration: BoxDecoration( + color: data.color.withOpacity(0.15), + borderRadius: BorderRadius.circular(5), + border: Border.all( + color: data.color.withOpacity(0.4), + width: 1, ), - ); - }, - ), - legend: Legend(isVisible: true, position: LegendPosition.bottom), - primaryXAxis: CategoryAxis(labelRotation: 45), - primaryYAxis: NumericAxis( - // ✅ Format axis labels with Utils - axisLabelFormatter: (AxisLabelRenderDetails details) { - final num value = details.value; - return ChartAxisLabel( - Utils.formatCurrency(value), const TextStyle(fontSize: 10)); - }, - axisLine: const AxisLine(width: 0), - majorGridLines: const MajorGridLines(width: 0.5), - ), - series: chartSeries.map((seriesInfo) { - return ColumnSeries( - dataSource: data.report, - xValueMapper: (item, _) => item.projectName, - yValueMapper: (item, _) => seriesInfo['yValue'](item), - name: seriesInfo['name'], - color: seriesInfo['color'], - dataLabelSettings: const DataLabelSettings(isVisible: true), - // ✅ Format data labels as well - dataLabelMapper: (item, _) => - Utils.formatCurrency(seriesInfo['yValue'](item)), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(data.icon, color: data.color, size: 18), + const SizedBox(height: 4), + Text( + data.label, + style: TextStyle( + fontSize: 11, + color: Colors.grey.shade700, + fontWeight: FontWeight.w600, + ), + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + Text( + Utils.formatCurrency(data.value), + style: TextStyle( + fontSize: 12, + color: data.color, + fontWeight: FontWeight.w700, + ), + ), + ], + ), ); }).toList(), ); } } + +class _DonutData { + final String label; + final double value; + final Color color; + final IconData icon; + + _DonutData(this.label, this.value, this.color, this.icon); +} diff --git a/lib/helpers/widgets/my_custom_skeleton.dart b/lib/helpers/widgets/my_custom_skeleton.dart index 7578b54..4537143 100644 --- a/lib/helpers/widgets/my_custom_skeleton.dart +++ b/lib/helpers/widgets/my_custom_skeleton.dart @@ -33,6 +33,65 @@ class SkeletonLoaders { ); } + // Chart Skeleton Loader (Donut Chart) + static Widget chartSkeletonLoader() { + return MyCard.bordered( + paddingAll: 16, + borderRadiusAll: 12, + shadow: MyShadow( + elevation: 1.5, + position: MyShadowPosition.bottom, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Chart Header Placeholder + Container( + height: 16, + width: 180, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(6), + ), + ), + const SizedBox(height: 16), + + // Donut Skeleton Placeholder + Expanded( + child: Center( + child: Container( + width: 180, + height: 180, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.grey.shade300.withOpacity(0.5), + ), + ), + ), + ), + + const SizedBox(height: 16), + + // Legend placeholders + Wrap( + spacing: 8, + runSpacing: 8, + children: List.generate(5, (index) { + return Container( + width: 100, + height: 14, + decoration: BoxDecoration( + color: Colors.grey.shade300, + borderRadius: BorderRadius.circular(6), + ), + ); + }), + ), + ], + ), + ); + } + // Date Skeleton Loader static Widget dateSkeletonLoader() { return Container( @@ -180,74 +239,6 @@ class SkeletonLoaders { ); } -// Chart Skeleton Loader - static Widget chartSkeletonLoader() { - return MyCard.bordered( - margin: MySpacing.only(bottom: 12), - paddingAll: 16, - borderRadiusAll: 16, - shadow: MyShadow( - elevation: 1.5, - position: MyShadowPosition.bottom, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Chart Title Placeholder - Container( - height: 14, - width: 120, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), - ), - ), - MySpacing.height(20), - - // Chart Bars (variable height for realism) - SizedBox( - height: 180, - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: List.generate(6, (index) { - return Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Container( - height: - (60 + (index * 20)).toDouble(), // fake chart shape - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(6), - ), - ), - ), - ); - }), - ), - ), - - MySpacing.height(16), - - // X-Axis Labels - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: List.generate(6, (index) { - return Container( - height: 10, - width: 30, - decoration: BoxDecoration( - color: Colors.grey.shade300, - borderRadius: BorderRadius.circular(4), - ), - ); - }), - ), - ], - ), - ); - } - // Document List Skeleton Loader static Widget documentSkeletonLoader() { return Column( diff --git a/lib/view/dashboard/dashboard_screen.dart b/lib/view/dashboard/dashboard_screen.dart index 89f8d5d..a8f2b5d 100644 --- a/lib/view/dashboard/dashboard_screen.dart +++ b/lib/view/dashboard/dashboard_screen.dart @@ -12,7 +12,7 @@ import 'package:marco/helpers/widgets/my_text.dart'; import 'package:marco/helpers/widgets/dashbaord/attendance_overview_chart.dart'; import 'package:marco/helpers/widgets/dashbaord/expense_by_status_widget.dart'; import 'package:marco/view/layouts/layout.dart'; -// import 'package:marco/helpers/widgets/dashbaord/expense_breakdown_chart.dart'; +import 'package:marco/helpers/widgets/dashbaord/expense_breakdown_chart.dart'; class DashboardScreen extends StatefulWidget { const DashboardScreen({super.key}); @@ -61,8 +61,8 @@ class _DashboardScreenState extends State with UIMixin { ExpenseByStatusWidget(controller: dashboardController), MySpacing.height(24), - // // Expense Type Report Chart - // ExpenseTypeReportChart(), + // Expense Type Report Chart + ExpenseTypeReportChart(), ], ), ),