import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lucide_icons/lucide_icons.dart'; import 'package:intl/intl.dart'; import '../providers/product_categories_provider.dart'; import '../providers/commodity_rates_provider.dart'; import '../domain/commodity_rate.dart'; import '../../../core/theme/app_theme.dart'; import '../../../core/utils/snackbar_service.dart'; import '../../../core/utils/purity_utils.dart'; class DailyRatesScreen extends ConsumerStatefulWidget { const DailyRatesScreen({super.key}); @override ConsumerState createState() => _DailyRatesScreenState(); } class _DailyRatesScreenState extends ConsumerState { final Map _rateControllers = {}; final Map _isSaving = {}; final Map _isSyncing = {}; final Map _commodityLabels = { 'XAU': 'Gold (XAU)', 'XAG': 'Silver (XAG)', 'XPT': 'Platinum (XPT)', 'XPD': 'Palladium (XPD)', 'OTH': 'Other Commodity', }; @override void dispose() { for (var controller in _rateControllers.values) { controller.dispose(); } super.dispose(); } Color _getCommodityColor(String code, bool isDark) { switch (code.toUpperCase()) { case 'XAU': return const Color(0xFFD4AF37); // Classic Gold case 'XAG': return const Color(0xFF8A9BA8); // Silver metallic case 'XPT': return const Color(0xFF00A896); // Platinum teal case 'XPD': return const Color(0xFF7209B7); // Palladium purple default: return const Color(0xFF3B82F6); // Modern Blue } } IconData _getCommodityIcon(String code) { switch (code.toUpperCase()) { case 'XAU': return LucideIcons.gem; case 'XAG': return LucideIcons.sparkles; case 'XPT': return LucideIcons.shieldCheck; case 'XPD': return LucideIcons.cpu; default: return LucideIcons.boxes; } } Future _saveRate(String commodityCode, String baseUnit) async { final text = _rateControllers[commodityCode]?.text.trim(); if (text == null || text.isEmpty) { SnackBarService.showError(context, 'Please enter a valid rate'); return; } final rate = double.tryParse(text); if (rate == null || rate <= 0) { SnackBarService.showError(context, 'Please enter a positive numeric rate'); return; } setState(() => _isSaving[commodityCode] = true); try { final name = _commodityLabels[commodityCode] ?? commodityCode; await ref.read(commodityRatesProvider.notifier).addRate( commodityCode, rate, commodityName: name, source: 'MANUAL', ); if (mounted) { SnackBarService.showSuccess(context, '$name rate updated to ₹${NumberFormat('#,##,##0.00').format(rate)} / $baseUnit'); } } catch (e) { if (mounted) { SnackBarService.showError(context, 'Failed to save rate: $e'); } } finally { if (mounted) { setState(() => _isSaving[commodityCode] = false); } } } Future _syncRates(String commodityCode) async { setState(() => _isSyncing[commodityCode] = true); try { final count = await ref.read(commodityRatesProvider.notifier).syncRateToProducts(commodityCode); if (mounted) { SnackBarService.showSuccess( context, count > 0 ? 'Successfully synced $commodityCode rates to $count products!' : 'Rates synced. No products currently require rate updates.', ); } } catch (e) { if (mounted) { SnackBarService.showError(context, 'Failed to sync rates: $e'); } } finally { if (mounted) { setState(() => _isSyncing[commodityCode] = false); } } } void _showHistorySheet(BuildContext context, String commodityCode, String baseUnit) { final color = _getCommodityColor(commodityCode, Theme.of(context).brightness == Brightness.dark); final label = _commodityLabels[commodityCode] ?? commodityCode; showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (ctx) { return DraggableScrollableSheet( initialChildSize: 0.65, minChildSize: 0.4, maxChildSize: 0.9, builder: (_, scrollController) { return Container( decoration: BoxDecoration( color: Theme.of(context).scaffoldBackgroundColor, borderRadius: const BorderRadius.vertical(top: Radius.circular(28)), boxShadow: [ BoxShadow(color: Colors.black.withValues(alpha: 0.2), blurRadius: 25, offset: const Offset(0, -5)), ], ), child: Column( children: [ // Handle Center( child: Container( margin: const EdgeInsets.only(top: 12, bottom: 8), width: 44, height: 4, decoration: BoxDecoration( color: Colors.grey.withValues(alpha: 0.4), borderRadius: BorderRadius.circular(2), ), ), ), // Title Header Padding( padding: const EdgeInsets.fromLTRB(24, 8, 16, 16), child: Row( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: color.withValues(alpha: 0.15), shape: BoxShape.circle, ), child: Icon(_getCommodityIcon(commodityCode), color: color, size: 22), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '$label History', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(height: 2), Text( 'Historical rate log per $baseUnit', style: TextStyle(fontSize: 12, color: Colors.grey.shade600), ), ], ), ), IconButton( icon: const Icon(LucideIcons.x, size: 20), onPressed: () => Navigator.pop(ctx), ), ], ), ), const Divider(height: 1), // History List Expanded( child: FutureBuilder>( future: ref.read(commodityRatesProvider.notifier).fetchRateHistory(commodityCode), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { return Center( child: Padding( padding: const EdgeInsets.all(24), child: Text('Error loading history: ${snapshot.error}', style: const TextStyle(color: Colors.red)), ), ); } final list = snapshot.data ?? []; if (list.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(LucideIcons.history, size: 48, color: Colors.grey.shade400), const SizedBox(height: 12), Text('No rate history recorded yet', style: TextStyle(color: Colors.grey.shade600, fontWeight: FontWeight.w500)), ], ), ); } return ListView.separated( controller: scrollController, padding: const EdgeInsets.all(20), itemCount: list.length, separatorBuilder: (_, __) => const SizedBox(height: 12), itemBuilder: (context, idx) { final item = list[idx]; final isLatest = idx == 0; return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isLatest ? color.withValues(alpha: 0.08) : Theme.of(context).cardColor, borderRadius: BorderRadius.circular(16), border: Border.all( color: isLatest ? color.withValues(alpha: 0.4) : Colors.grey.withValues(alpha: 0.15), width: isLatest ? 1.5 : 1, ), ), child: Row( children: [ Container( width: 8, height: 8, decoration: BoxDecoration( color: isLatest ? color : Colors.grey.shade400, shape: BoxShape.circle, ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( DateFormat('dd MMM yyyy, hh:mm a').format(item.effectiveAt), style: TextStyle( fontSize: 13, fontWeight: isLatest ? FontWeight.bold : FontWeight.w500, color: isLatest ? null : Colors.grey.shade700, ), ), if (isLatest) ...[ const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(6), ), child: const Text('LATEST', style: TextStyle(color: Colors.white, fontSize: 9, fontWeight: FontWeight.bold)), ), ], ], ), if (item.source != null && item.source!.isNotEmpty) ...[ const SizedBox(height: 2), Text('Source: ${item.source}', style: TextStyle(fontSize: 11, color: Colors.grey.shade500)), ], ], ), ), Text( '₹${NumberFormat('#,##,##0.00').format(item.rate)}', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: isLatest ? color : null, ), ), ], ), ); }, ); }, ), ), ], ), ); }, ); }, ); } @override Widget build(BuildContext context) { final categoriesState = ref.watch(productCategoriesProvider); final commodityRatesState = ref.watch(commodityRatesProvider); final isDark = Theme.of(context).brightness == Brightness.dark; return Scaffold( backgroundColor: isDark ? const Color(0xFF0F172A) : const Color(0xFFF8FAFC), appBar: AppBar( title: const Text( 'Daily Commodity Rates', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 19), ), elevation: 0, backgroundColor: isDark ? const Color(0xFF1E293B) : Colors.white, foregroundColor: isDark ? Colors.white : const Color(0xFF0F172A), centerTitle: true, actions: [ IconButton( icon: const Icon(LucideIcons.refreshCw, size: 20), tooltip: 'Refresh Rates', onPressed: () { ref.invalidate(productCategoriesProvider); ref.invalidate(commodityRatesProvider); }, ), ], ), body: categoriesState.when( loading: () => const Center(child: CircularProgressIndicator()), error: (err, _) => Center(child: Text('Error loading categories: $err')), data: (categories) { // Leaf categories that have a commodity code assigned final leafCommodities = categories.where((c) => !c.hasChild && c.commodityCode != null && c.commodityCode!.trim().isNotEmpty ).toList(); if (leafCommodities.isEmpty) { return _buildEmptyState(isDark); } // Group leaf categories by commodity code final Map> grouped = {}; for (var cat in leafCommodities) { final code = cat.commodityCode!.trim().toUpperCase(); grouped.putIfAbsent(code, () => []).add(cat); } final commodityCodes = grouped.keys.toList()..sort(); final latestRates = commodityRatesState.value ?? []; return RefreshIndicator( onRefresh: () async { ref.invalidate(productCategoriesProvider); ref.invalidate(commodityRatesProvider); await Future.wait([ ref.read(productCategoriesProvider.future), ref.read(commodityRatesProvider.future), ]); }, child: ListView( padding: const EdgeInsets.fromLTRB(16, 16, 16, 32), children: [ // Info Banner _buildInfoBanner(isDark, commodityCodes.length, leafCommodities.length), const SizedBox(height: 16), // Commodity Cards ...commodityCodes.map((code) { final linkedCats = grouped[code]!; final baseUnit = linkedCats.first.baseUnit; // Find latest rate for this code final rateEntry = latestRates.where((r) => r.commodityCode.toUpperCase() == code || (r.commodityName != null && r.commodityName!.toUpperCase() == code) ).firstOrNull; final currentRate = rateEntry?.rate ?? 0.0; // Initialize controller if not present if (!_rateControllers.containsKey(code) || _rateControllers[code]!.text.isEmpty) { _rateControllers[code] = TextEditingController( text: currentRate > 0 ? currentRate.toStringAsFixed(2) : '', ); } return _buildCommodityCard( context: context, commodityCode: code, linkedCategories: linkedCats, currentRate: currentRate, rateEntry: rateEntry, baseUnit: baseUnit, isDark: isDark, ); }), ], ), ); }, ), ); } Widget _buildInfoBanner(bool isDark, int commodityCount, int categoryCount) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isDark ? const Color(0xFF1E293B) : Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all(color: isDark ? Colors.white10 : Colors.black.withValues(alpha: 0.06)), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: isDark ? 0.2 : 0.03), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Row( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: AppTheme.primaryColor.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), ), child: const Icon(LucideIcons.trendingUp, color: AppTheme.primaryColor, size: 24), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Pricing Flow & Valuation', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14), ), const SizedBox(height: 2), Text( '$commodityCount active commodities governing $categoryCount leaf product categories.', style: TextStyle(fontSize: 12, color: Colors.grey.shade600), ), ], ), ), ], ), ); } Widget _buildCommodityCard({ required BuildContext context, required String commodityCode, required List linkedCategories, required double currentRate, required CommodityRateHistory? rateEntry, required String baseUnit, required bool isDark, }) { final color = _getCommodityColor(commodityCode, isDark); final label = _commodityLabels[commodityCode] ?? commodityCode; final controller = _rateControllers[commodityCode]!; final isSaving = _isSaving[commodityCode] ?? false; final isSyncing = _isSyncing[commodityCode] ?? false; return Container( margin: const EdgeInsets.only(bottom: 20), decoration: BoxDecoration( color: isDark ? const Color(0xFF1E293B) : Colors.white, borderRadius: BorderRadius.circular(24), border: Border.all( color: color.withValues(alpha: isDark ? 0.35 : 0.25), width: 1.5, ), boxShadow: [ BoxShadow( color: color.withValues(alpha: isDark ? 0.15 : 0.08), blurRadius: 18, offset: const Offset(0, 8), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header banner Padding( padding: const EdgeInsets.fromLTRB(20, 20, 20, 14), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: color.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(16), border: Border.all(color: color.withValues(alpha: 0.3)), ), child: Icon(_getCommodityIcon(commodityCode), color: color, size: 24), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( label, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, letterSpacing: -0.3, ), ), const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: color.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(6), ), child: Text( commodityCode, style: TextStyle( color: color, fontSize: 11, fontWeight: FontWeight.bold, ), ), ), ], ), const SizedBox(height: 3), Text( rateEntry != null ? 'Updated ${DateFormat('dd MMM, hh:mm a').format(rateEntry.effectiveAt)}' : 'No rate recorded yet', style: TextStyle(fontSize: 12, color: Colors.grey.shade500), ), ], ), ), // History Button IconButton.filledTonal( onPressed: () => _showHistorySheet(context, commodityCode, baseUnit), style: IconButton.styleFrom( backgroundColor: isDark ? Colors.white10 : Colors.grey.shade100, foregroundColor: isDark ? Colors.white : Colors.black87, ), icon: const Icon(LucideIcons.history, size: 18), tooltip: 'View History', ), ], ), ), // Rate Display & Editor Box Container( margin: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isDark ? const Color(0xFF0F172A) : const Color(0xFFF1F5F9), borderRadius: BorderRadius.circular(18), border: Border.all(color: isDark ? Colors.white12 : Colors.black.withValues(alpha: 0.05)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'CURRENT RATE', style: TextStyle( fontSize: 11, fontWeight: FontWeight.bold, letterSpacing: 0.5, color: Colors.grey.shade500, ), ), Text( currentRate > 0 ? '₹ ${NumberFormat('#,##,##0.00').format(currentRate)} / $baseUnit' : 'Not Set', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: currentRate > 0 ? color : Colors.grey.shade400, ), ), ], ), const SizedBox(height: 12), // Inline Updater Field & Save Button Row( children: [ Expanded( child: Container( decoration: BoxDecoration( color: isDark ? const Color(0xFF1E293B) : Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: isDark ? Colors.white24 : Colors.grey.shade300), ), child: TextField( controller: controller, keyboardType: const TextInputType.numberWithOptions(decimal: true), style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), decoration: InputDecoration( hintText: '0.00', prefixIcon: const Padding( padding: EdgeInsets.symmetric(horizontal: 12), child: Text('₹', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), ), prefixIconConstraints: const BoxConstraints(minWidth: 0, minHeight: 0), suffixText: '/ $baseUnit', suffixStyle: TextStyle(color: Colors.grey.shade500, fontSize: 12), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), border: InputBorder.none, ), ), ), ), const SizedBox(width: 10), ElevatedButton( onPressed: isSaving ? null : () => _saveRate(commodityCode, baseUnit), style: ElevatedButton.styleFrom( backgroundColor: color, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), elevation: 0, ), child: isSaving ? const SizedBox(width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) : const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(LucideIcons.check, size: 16), SizedBox(width: 6), Text('Save', style: TextStyle(fontWeight: FontWeight.bold)), ], ), ), ], ), ], ), ), const SizedBox(height: 14), // Linked Leaf Categories Section Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'LINKED LEAF CATEGORIES', style: TextStyle( fontSize: 11, fontWeight: FontWeight.bold, letterSpacing: 0.5, color: Colors.grey.shade500, ), ), Text( '${linkedCategories.length} linked', style: TextStyle(fontSize: 11, color: Colors.grey.shade500), ), ], ), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 8, children: linkedCategories.map((cat) { final purity = normalizePurity(cat.purityFactor); final effectiveUnitRate = currentRate * purity; return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), decoration: BoxDecoration( color: isDark ? const Color(0xFF0F172A) : Colors.grey.shade100, borderRadius: BorderRadius.circular(10), border: Border.all(color: isDark ? Colors.white10 : Colors.grey.shade300), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(LucideIcons.tag, size: 12, color: color), const SizedBox(width: 6), Text( cat.name, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600), ), const SizedBox(width: 6), Container( padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1), decoration: BoxDecoration( color: color.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(4), ), child: Text( '${formatPurity(purity)} (₹${effectiveUnitRate.toStringAsFixed(0)}/g)', style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold, color: color), ), ), ], ), ); }).toList(), ), ], ), ), const SizedBox(height: 16), // Sync to Products Action Bar Container( padding: const EdgeInsets.fromLTRB(16, 12, 16, 14), decoration: BoxDecoration( color: isDark ? Colors.black.withValues(alpha: 0.2) : Colors.grey.shade50, borderRadius: const BorderRadius.vertical(bottom: Radius.circular(24)), ), child: Row( children: [ Expanded( child: Text( 'Sync live rates to all products under this commodity:', style: TextStyle(fontSize: 12, color: Colors.grey.shade600), ), ), OutlinedButton.icon( onPressed: isSyncing ? null : () => _syncRates(commodityCode), style: OutlinedButton.styleFrom( foregroundColor: color, side: BorderSide(color: color.withValues(alpha: 0.5)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), ), icon: isSyncing ? SizedBox(width: 14, height: 14, child: CircularProgressIndicator(strokeWidth: 2, color: color)) : Icon(LucideIcons.refreshCw, size: 14, color: color), label: const Text('Sync Products', style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold)), ), ], ), ), ], ), ); } Widget _buildEmptyState(bool isDark) { return Center( child: Padding( padding: const EdgeInsets.all(32), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: const Color(0xFFD4AF37).withValues(alpha: 0.1), shape: BoxShape.circle, ), child: const Icon(LucideIcons.coins, size: 56, color: Color(0xFFD4AF37)), ), const SizedBox(height: 20), const Text( 'No Commodity Categories Configured', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), textAlign: TextAlign.center, ), const SizedBox(height: 10), Text( 'Assign a commodity code (e.g. Gold XAU, Silver XAG) and purity factor to your leaf categories in Category Management to track their live daily rates.', style: TextStyle(fontSize: 13, color: Colors.grey.shade600), textAlign: TextAlign.center, ), ], ), ), ); } }