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(); final Map _roleColorMap = {}; final List flatColors = [ const Color(0xFFE57373), // Red const Color(0xFF64B5F6), // Blue const Color(0xFF81C784), // Green const Color(0xFFFFB74D), // Orange const Color(0xFFBA68C8), // Purple const Color(0xFFFF8A65), // Deep Orange const Color(0xFF4DB6AC), // Teal const Color(0xFFA1887F), // Brown const Color(0xFFDCE775), // Lime const Color(0xFF9575CD), // Violet` const Color(0xFF7986CB), // Indigo const Color(0xFFAED581), // Light Green const Color(0xFF4FC3F7), // Light Blue const Color(0xFFF06292), // Pink const Color(0xFFE0E0E0), // Grey const Color(0xFFCE93D8), // Lavender const Color(0xFFFFF176), // Yellow const Color(0xFFA5D6A7), // Mint Green const Color(0xFF90CAF9), // Sky Blue const Color(0xFFFFAB91), // Salmon const Color(0xFFE6EE9C), // Pale Lime const Color(0xFFFFCC80), // Apricot const Color(0xFFB39DDB), // Soft Purple const Color(0xFF80DEEA), // Light Cyan const Color(0xFFB0BEC5), // Blue Grey const Color(0xFFEF9A9A), // Soft Red const Color(0xFFFFCDD2), // Rose const Color(0xFFC5CAE9), // Periwinkle const Color(0xFFF8BBD0), // Baby Pink const Color(0xFFD1C4E9), // Lilac const Color(0xFFFFF9C4), // Pastel Yellow const Color(0xFFC8E6C9), // Pastel Green const Color(0xFFBBDEFB), // Pastel Blue const Color(0xFFFFECB3), // Butter const Color(0xFFE1BEE7), // Orchid const Color(0xFFB2EBF2), // Powder Blue const Color(0xFFCFD8DC), // Cool Grey const Color(0xFFFBE9E7), // Peach Cream const Color(0xFFF3E5F5), // Lavender Mist const Color(0xFFFFFDE7), // Soft Sunlight const Color(0xFFDCEDC8), // Soft Apple const Color(0xFFB3E5FC), // Ice Blue const Color(0xFFFFF3E0), // Cream const Color(0xFFFCE4EC), // Candy Pink const Color(0xFFEDE7F6), // Wisteria const Color(0xFFE0F7FA), // Arctic Blue const Color(0xFFECEFF1), // Frost const Color(0xFFFFE0B2), // Sandy Orange const Color(0xFFFFD54F), // Mango const Color(0xFFFFA726), // Tangerine ]; 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(() { // Now this observes `controller.roleWiseData`, `isLoading`, `isChartView`, etc. final isChartView = controller.isChartView.value; final selectedRange = controller.selectedRange.value; final isLoading = controller.isLoading.value; return Container( decoration: const BoxDecoration( gradient: LinearGradient( colors: [Color(0xfff0f4f8), Color(0xffe2ebf0)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), ), child: Card( color: Colors.white, elevation: 6, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), shadowColor: Colors.black12, child: Padding( 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: filteredRoles.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), ); } }