import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:kifi_app/features/inventory/domain/inventory_item.dart'; import 'package:kifi_app/features/inventory/domain/product.dart'; import 'package:kifi_app/features/inventory/providers/products_provider.dart'; import 'package:kifi_app/features/inventory/providers/product_categories_provider.dart'; import 'package:kifi_app/features/inventory/providers/commodity_rates_provider.dart'; import 'package:kifi_app/core/network/dio_client.dart'; import 'package:intl/intl.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import 'package:kifi_app/features/vendor/presentation/purchase_order_details_screen.dart'; import 'package:kifi_app/features/vendor/providers/purchase_orders_provider.dart'; import 'package:kifi_app/core/utils/purity_utils.dart'; import '../../transactions/presentation/widgets/attachment_gallery_screen.dart'; class StockLedgerTab extends ConsumerStatefulWidget { final Product product; const StockLedgerTab({super.key, required this.product}); @override ConsumerState createState() => _StockLedgerTabState(); } class _StockLedgerTabState extends ConsumerState { List? _items; bool _isLoading = true; String _selectedFilter = 'IN_STOCK'; // 'IN_STOCK', 'SOLD', 'ALL' String _resolveImageUrl(String path) { if (path.startsWith('http://') || path.startsWith('https://')) { return path; } final base = DioClient().dio.options.baseUrl; final cleanPath = path.startsWith('/') ? path.substring(1) : path; if (cleanPath.startsWith('api/kifi-v2/upload/view') || cleanPath.startsWith('upload/view')) { return '$base/${cleanPath.replaceFirst('api/kifi-v2/', '')}'; } return '$base/upload/view?path=${Uri.encodeComponent(cleanPath)}'; } @override void initState() { super.initState(); _fetchLedger(); } Future _fetchLedger() async { try { final items = await ref .read(productsProvider.notifier) .fetchInventoryItems(widget.product.id!); if (mounted) { setState(() { _items = items; _isLoading = false; }); } } catch (e) { if (mounted) { setState(() { _isLoading = false; }); ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text(e.toString()))); } } } @override Widget build(BuildContext context) { if (_isLoading) { return const Center(child: CircularProgressIndicator()); } if (_items == null || _items!.isEmpty) { return const Center(child: Text("No stock records found.")); } final inStockItems = _items!.where((i) => i.status != 'SOLD').toList(); final soldItems = _items!.where((i) => i.status == 'SOLD').toList(); List displayItems; if (_selectedFilter == 'IN_STOCK') { displayItems = inStockItems; } else if (_selectedFilter == 'SOLD') { displayItems = soldItems; } else { displayItems = _items!; } final categoriesState = ref.watch(productCategoriesProvider); final commodityRatesState = ref.watch(commodityRatesProvider); final category = categoriesState.value?.where( (c) => c.id == widget.product.categoryId, ).firstOrNull; final String unit = category?.baseUnit ?? 'g'; final double purityFactor = resolvePurity(categoryPurity: category?.purityFactor, productPurity: widget.product.purityFactor); double commodityRate = 0.0; if (category?.commodityCode != null && commodityRatesState.value != null) { final match = commodityRatesState.value!.where( (r) => r.commodityCode.toUpperCase() == category!.commodityCode!.toUpperCase() ).firstOrNull; if (match != null) { commodityRate = match.rate; } } final double currentRate = commodityRate > 0 ? (commodityRate * purityFactor) : (category?.dailyRate ?? 0.0); return Column( children: [ // Filter Chips Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), child: Row( children: [ _buildFilterChip('In Stock (${inStockItems.length})', 'IN_STOCK'), const SizedBox(width: 8), _buildFilterChip('Sold (${soldItems.length})', 'SOLD'), const SizedBox(width: 8), _buildFilterChip('All (${_items!.length})', 'ALL'), ], ), ), Expanded( child: displayItems.isEmpty ? Center( child: Text( _selectedFilter == 'IN_STOCK' ? 'No active stock available.' : (_selectedFilter == 'SOLD' ? 'No sold items yet.' : 'No stock records.'), style: const TextStyle(color: Colors.grey), ), ) : RefreshIndicator( onRefresh: _fetchLedger, child: ListView.builder( padding: const EdgeInsets.all(16.0), itemCount: displayItems.length, itemBuilder: (context, index) { final item = displayItems[index]; final isSold = item.status == 'SOLD'; final purchaseOrdersState = ref.watch(purchaseOrdersProvider); final purchaseOrders = purchaseOrdersState.value ?? []; double weight = (item.grossWeight != null && item.grossWeight! > 0) ? item.grossWeight! : ((item.netWeight != null && item.netWeight! > 0) ? item.netWeight! : (item.fineWeight ?? 0.0)); double purchaseRate = (item.purchaseCost != null && item.purchaseCost! > 0) ? item.purchaseCost! : ((item.metalCost != null && item.metalCost! > 0 && weight > 0) ? (item.metalCost! / weight) : 0.0); // If weight or rate was lost due to earlier db overwrite, recover from purchase order if ((weight == 0.0 || purchaseRate == 0.0) && item.purchaseRef != null) { final po = purchaseOrders.where((p) => p.poNumber == item.purchaseRef).firstOrNull; if (po != null) { final poItem = po.items.where((i) => i.productId == widget.product.id || (item.huid != null && i.huid == item.huid) || (item.sku != null && i.sku == item.sku)).firstOrNull ?? po.items.firstOrNull; if (poItem != null) { if (weight == 0.0) { weight = poItem.weight ?? (poItem.quantity > 0 ? poItem.quantity : 1.0); } if (purchaseRate == 0.0) { purchaseRate = poItem.unitPrice; } } } } // Resolve purchase item thumbnail photo String? itemPhotoUrl = (item.photoUrl != null && item.photoUrl!.isNotEmpty) ? item.photoUrl : null; if (itemPhotoUrl == null && item.purchaseRef != null) { final po = purchaseOrders.where((p) => p.poNumber == item.purchaseRef).firstOrNull; if (po != null) { final poItem = po.items.where((i) => (item.huid != null && i.huid == item.huid) || (item.sku != null && i.sku == item.sku) || i.productId == widget.product.id ).firstOrNull ?? po.items.firstOrNull; if (poItem != null && poItem.photoUrl != null && poItem.photoUrl!.isNotEmpty) { itemPhotoUrl = poItem.photoUrl; } } } String? finalDisplayUrl; bool isProductFallback = false; if (itemPhotoUrl != null) { finalDisplayUrl = _resolveImageUrl(itemPhotoUrl); } else if (widget.product.imageIds.isNotEmpty) { finalDisplayUrl = '${DioClient().dio.options.baseUrl}/inventory/products/${widget.product.id}/images/${widget.product.imageIds.first}/content'; isProductFallback = true; } final purchasePrice = weight * purchaseRate; final currentPrice = weight * currentRate; final gainLoss = currentPrice - purchasePrice; final isGain = gainLoss >= 0; return Card( elevation: 0, margin: const EdgeInsets.only(bottom: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide( color: isSold ? Colors.grey.withValues(alpha: 0.2) : Colors.grey.withValues(alpha: 0.3), ), ), color: isSold ? Colors.grey.withValues(alpha: 0.03) : null, child: Padding( padding: const EdgeInsets.all(12.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ FutureBuilder( future: DioClient().storage.read(key: 'jwt_token'), builder: (context, snapshot) { final hasImage = finalDisplayUrl != null; return GestureDetector( onTap: hasImage ? () { final token = snapshot.data; final headers = (token != null && isProductFallback) ? {'Authorization': 'Bearer $token'} : null; Navigator.push( context, MaterialPageRoute( builder: (_) => AttachmentGalleryScreen( images: [ NetworkImage( finalDisplayUrl!, headers: headers, ), ], initialIndex: 0, ), ), ); } : null, child: MouseRegion( cursor: hasImage ? SystemMouseCursors.click : SystemMouseCursors.basic, child: Container( width: 44, height: 44, decoration: BoxDecoration( color: Colors.grey.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), border: Border.all( color: Colors.grey.withValues(alpha: 0.2), ), ), child: hasImage ? Stack( children: [ Positioned.fill( child: ClipRRect( borderRadius: BorderRadius.circular(7), child: Image.network( finalDisplayUrl, fit: BoxFit.cover, headers: (snapshot.hasData && isProductFallback) ? {'Authorization': 'Bearer ${snapshot.data}'} : null, errorBuilder: (context, error, stackTrace) => const Icon( LucideIcons.imageOff, color: Colors.grey, size: 20, ), ), ), ), Positioned( bottom: 2, right: 2, child: Container( padding: const EdgeInsets.all(2), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.5), borderRadius: BorderRadius.circular(4), ), child: const Icon( LucideIcons.maximize2, size: 8, color: Colors.white, ), ), ), ], ) : const Icon( LucideIcons.image, color: Colors.grey, size: 20, ), ), ), ); }, ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item.createdAt != null ? DateFormat('dd MMM yyyy').format(item.createdAt!) : '', style: TextStyle(color: Colors.grey[600], fontSize: 12), ), const SizedBox(height: 4), if (item.purchaseRef != null) InkWell( onTap: () async { try { var pos = ref.read(purchaseOrdersProvider).value; if (pos == null || pos.isEmpty) { pos = await ref.read(purchaseOrdersProvider.future); } final po = pos!.firstWhere( (p) => p.poNumber == item.purchaseRef, orElse: () => throw Exception('Purchase invoice not found'), ); if (mounted) { Navigator.push( context, MaterialPageRoute( builder: (context) => PurchaseOrderDetailsScreen(po: po), ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Could not open purchase invoice: $e')), ); } } }, child: Text( 'INV: ${item.purchaseRef}', style: const TextStyle( color: Colors.blue, decoration: TextDecoration.underline, fontWeight: FontWeight.w600, ), ), ), if (item.huid != null && item.huid!.isNotEmpty) Padding( padding: const EdgeInsets.only(top: 4.0), child: Text('HUID: ${item.huid}', style: const TextStyle(fontWeight: FontWeight.w500)), ), ], ), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: isSold ? Colors.grey.withValues(alpha: 0.15) : Colors.green.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4), ), child: Text( isSold ? 'SOLD' : 'IN STOCK', style: TextStyle( fontSize: 10, fontWeight: FontWeight.bold, color: isSold ? Colors.grey.shade700 : Colors.green, ), ), ), const SizedBox(height: 4), Text( '${weight.toStringAsFixed(3)} $unit', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 14, color: isSold ? Colors.grey.shade600 : null, ), ), const SizedBox(height: 3), Container( padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1.5), decoration: BoxDecoration( color: Colors.amber.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(4), border: Border.all(color: Colors.amber.withValues(alpha: 0.3)), ), child: Text( 'Purity: ${formatPurity(double.tryParse(item.purity ?? '') ?? purityFactor)}', style: TextStyle( fontSize: 10, fontWeight: FontWeight.bold, color: Colors.amber.shade900, ), ), ), ], ), ], ), const Divider(height: 24), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Purchase Rate', style: TextStyle(color: Colors.grey[500], fontSize: 12)), Text('₹${purchaseRate.toStringAsFixed(2)}/$unit', style: const TextStyle(fontWeight: FontWeight.w600)), ], ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text('Purchase Amt', style: TextStyle(color: Colors.grey[500], fontSize: 12)), Text('₹${purchasePrice.toStringAsFixed(2)}', style: const TextStyle(fontWeight: FontWeight.w600)), ], ), ], ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(isSold ? 'Selling Rate' : 'Current Rate', style: TextStyle(color: Colors.grey[500], fontSize: 12)), Text('₹${currentRate.toStringAsFixed(2)}/$unit', style: const TextStyle(fontWeight: FontWeight.w600)), ], ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text(isSold ? 'Valuation' : 'Current Amt', style: TextStyle(color: Colors.grey[500], fontSize: 12)), Text('₹${currentPrice.toStringAsFixed(2)}', style: const TextStyle(fontWeight: FontWeight.w600)), ], ), ], ), const SizedBox(height: 16), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: isGain ? Colors.green.withValues(alpha: 0.1) : Colors.red.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( isGain ? LucideIcons.trendingUp : LucideIcons.trendingDown, color: isGain ? Colors.green : Colors.red, size: 16, ), const SizedBox(width: 8), Text( '${isSold ? "Realized " : ""}${isGain ? 'Gain' : 'Loss'}: ₹${gainLoss.abs().toStringAsFixed(2)}', style: TextStyle( color: isGain ? Colors.green : Colors.red, fontWeight: FontWeight.bold, fontSize: 14, ), ), ], ), ), ], ), ), ); }, ), ), ), ], ); } Widget _buildFilterChip(String label, String value) { final isSelected = _selectedFilter == value; return InkWell( onTap: () => setState(() => _selectedFilter = value), borderRadius: BorderRadius.circular(20), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: isSelected ? Colors.blue : Colors.grey.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(20), ), child: Text( label, style: TextStyle( color: isSelected ? Colors.white : Colors.grey.shade700, fontSize: 12, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), ), ), ); } }