// ignore_for_file: public_member_api_docs, sort_constructors_first import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_posresto_app/core/extensions/build_context_ext.dart'; import 'package:flutter_posresto_app/core/extensions/int_ext.dart'; import 'package:flutter_posresto_app/core/extensions/string_ext.dart'; import 'package:flutter_posresto_app/data/models/response/table_model.dart'; import 'package:flutter_posresto_app/presentation/home/bloc/local_product/local_product_bloc.dart'; import 'package:flutter_posresto_app/presentation/home/dialog/discount_dialog.dart'; import 'package:flutter_posresto_app/presentation/home/dialog/tax_dialog.dart'; import 'package:flutter_posresto_app/presentation/home/pages/confirm_payment_page.dart'; import 'package:flutter_posresto_app/presentation/home/pages/dashboard_page.dart'; import 'package:flutter_posresto_app/data/datasources/product_local_datasource.dart'; import 'package:flutter_posresto_app/presentation/setting/bloc/sync_product/sync_product_bloc.dart'; import 'package:flutter_posresto_app/data/models/response/product_response_model.dart'; import '../../../core/assets/assets.gen.dart'; import '../../../core/components/buttons.dart'; import '../../../core/components/spaces.dart'; import '../../../core/constants/colors.dart'; import '../bloc/checkout/checkout_bloc.dart'; import '../dialog/service_dialog.dart'; import '../widgets/column_button.dart'; import '../widgets/custom_tab_bar.dart'; import '../widgets/home_title.dart'; import '../widgets/order_menu.dart'; import '../widgets/product_card.dart'; class HomePage extends StatefulWidget { final bool isTable; final TableModel? table; const HomePage({ Key? key, required this.isTable, this.table, }) : super(key: key); @override State createState() => _HomePageState(); } class _HomePageState extends State { final searchController = TextEditingController(); String searchQuery = ''; @override void initState() { // First sync products from API, then load local products _syncAndLoadProducts(); super.initState(); } void _syncAndLoadProducts() { // Trigger sync from API first context.read().add(const SyncProductEvent.syncProduct()); // Also load local products initially in case sync fails or takes time context .read() .add(const LocalProductEvent.getLocalProduct()); // Initialize checkout with tax and service charge settings context.read().add(const CheckoutEvent.started()); } void onCategoryTap(int index) { searchController.clear(); setState(() { searchQuery = ''; }); } List _filterProducts(List products) { if (searchQuery.isEmpty) { return products; } return products.where((product) { final productName = product.name?.toLowerCase() ?? ''; final queryLower = searchQuery.toLowerCase(); return productName.contains(queryLower); }).toList(); } List _filterProductsByCategory(List products, int categoryId) { final filteredBySearch = _filterProducts(products); return filteredBySearch.where((element) => element.category?.id == categoryId).toList(); } @override Widget build(BuildContext context) { return Scaffold( body: BlocListener( listener: (context, state) { state.maybeWhen( orElse: () {}, error: (message) { // If sync fails, still try to load local products context .read() .add(const LocalProductEvent.getLocalProduct()); }, loaded: (productResponseModel) async { // Store context reference before async operations final localProductBloc = context.read(); // Save synced products to local database await ProductLocalDatasource.instance.deleteAllProducts(); await ProductLocalDatasource.instance.insertProducts( productResponseModel.data!, ); // Then load local products to display localProductBloc.add(const LocalProductEvent.getLocalProduct()); }, ); }, child: Row( children: [ Expanded( flex: 3, child: Align( alignment: AlignmentDirectional.topStart, child: Padding( padding: const EdgeInsets.all(16.0), child: SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ HomeTitle( controller: searchController, onChanged: (value) { setState(() { searchQuery = value; }); }, ), const SizedBox(height: 24), BlocBuilder( builder: (context, state) { return CustomTabBar( tabTitles: const [ 'Semua', 'Makanan', 'Minuman', 'Snack' ], initialTabIndex: 0, tabViews: [ // All Products Tab SizedBox( child: state.maybeWhen(orElse: () { return const Center( child: CircularProgressIndicator(), ); }, loading: () { return const Center( child: CircularProgressIndicator(), ); }, loaded: (products) { final filteredProducts = _filterProducts(products); if (filteredProducts.isEmpty) { return const Center( child: Text('No Items Found'), ); } return GridView.builder( shrinkWrap: true, itemCount: filteredProducts.length, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( childAspectRatio: 0.85, crossAxisCount: 3, crossAxisSpacing: 30.0, mainAxisSpacing: 30.0, ), itemBuilder: (context, index) => ProductCard( data: filteredProducts[index], onCartButton: () {}, ), ); }), ), // Makanan Tab SizedBox( child: state.maybeWhen(orElse: () { return const Center( child: CircularProgressIndicator(), ); }, loading: () { return const Center( child: CircularProgressIndicator(), ); }, loaded: (products) { if (products.isEmpty) { return const Center( child: Text('No Items'), ); } final filteredProducts = _filterProductsByCategory(products, 1); return filteredProducts.isEmpty ? const _IsEmpty() : GridView.builder( shrinkWrap: true, itemCount: filteredProducts.length, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( childAspectRatio: 0.85, crossAxisCount: 3, crossAxisSpacing: 30.0, mainAxisSpacing: 30.0, ), itemBuilder: (context, index) => ProductCard( data: filteredProducts[index], onCartButton: () {}, ), ); }), ), // Minuman Tab SizedBox( child: state.maybeWhen(orElse: () { return const Center( child: CircularProgressIndicator(), ); }, loading: () { return const Center( child: CircularProgressIndicator(), ); }, loaded: (products) { if (products.isEmpty) { return const Center( child: Text('No Items'), ); } final filteredProducts = _filterProductsByCategory(products, 2); return filteredProducts.isEmpty ? const _IsEmpty() : GridView.builder( shrinkWrap: true, itemCount: filteredProducts.length, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( childAspectRatio: 0.85, crossAxisCount: 3, crossAxisSpacing: 30.0, mainAxisSpacing: 30.0, ), itemBuilder: (context, index) { return ProductCard( data: filteredProducts[index], onCartButton: () {}, ); }, ); }), ), // Snack Tab SizedBox( child: state.maybeWhen(orElse: () { return const Center( child: CircularProgressIndicator(), ); }, loading: () { return const Center( child: CircularProgressIndicator(), ); }, loaded: (products) { if (products.isEmpty) { return const Center( child: Text('No Items'), ); } final filteredProducts = _filterProductsByCategory(products, 3); return filteredProducts.isEmpty ? const _IsEmpty() : GridView.builder( shrinkWrap: true, itemCount: filteredProducts.length, physics: const NeverScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( childAspectRatio: 0.85, crossAxisCount: 3, crossAxisSpacing: 30.0, mainAxisSpacing: 30.0, ), itemBuilder: (context, index) { return ProductCard( data: filteredProducts[index], onCartButton: () {}, ); }, ); }), ), ], ); }, ), ], ), ), ), ), ), Expanded( flex: 2, child: Align( alignment: Alignment.topCenter, child: Stack( children: [ SingleChildScrollView( padding: const EdgeInsets.all(24.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ GestureDetector( onTap: () { if (widget.table == null) { context.push(DashboardPage( index: 1, )); } }, child: Text( 'Meja: ${widget.table == null ? 'Belum Pilih Meja' : '${widget.table!.id}'}', style: TextStyle( color: AppColors.primary, fontSize: 20, fontWeight: FontWeight.w600, ), ), ), const SpaceHeight(8.0), Button.filled( width: 180.0, height: 40, onPressed: () {}, label: 'Pesanan#', ), // Row( // children: [ // Button.filled( // width: 120.0, // height: 40, // onPressed: () {}, // label: 'Dine In', // ), // const SpaceWidth(8.0), // Button.outlined( // width: 100.0, // height: 40, // onPressed: () {}, // label: 'To Go', // ), // ], // ), const SpaceHeight(16.0), const Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Item', style: TextStyle( color: AppColors.primary, fontSize: 16, fontWeight: FontWeight.w600, ), ), SizedBox( width: 130, ), SizedBox( width: 50.0, child: Text( 'Qty', style: TextStyle( color: AppColors.primary, fontSize: 16, fontWeight: FontWeight.w600, ), ), ), SizedBox( child: Text( 'Price', style: TextStyle( color: AppColors.primary, fontSize: 16, fontWeight: FontWeight.w600, ), ), ), ], ), const SpaceHeight(8), const Divider(), const SpaceHeight(8), BlocBuilder( builder: (context, state) { return state.maybeWhen( orElse: () => const Center( child: Text('No Items'), ), loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName) { if (products.isEmpty) { return const Center( child: Text('No Items'), ); } return ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => OrderMenu(data: products[index]), separatorBuilder: (context, index) => const SpaceHeight(1.0), itemCount: products.length, ); }, ); }, ), const SpaceHeight(8.0), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ColumnButton( label: 'Diskon', svgGenImage: Assets.icons.diskon, onPressed: () => showDialog( context: context, barrierDismissible: false, builder: (context) => const DiscountDialog(), ), ), ColumnButton( label: 'Pajak PB1', svgGenImage: Assets.icons.pajak, onPressed: () => showDialog( context: context, builder: (context) => const TaxDialog(), ), ), ColumnButton( label: 'Layanan', svgGenImage: Assets.icons.layanan, onPressed: () => showDialog( context: context, builder: (context) => const ServiceDialog(), ), ), ], ), const SpaceHeight(8.0), const Divider(), const SpaceHeight(8.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Pajak PB1', style: TextStyle(color: AppColors.grey), ), BlocBuilder( builder: (context, state) { final tax = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName) { if (products.isEmpty) { return 0; } return tax; }); return Text( '$tax %', style: const TextStyle( color: AppColors.primary, fontWeight: FontWeight.w600, ), ); }, ), ], ), const SpaceHeight(8.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Layanan', style: TextStyle(color: AppColors.grey), ), BlocBuilder( builder: (context, state) { final serviceCharge = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName) { return serviceCharge; }); return Text( '$serviceCharge %', style: const TextStyle( color: AppColors.primary, fontWeight: FontWeight.w600, ), ); }, ), ], ), const SpaceHeight(8.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Diskon', style: TextStyle(color: AppColors.grey), ), BlocBuilder( builder: (context, state) { final discount = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName) { if (discountModel == null) { return 0; } return discountModel.value! .replaceAll('.00', '') .toIntegerFromText; }); return Text( '$discount %', style: const TextStyle( color: AppColors.primary, fontWeight: FontWeight.w600, ), ); }, ), ], ), const SpaceHeight(8.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Sub total', style: TextStyle( color: AppColors.grey, fontWeight: FontWeight.bold, fontSize: 16), ), BlocBuilder( builder: (context, state) { final price = state.maybeWhen( orElse: () => 0, loaded: (products, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName) { if (products.isEmpty) { return 0; } return products .map((e) => e.product.price! .toIntegerFromText * e.quantity) .reduce((value, element) => value + element); }); return Text( price.currencyFormatRp, style: const TextStyle( color: AppColors.primary, fontWeight: FontWeight.bold, fontSize: 16), ); }, ), ], ), const SpaceHeight(100.0), ], ), ), Align( alignment: Alignment.bottomCenter, child: ColoredBox( color: AppColors.white, child: Padding( padding: const EdgeInsets.symmetric( horizontal: 24.0, vertical: 16.0), child: Button.filled( onPressed: () { context.push(ConfirmPaymentPage( isTable: widget.isTable, table: widget.table, )); }, label: 'Lanjutkan Pembayaran', ), ), ), ), ], ), ), ), ], ), ), ), ); } } class _IsEmpty extends StatelessWidget { const _IsEmpty(); @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SpaceHeight(40), Assets.icons.noProduct.svg(), const SizedBox(height: 40.0), const Text( 'Belum Ada Produk', textAlign: TextAlign.center, style: TextStyle(fontSize: 16), ), ], ); } }