import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; 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/helpers/widgets/my_custom_skeleton.dart'; class AttendanceDashboardChart extends StatelessWidget { final DashboardController controller = Get.find(); AttendanceDashboardChart({super.key}); List> get filteredData { final now = DateTime.now(); final daysBack = controller.rangeDays; return controller.roleWiseData.where((entry) { final date = DateTime.parse(entry['date']); return date.isAfter(now.subtract(Duration(days: daysBack))) && !date.isAfter(now); }).toList(); } List get filteredDateTimes { final uniqueDates = filteredData .map((e) => DateTime.parse(e['date'] as String)) .toSet() .toList() ..sort(); return uniqueDates; } List get filteredDates => filteredDateTimes.map((d) => DateFormat('d MMMM').format(d)).toList(); List get filteredRoles => filteredData.map((e) => e['role'] as String).toSet().toList(); List get rolesWithData => filteredRoles.where((role) { return filteredData.any( (entry) => entry['role'] == role && (entry['present'] ?? 0) > 0); }).toList(); final Map _roleColorMap = {}; final List flatColors = [ const Color(0xFFE57373), const Color(0xFF64B5F6), const Color(0xFF81C784), const Color(0xFFFFB74D), const Color(0xFFBA68C8), const Color(0xFFFF8A65), const Color(0xFF4DB6AC), const Color(0xFFA1887F), const Color(0xFFDCE775), const Color(0xFF9575CD), ]; Color _getRoleColor(String role) { if (_roleColorMap.containsKey(role)) return _roleColorMap[role]!; final index = _roleColorMap.length % flatColors.length; final color = flatColors[index]; _roleColorMap[role] = color; return color; } @override Widget build(BuildContext context) { return Obx(() { final isChartView = controller.isChartView.value; final selectedRange = controller.selectedRange.value; final isLoading = controller.isLoading.value; return Container( // flat white background color: Colors.white, padding: const EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildHeader(selectedRange, isChartView), const SizedBox(height: 12), AnimatedSwitcher( duration: const Duration(milliseconds: 300), child: isLoading ? SkeletonLoaders.buildLoadingSkeleton() : filteredData.isEmpty ? _buildNoDataMessage() : isChartView ? _buildChart() : _buildTable(), ), ], ), ); }); } Widget _buildHeader(String selectedRange, bool isChartView) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ MyText.bodyMedium('Attendance Overview', fontWeight: 600), MyText.bodySmall('Role-wise present count', color: Colors.grey), ], ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.shade300), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ PopupMenuButton( padding: EdgeInsets.zero, tooltip: 'Select Range', onSelected: (value) => controller.selectedRange.value = value, itemBuilder: (context) => const [ PopupMenuItem(value: '7D', child: Text('Last 7 Days')), PopupMenuItem(value: '15D', child: Text('Last 15 Days')), PopupMenuItem(value: '30D', child: Text('Last 30 Days')), ], child: Row( children: [ const Icon(Icons.calendar_today_outlined, size: 18), const SizedBox(width: 4), MyText.labelSmall(selectedRange), ], ), ), const SizedBox(width: 8), IconButton( icon: Icon( Icons.bar_chart_rounded, size: 20, color: isChartView ? Colors.blueAccent : Colors.grey, ), visualDensity: VisualDensity(horizontal: -4, vertical: -4), constraints: const BoxConstraints(), padding: EdgeInsets.zero, onPressed: () => controller.isChartView.value = true, tooltip: 'Chart View', ), IconButton( icon: Icon( Icons.table_chart, size: 20, color: !isChartView ? Colors.blueAccent : Colors.grey, ), visualDensity: VisualDensity(horizontal: -4, vertical: -4), constraints: const BoxConstraints(), padding: EdgeInsets.zero, onPressed: () => controller.isChartView.value = false, tooltip: 'Table View', ), ], ), ), ], ), ); } Widget _buildChart() { final formattedDateMap = { for (var e in filteredData) '${e['role']}_${DateFormat('d MMMM').format(DateTime.parse(e['date']))}': e['present'] }; return SizedBox( height: 360, child: SfCartesianChart( tooltipBehavior: TooltipBehavior( enable: true, shared: true, activationMode: ActivationMode.singleTap, tooltipPosition: TooltipPosition.pointer, ), legend: const Legend( isVisible: true, position: LegendPosition.bottom, overflowMode: LegendItemOverflowMode.wrap, ), primaryXAxis: CategoryAxis( labelRotation: 45, majorGridLines: const MajorGridLines(width: 0), ), primaryYAxis: NumericAxis( minimum: 0, interval: 1, majorGridLines: const MajorGridLines(width: 0), ), series: rolesWithData.map((role) { final data = filteredDates.map((formattedDate) { final key = '${role}_$formattedDate'; return { 'date': formattedDate, 'present': formattedDateMap[key] ?? 0 }; }).toList(); return StackedColumnSeries, String>( dataSource: data, xValueMapper: (d, _) => d['date'], yValueMapper: (d, _) => d['present'], name: role, legendIconType: LegendIconType.circle, dataLabelSettings: const DataLabelSettings(isVisible: true), dataLabelMapper: (d, _) => d['present'] == 0 ? '' : d['present'].toString(), color: _getRoleColor(role), ); }).toList(), ), ); } Widget _buildNoDataMessage() { return SizedBox( height: 200, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.info_outline, color: Colors.grey.shade500, size: 48), const SizedBox(height: 12), MyText.bodyMedium( 'No attendance data available for the selected range or project.', textAlign: TextAlign.center, color: Colors.grey.shade600, ), ], ), ), ); } Widget _buildTable() { final formattedDateMap = { for (var e in filteredData) '${e['role']}_${DateFormat('d MMMM').format(DateTime.parse(e['date']))}': e['present'] }; return Container( decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(12), ), child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: DataTable( columnSpacing: 28, headingRowHeight: 42, headingRowColor: WidgetStateProperty.all(Colors.blueAccent.withOpacity(0.1)), headingTextStyle: const TextStyle( fontWeight: FontWeight.bold, color: Colors.black87), columns: [ DataColumn(label: MyText.labelSmall('Role', fontWeight: 600)), ...filteredDates.map((date) => DataColumn( label: MyText.labelSmall(date, fontWeight: 600), )), ], rows: filteredRoles.map((role) { return DataRow( cells: [ DataCell(Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: _rolePill(role), )), ...filteredDates.map((date) { final key = '${role}_$date'; return DataCell(Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: MyText.labelSmall('${formattedDateMap[key] ?? 0}'), )); }), ], ); }).toList(), ), ), ); } Widget _rolePill(String role) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: _getRoleColor(role).withOpacity(0.15), borderRadius: BorderRadius.circular(8), ), child: MyText.labelSmall(role, fontWeight: 500), ); } }