import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import '../../../common/theme/theme.dart'; import '../../components/appbar/appbar.dart'; import 'widgets/ingredient_tile.dart'; import 'widgets/product_tile.dart'; import 'widgets/stat_card.dart'; import 'widgets/tabbar_delegate.dart'; // Sample inventory data for products class ProductItem { final String id; final String name; final String category; final int quantity; final double price; final String status; final String image; ProductItem({ required this.id, required this.name, required this.category, required this.quantity, required this.price, required this.status, required this.image, }); } // Sample inventory data for ingredients class IngredientItem { final String id; final String name; final String unit; final double quantity; final double minQuantity; final String status; final String image; IngredientItem({ required this.id, required this.name, required this.unit, required this.quantity, required this.minQuantity, required this.status, required this.image, }); } // Custom SliverPersistentHeaderDelegate untuk TabBar @RoutePage() class InventoryPage extends StatefulWidget { const InventoryPage({super.key}); @override State createState() => _InventoryPageState(); } class _InventoryPageState extends State with TickerProviderStateMixin { late AnimationController _fadeAnimationController; late AnimationController _slideAnimationController; late Animation _fadeAnimation; late Animation _slideAnimation; late TabController _tabController; final List productItems = [ ProductItem( id: '1', name: 'Laptop Gaming ASUS ROG', category: 'Elektronik', quantity: 5, price: 15000000, status: 'available', image: '💻', ), ProductItem( id: '2', name: 'Kemeja Formal Pria', category: 'Fashion', quantity: 25, price: 250000, status: 'available', image: '👔', ), ProductItem( id: '3', name: 'Smartphone Samsung Galaxy', category: 'Elektronik', quantity: 12, price: 8500000, status: 'available', image: '📱', ), ProductItem( id: '4', name: 'Tas Ransel Travel', category: 'Fashion', quantity: 8, price: 350000, status: 'low_stock', image: '🎒', ), ProductItem( id: '4', name: 'Tas Ransel Travel', category: 'Fashion', quantity: 8, price: 350000, status: 'low_stock', image: '🎒', ), ProductItem( id: '4', name: 'Tas Ransel Travel', category: 'Fashion', quantity: 8, price: 350000, status: 'low_stock', image: '🎒', ), ]; final List ingredientItems = [ IngredientItem( id: '1', name: 'Tepung Terigu', unit: 'kg', quantity: 50.5, minQuantity: 10.0, status: 'available', image: '🌾', ), IngredientItem( id: '2', name: 'Gula Pasir', unit: 'kg', quantity: 2.5, minQuantity: 5.0, status: 'low_stock', image: '🍬', ), IngredientItem( id: '3', name: 'Telur Ayam', unit: 'butir', quantity: 120, minQuantity: 50, status: 'available', image: '🥚', ), IngredientItem( id: '4', name: 'Susu Segar', unit: 'liter', quantity: 0, minQuantity: 10.0, status: 'out_of_stock', image: '🥛', ), IngredientItem( id: '5', name: 'Mentega', unit: 'kg', quantity: 15.2, minQuantity: 5.0, status: 'available', image: '🧈', ), ]; @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); _fadeAnimationController = AnimationController( duration: const Duration(milliseconds: 1000), vsync: this, ); _slideAnimationController = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ); _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _fadeAnimationController, curve: Curves.easeInOut, ), ); _slideAnimation = Tween(begin: const Offset(0.0, 0.3), end: Offset.zero).animate( CurvedAnimation( parent: _slideAnimationController, curve: Curves.easeOutBack, ), ); _fadeAnimationController.forward(); _slideAnimationController.forward(); } @override void dispose() { _fadeAnimationController.dispose(); _slideAnimationController.dispose(); _tabController.dispose(); super.dispose(); } Color getStatusColor(String status) { switch (status) { case 'available': return AppColor.success; case 'low_stock': return AppColor.warning; case 'out_of_stock': return AppColor.error; default: return AppColor.textSecondary; } } String getStatusText(String status) { switch (status) { case 'available': return 'Tersedia'; case 'low_stock': return 'Stok Rendah'; case 'out_of_stock': return 'Habis'; default: return 'Unknown'; } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColor.background, body: FadeTransition( opacity: _fadeAnimation, child: SlideTransition( position: _slideAnimation, child: NestedScrollView( headerSliverBuilder: (context, innerBoxIsScrolled) { return [ _buildSliverAppBar(), SliverPersistentHeader( pinned: true, delegate: InventorySliverTabBarDelegate( tabBar: TabBar( controller: _tabController, indicator: BoxDecoration( gradient: LinearGradient( colors: AppColor.primaryGradient, begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(25), boxShadow: [ BoxShadow( color: AppColor.primary.withOpacity(0.3), blurRadius: 12, offset: const Offset(0, 4), ), ], ), indicatorSize: TabBarIndicatorSize.tab, indicatorPadding: const EdgeInsets.all(6), labelColor: AppColor.textWhite, unselectedLabelColor: AppColor.textSecondary, labelStyle: const TextStyle( fontWeight: FontWeight.w700, fontSize: 13, ), unselectedLabelStyle: const TextStyle( fontWeight: FontWeight.w500, fontSize: 13, ), dividerColor: Colors.transparent, splashFactory: NoSplash.splashFactory, overlayColor: MaterialStateProperty.all( Colors.transparent, ), tabs: [ Tab( height: 40, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12), child: const Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.inventory_2_rounded, size: 16), SizedBox(width: 6), Text('Produk'), ], ), ), ), Tab( height: 40, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12), child: const Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.restaurant_menu_rounded, size: 16), SizedBox(width: 6), Text('Bahan'), ], ), ), ), ], ), ), ), ]; }, body: TabBarView( controller: _tabController, children: [_buildProductTab(), _buildIngredientTab()], ), ), ), ), ); } Widget _buildSliverAppBar() { return SliverAppBar( expandedHeight: 120, floating: false, pinned: true, elevation: 0, backgroundColor: AppColor.primary, flexibleSpace: CustomAppBar(title: 'Inventaris'), ); } Widget _buildProductTab() { return CustomScrollView( slivers: [ SliverToBoxAdapter(child: _buildProductStats()), SliverPadding( padding: const EdgeInsets.all(16), sliver: SliverGrid( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 12, mainAxisSpacing: 12, childAspectRatio: 0.75, ), delegate: SliverChildBuilderDelegate( (context, index) => InventoryProductTile(item: productItems[index]), childCount: productItems.length, ), ), ), ], ); } Widget _buildIngredientTab() { return CustomScrollView( slivers: [ SliverToBoxAdapter(child: _buildIngredientStats()), SliverPadding( padding: const EdgeInsets.all(16), sliver: SliverList( delegate: SliverChildBuilderDelegate( (context, index) => InventoryIngredientTile(item: ingredientItems[index]), childCount: ingredientItems.length, ), ), ), ], ); } Widget _buildProductStats() { final totalProducts = productItems.length; final availableProducts = productItems .where((item) => item.status == 'available') .length; final lowStockProducts = productItems .where((item) => item.status == 'low_stock') .length; return Container( margin: const EdgeInsets.all(16), child: Column( children: [ Row( children: [ Expanded( child: _buildStatCard( 'Total Produk', totalProducts.toString(), Icons.inventory_2_rounded, AppColor.primary, '+12%', ), ), const SizedBox(width: 16), Expanded( child: _buildStatCard( 'Tersedia', availableProducts.toString(), Icons.check_circle_rounded, AppColor.success, '+5%', ), ), ], ), const SizedBox(height: 16), Row( children: [ Expanded( child: _buildStatCard( 'Stok Rendah', lowStockProducts.toString(), Icons.warning_rounded, AppColor.warning, '-8%', ), ), const SizedBox(width: 16), Expanded( child: Container(), // Empty space for balance ), ], ), ], ), ); } Widget _buildIngredientStats() { final totalIngredients = ingredientItems.length; final availableIngredients = ingredientItems .where((item) => item.status == 'available') .length; final lowStockIngredients = ingredientItems .where((item) => item.status == 'low_stock') .length; final outOfStockIngredients = ingredientItems .where((item) => item.status == 'out_of_stock') .length; return Container( margin: const EdgeInsets.all(16), child: Column( children: [ Row( children: [ Expanded( child: _buildStatCard( 'Total Bahan', totalIngredients.toString(), Icons.restaurant_menu_rounded, AppColor.primary, '+8%', ), ), const SizedBox(width: 16), Expanded( child: _buildStatCard( 'Tersedia', availableIngredients.toString(), Icons.check_circle_rounded, AppColor.success, '+15%', ), ), ], ), const SizedBox(height: 16), Row( children: [ Expanded( child: _buildStatCard( 'Stok Kurang', lowStockIngredients.toString(), Icons.warning_rounded, AppColor.warning, '-3%', ), ), const SizedBox(width: 16), Expanded( child: _buildStatCard( 'Habis', outOfStockIngredients.toString(), Icons.error_rounded, AppColor.error, '+1', ), ), ], ), ], ), ); } Widget _buildStatCard( String title, String value, IconData icon, Color color, String change, ) { return TweenAnimationBuilder( tween: Tween(begin: 0, end: 1), duration: const Duration(milliseconds: 800), builder: (context, animationValue, child) { return Transform.scale( scale: animationValue, child: InventoryStatCard( title: title, value: value, icon: icon, color: color, change: change, ), ); }, ); } }