Fixed inventory items photo thumbnail issue

This commit is contained in:
2026-09-01 07:44:23 +05:30
parent bfe0c1efbd
commit 3a5e812947
6 changed files with 130 additions and 22 deletions

View File

@@ -11,6 +11,7 @@ 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;
@@ -26,6 +27,18 @@ class _StockLedgerTabState extends ConsumerState<StockLedgerTab> {
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();
@@ -163,6 +176,31 @@ class _StockLedgerTabState extends ConsumerState<StockLedgerTab> {
}
}
// 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;
@@ -188,29 +226,92 @@ class _StockLedgerTabState extends ConsumerState<StockLedgerTab> {
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.grey.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: FutureBuilder<String?>(
future: DioClient().storage.read(key: 'jwt_token'),
builder: (context, snapshot) {
if (snapshot.hasData && widget.product.imageIds.isNotEmpty) {
return ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network(
'${DioClient().dio.options.baseUrl}/inventory/products/${widget.product.id}/images/${widget.product.imageIds.first}/content',
fit: BoxFit.cover,
headers: {'Authorization': 'Bearer ${snapshot.data}'},
FutureBuilder<String?>(
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),
),
),
);
}
return const Icon(LucideIcons.image, color: Colors.grey, size: 20);
},
),
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(