diff --git a/lib/presentation/pages/product/product_page.dart b/lib/presentation/pages/product/product_page.dart index c44fecb..8cce6f0 100644 --- a/lib/presentation/pages/product/product_page.dart +++ b/lib/presentation/pages/product/product_page.dart @@ -18,10 +18,13 @@ class ProductPage extends StatefulWidget { State createState() => _ProductPageState(); } +enum ViewType { grid, list } + class _ProductPageState extends State with TickerProviderStateMixin { String selectedCategory = 'Semua'; List categories = ['Semua', 'Makanan', 'Minuman', 'Snack', 'Dessert']; + ViewType currentViewType = ViewType.grid; // Animation late AnimationController _rotationController; @@ -156,7 +159,7 @@ class _ProductPageState extends State slivers: [ _buildSliverAppBar(), _buildCategoryFilter(), - _buildProductGrid(), + _buildProductContent(), _buildEmptyState(), ], ), @@ -173,6 +176,12 @@ class _ProductPageState extends State actions: [ ActionIconButton(onTap: () {}, icon: LineIcons.search), ActionIconButton(onTap: _showAddProductDialog, icon: LineIcons.plus), + ActionIconButton( + onTap: _toggleViewType, + icon: currentViewType == ViewType.grid + ? LineIcons.list + : LineIcons.thLarge, + ), ActionIconButton(onTap: _showOptionsMenu, icon: LineIcons.filter), ], ); @@ -193,11 +202,17 @@ class _ProductPageState extends State ); } - Widget _buildProductGrid() { + Widget _buildProductContent() { if (filteredProducts.isEmpty) { return const SliverToBoxAdapter(child: SizedBox.shrink()); } + return currentViewType == ViewType.grid + ? _buildProductGrid() + : _buildProductList(); + } + + Widget _buildProductGrid() { return SliverPadding( padding: const EdgeInsets.all(16.0), sliver: SliverGrid( @@ -215,6 +230,153 @@ class _ProductPageState extends State ); } + Widget _buildProductList() { + return SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + sliver: SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + final product = filteredProducts[index]; + return _buildProductListItem(product); + }, childCount: filteredProducts.length), + ), + ); + } + + Widget _buildProductListItem(Product product) { + return Container( + margin: const EdgeInsets.only(bottom: 12.0), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.1), + spreadRadius: 1, + blurRadius: 6, + offset: const Offset(0, 2), + ), + ], + ), + child: InkWell( + onTap: () {}, + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + children: [ + // Product Image + Container( + width: 80, + height: 80, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: AppColor.background, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.asset( + product.imageUrl, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Container( + color: AppColor.background, + child: Icon( + Icons.image_outlined, + color: AppColor.textLight, + size: 32, + ), + ); + }, + ), + ), + ), + const SizedBox(width: 12), + // Product Info + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + product.name, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + Text( + product.category, + style: TextStyle(fontSize: 12, color: AppColor.textLight), + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Rp ${_formatPrice(product.price)}', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: AppColor.primary, + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: product.isActive + ? Colors.green.withOpacity(0.1) + : Colors.red.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + 'Stock: ${product.stock}', + style: TextStyle( + fontSize: 12, + color: product.isActive + ? Colors.green + : Colors.red, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ], + ), + ), + // Action Button + IconButton( + onPressed: () {}, + icon: const Icon(Icons.more_vert), + color: AppColor.textLight, + ), + ], + ), + ), + ), + ); + } + + String _formatPrice(int price) { + return price.toString().replaceAllMapped( + RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'), + (Match m) => '${m[1]}.', + ); + } + + void _toggleViewType() { + setState(() { + currentViewType = currentViewType == ViewType.grid + ? ViewType.list + : ViewType.grid; + }); + } + Widget _buildEmptyState() { if (filteredProducts.isNotEmpty) { return const SliverToBoxAdapter(child: SizedBox.shrink()); @@ -283,6 +445,20 @@ class _ProductPageState extends State child: Column( mainAxisSize: MainAxisSize.min, children: [ + ListTile( + leading: Icon( + currentViewType == ViewType.grid ? Icons.list : Icons.grid_view, + ), + title: Text( + currentViewType == ViewType.grid + ? 'Tampilan List' + : 'Tampilan Grid', + ), + onTap: () { + Navigator.pop(context); + _toggleViewType(); + }, + ), ListTile( leading: const Icon(Icons.sort), title: const Text('Urutkan'), diff --git a/lib/presentation/pages/product/widgets/appbar.dart b/lib/presentation/pages/product/widgets/appbar.dart index b50a9f6..a81d162 100644 --- a/lib/presentation/pages/product/widgets/appbar.dart +++ b/lib/presentation/pages/product/widgets/appbar.dart @@ -9,7 +9,7 @@ class ProductAppbar extends StatelessWidget { @override Widget build(BuildContext context) { return FlexibleSpaceBar( - centerTitle: true, + titlePadding: const EdgeInsets.only(left: 50, bottom: 16), title: Text( 'Produk', style: AppStyle.xl.copyWith(