import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:enaklo_pos/data/models/response/dashboard_analytic_response_model.dart'; import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:intl/intl.dart'; // App Colors class AppColorDashboard { static const secondary = Color(0xff7c3aed); static const success = Color(0xff10b981); static const warning = Color(0xfff59e0b); static const danger = Color(0xffef4444); static const info = Color(0xff3b82f6); } class DashboardAnalyticWidget extends StatelessWidget { final String title; final String searchDateFormatted; final DashboardAnalyticData data; const DashboardAnalyticWidget({ super.key, required this.data, required this.title, required this.searchDateFormatted, }); @override Widget build(BuildContext context) { return Container( color: const Color(0xFFF8FAFC), padding: const EdgeInsets.all(24.0), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildHeader(), const SizedBox(height: 24), _buildKPICards(), const SizedBox(height: 24), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(flex: 2, child: _buildSalesChart()), const SizedBox(width: 16), Expanded(flex: 1, child: _buildProductChart()), ], ), const SizedBox(height: 24), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(flex: 2, child: _buildTopProductsList()), const SizedBox(width: 16), Expanded(flex: 1, child: _buildOrderSummary()), ], ), ], ), ), ); } Widget _buildHeader() { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF1F2937), ), ), const SizedBox(height: 4), Text( 'Analisis performa penjualan outlet', style: TextStyle( fontSize: 14, color: Colors.grey[600], ), ), ], ), Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: AppColors.primary, borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.calendar_today, color: Colors.white, size: 16), const SizedBox(width: 8), Text( searchDateFormatted, style: const TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.w500, ), ), ], ), ), ], ); } Widget _buildKPICards() { final successfulOrders = data.overview.totalOrders - data.overview.voidedOrders - data.overview.refundedOrders; final kpiData = [ { 'title': 'Total Penjualan', 'value': _formatCurrency(data.overview.totalSales), 'icon': Icons.trending_up, 'color': AppColorDashboard.success, 'bgColor': AppColorDashboard.success.withOpacity(0.1), }, { 'title': 'Total Pesanan', 'value': '${data.overview.totalOrders}', 'icon': Icons.shopping_cart, 'color': AppColorDashboard.info, 'bgColor': AppColorDashboard.info.withOpacity(0.1), }, { 'title': 'Rata-rata Pesanan', 'value': _formatCurrency(data.overview.averageOrderValue.toInt()), 'icon': Icons.attach_money, 'color': AppColorDashboard.warning, 'bgColor': AppColorDashboard.warning.withOpacity(0.1), }, { 'title': 'Pesanan Sukses', 'value': '$successfulOrders', 'icon': Icons.check_circle, 'color': AppColors.primary, 'bgColor': AppColors.primary.withOpacity(0.1), }, ]; return Row( children: kpiData.map((kpi) { return Expanded( child: Container( margin: const EdgeInsets.only(right: 16), padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[200]!), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: kpi['bgColor'] as Color, borderRadius: BorderRadius.circular(8), ), child: Icon( kpi['icon'] as IconData, color: kpi['color'] as Color, size: 20, ), ), Icon( Icons.trending_up, color: Colors.grey[400], size: 16, ), ], ), const SizedBox(height: 16), Text( kpi['value'] as String, style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Color(0xFF1F2937), ), ), const SizedBox(height: 4), Text( kpi['title'] as String, style: TextStyle( fontSize: 12, color: Colors.grey[600], fontWeight: FontWeight.w500, ), ), ], ), ), ); }).toList(), ); } Widget _buildSalesChart() { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[200]!), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Tren Penjualan Harian', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF1F2937), ), ), const SizedBox(height: 20), SizedBox( height: 200, child: LineChart( LineChartData( gridData: FlGridData( show: true, drawHorizontalLine: true, drawVerticalLine: false, horizontalInterval: 200000, getDrawingHorizontalLine: (value) { return FlLine( color: Colors.grey[200]!, strokeWidth: 1, ); }, ), titlesData: FlTitlesData( leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 60, getTitlesWidget: (value, meta) { return Text( '${(value / 1000).toInt()}K', style: TextStyle( color: Colors.grey[600], fontSize: 10, ), ); }, ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { final index = value.toInt(); if (index >= 0 && index < data.recentSales.length) { final date = DateTime.parse(data.recentSales[index].date); final formatter = DateFormat('dd MMM'); return Padding( padding: const EdgeInsets.only(top: 8), child: Text( formatter.format(date), style: TextStyle( color: Colors.grey[600], fontSize: 10, ), ), ); } return const SizedBox(); }, ), ), rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false)), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false)), ), borderData: FlBorderData(show: false), lineBarsData: [ LineChartBarData( spots: data.recentSales.asMap().entries.map((entry) { return FlSpot( entry.key.toDouble(), entry.value.sales.toDouble()); }).toList(), isCurved: true, color: AppColors.primary, // strokeWidth: 3, dotData: const FlDotData(show: true), belowBarData: BarAreaData( show: true, color: AppColors.primary.withOpacity(0.1), ), ), ], ), ), ), ], ), ); } Widget _buildProductChart() { final colors = [ AppColors.primary, AppColorDashboard.secondary, AppColorDashboard.info, AppColorDashboard.warning, AppColorDashboard.success, ]; return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[200]!), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Distribusi Produk', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF1F2937), ), ), const SizedBox(height: 20), SizedBox( height: 160, child: PieChart( PieChartData( sectionsSpace: 2, centerSpaceRadius: 40, sections: data.topProducts.asMap().entries.map((entry) { return PieChartSectionData( color: colors[entry.key % colors.length], value: entry.value.quantitySold.toDouble(), title: '${entry.value.quantitySold}', radius: 60, titleStyle: const TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: Colors.white, ), ); }).toList(), ), ), ), const SizedBox(height: 16), Column( children: data.topProducts.take(3).map((product) { final index = data.topProducts.indexOf(product); return Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( children: [ Container( width: 8, height: 8, decoration: BoxDecoration( color: colors[index % colors.length], shape: BoxShape.circle, ), ), const SizedBox(width: 8), Expanded( child: Text( product.productName, style: const TextStyle(fontSize: 12), ), ), Text( '${product.quantitySold}', style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w500, ), ), ], ), ); }).toList(), ), ], ), ); } Widget _buildTopProductsList() { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[200]!), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Produk Terlaris', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF1F2937), ), ), const SizedBox(height: 16), Column( children: data.topProducts.map((product) { return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFFF8FAFC), borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey[200]!), ), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: AppColors.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: const Icon( Icons.local_cafe, color: AppColors.primary, size: 20, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( product.productName, style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 14, ), ), const SizedBox(height: 2), Text( product.categoryName, style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), ], ), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '${product.quantitySold} unit', style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 14, ), ), const SizedBox(height: 2), Text( _formatCurrency(product.revenue), style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), ], ), ], ), ); }).toList(), ), ], ), ); } Widget _buildOrderSummary() { final successfulOrders = data.overview.totalOrders - data.overview.voidedOrders - data.overview.refundedOrders; return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[200]!), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Ringkasan Pesanan', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF1F2937), ), ), const SizedBox(height: 20), _buildSummaryItem('Total Pesanan', '${data.overview.totalOrders}', Icons.shopping_cart, AppColorDashboard.info), _buildSummaryItem('Pesanan Sukses', '$successfulOrders', Icons.check_circle, AppColorDashboard.success), _buildSummaryItem( 'Pesanan Dibatalkan', '${data.overview.voidedOrders}', Icons.cancel, AppColorDashboard.danger), _buildSummaryItem('Pesanan Refund', '${data.overview.refundedOrders}', Icons.refresh, AppColorDashboard.warning), const SizedBox(height: 20), // Payment Methods if (data.paymentMethods.isNotEmpty) Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Column( children: [ const Text( 'Metode Pembayaran', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: Color(0xFF374151), ), ), const SizedBox(height: 8), ...data.paymentMethods .map((method) => Padding( padding: const EdgeInsets.only(bottom: 4), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( method.paymentMethodType == 'cash' ? Icons.payments : Icons.credit_card, color: AppColors.primary, size: 16, ), const SizedBox(width: 8), Text( method.paymentMethodName, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), ], ), )) .toList(), ], ), ), ], ), ); } Widget _buildSummaryItem( String title, String value, IconData icon, Color color) { return Padding( padding: const EdgeInsets.only(bottom: 16), child: Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(icon, color: color, size: 16), ), const SizedBox(width: 12), Expanded( child: Text( title, style: const TextStyle(fontSize: 12), ), ), Text( value, style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 14, ), ), ], ), ); } String _formatCurrency(int amount) { final formatter = NumberFormat.currency( locale: 'id_ID', symbol: 'Rp ', decimalDigits: 0, ); return formatter.format(amount); } }