feat: update outlet

This commit is contained in:
efrilm
2025-08-05 15:25:10 +07:00
parent 5af0259f0a
commit 935b6b9a5b
10 changed files with 1421 additions and 441 deletions
+456 -433
View File
@@ -2,6 +2,7 @@
import 'package:enaklo_pos/core/components/flushbar.dart';
import 'package:enaklo_pos/presentation/home/bloc/current_outlet/current_outlet_bloc.dart';
import 'package:enaklo_pos/presentation/home/bloc/product_loader/product_loader_bloc.dart';
import 'package:enaklo_pos/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart';
import 'package:enaklo_pos/presentation/home/widgets/home_right_title.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -99,455 +100,477 @@ class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Hero(
tag: 'confirmation_screen',
child: Scaffold(
backgroundColor: AppColors.white,
body: Row(
children: [
Expanded(
flex: 3,
child: Align(
alignment: AlignmentDirectional.topStart,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
HomeTitle(
controller: searchController,
onChanged: (value) {
setState(() {
searchQuery = value;
});
},
),
BlocBuilder<ProductLoaderBloc, ProductLoaderState>(
builder: (context, state) {
return Expanded(
child: CustomTabBarV2(
tabTitles: const [
'Semua',
'Makanan',
'Minuman',
'Paket'
],
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(
itemCount: filteredProducts.length,
padding: const EdgeInsets.all(16),
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 180,
mainAxisSpacing: 30,
crossAxisSpacing: 30,
childAspectRatio: 180 / 240,
),
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(
itemCount: filteredProducts.length,
padding: const EdgeInsets.all(16),
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent:
200, // Lebar maksimal tiap item (bisa kamu ubah)
mainAxisSpacing: 30,
crossAxisSpacing: 30,
childAspectRatio: 0.85,
),
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(
itemCount: filteredProducts.length,
padding: const EdgeInsets.all(16),
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent:
200, // Lebar maksimal tiap item (bisa kamu ubah)
mainAxisSpacing: 30,
crossAxisSpacing: 30,
childAspectRatio: 0.85,
),
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(
itemCount: filteredProducts.length,
padding: const EdgeInsets.all(16),
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent:
200, // Lebar maksimal tiap item (bisa kamu ubah)
mainAxisSpacing: 30,
crossAxisSpacing: 30,
childAspectRatio: 0.85,
),
itemBuilder: (context, index) {
return ProductCard(
data: filteredProducts[index],
onCartButton: () {},
);
},
);
}),
),
],
),
);
},
),
],
),
),
),
Expanded(
flex: 2,
child: Align(
alignment: Alignment.topCenter,
child: Material(
color: Colors.white,
return BlocListener<UserUpdateOutletBloc, UserUpdateOutletState>(
listener: (context, state) {
state.maybeWhen(
orElse: () {},
loading: () {},
success: () {
context.pop();
Future.delayed(Duration(milliseconds: 300), () {
AppFlushbar.showSuccess(context, 'Outlet berhasil diubah');
context
.read<CurrentOutletBloc>()
.add(CurrentOutletEvent.currentOutlet());
});
},
error: (message) => AppFlushbar.showError(context, message),
);
},
child: Hero(
tag: 'confirmation_screen',
child: Scaffold(
backgroundColor: AppColors.white,
body: Row(
children: [
Expanded(
flex: 3,
child: Align(
alignment: AlignmentDirectional.topStart,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
HomeRightTitle(
table: widget.table,
HomeTitle(
controller: searchController,
onChanged: (value) {
setState(() {
searchQuery = value;
});
},
),
Padding(
padding: const EdgeInsets.all(16.0)
.copyWith(bottom: 0, top: 27),
child: Column(
children: [
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,
),
),
),
BlocBuilder<ProductLoaderBloc, ProductLoaderState>(
builder: (context, state) {
return Expanded(
child: CustomTabBarV2(
tabTitles: const [
'Semua',
'Makanan',
'Minuman',
'Paket'
],
),
const SpaceHeight(8),
const Divider(),
],
),
),
Expanded(
child: BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: Text('No Items'),
),
loaded: (products,
discountModel,
discount,
discountAmount,
tax,
serviceCharge,
totalQuantity,
totalPrice,
draftName,
orderType) {
if (products.isEmpty) {
return const Center(
child: Text('No Items'),
);
}
return ListView.separated(
shrinkWrap: true,
padding: const EdgeInsets.symmetric(
horizontal: 16),
itemBuilder: (context, index) =>
OrderMenu(data: products[index]),
separatorBuilder: (context, index) =>
const SpaceHeight(1.0),
itemCount: products.length,
);
},
);
},
),
),
Padding(
padding: const EdgeInsets.all(16.0).copyWith(top: 0),
child: Column(
children: [
const Divider(),
const SpaceHeight(16.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Pajak',
style: TextStyle(
color: AppColors.black,
fontWeight: FontWeight.bold,
),
),
BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
final tax = state.maybeWhen(
orElse: () => 0,
loaded: (products,
discountModel,
discount,
discountAmount,
tax,
serviceCharge,
totalQuantity,
totalPrice,
draftName,
orderType) {
if (products.isEmpty) {
return 0;
}
return tax;
});
return Text(
'$tax %',
style: const TextStyle(
color: AppColors.primary,
fontWeight: FontWeight.w600,
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(
itemCount: filteredProducts.length,
padding: const EdgeInsets.all(16),
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 180,
mainAxisSpacing: 30,
crossAxisSpacing: 30,
childAspectRatio: 180 / 240,
),
itemBuilder: (context, index) =>
ProductCard(
data: filteredProducts[index],
onCartButton: () {},
),
);
},
}),
),
],
),
const SpaceHeight(16.0),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Sub total',
style: TextStyle(
color: AppColors.black,
fontWeight: FontWeight.bold,
),
),
BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
final price = state.maybeWhen(
orElse: () => 0,
loaded: (products,
discountModel,
discount,
discountAmount,
tax,
serviceCharge,
totalQuantity,
totalPrice,
draftName,
orderType) {
if (products.isEmpty) {
return 0;
}
return products
.map((e) =>
e.product.price! * e.quantity)
.reduce((value, element) =>
value + element);
});
return Text(
price.currencyFormatRp,
style: const TextStyle(
color: AppColors.primary,
fontWeight: FontWeight.w900,
),
// 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(
itemCount: filteredProducts.length,
padding: const EdgeInsets.all(16),
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent:
200, // Lebar maksimal tiap item (bisa kamu ubah)
mainAxisSpacing: 30,
crossAxisSpacing: 30,
childAspectRatio: 0.85,
),
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(
itemCount: filteredProducts.length,
padding: const EdgeInsets.all(16),
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent:
200, // Lebar maksimal tiap item (bisa kamu ubah)
mainAxisSpacing: 30,
crossAxisSpacing: 30,
childAspectRatio: 0.85,
),
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(
itemCount: filteredProducts.length,
padding: const EdgeInsets.all(16),
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent:
200, // Lebar maksimal tiap item (bisa kamu ubah)
mainAxisSpacing: 30,
crossAxisSpacing: 30,
childAspectRatio: 0.85,
),
itemBuilder: (context, index) {
return ProductCard(
data: filteredProducts[index],
onCartButton: () {},
);
},
);
}),
),
],
),
SpaceHeight(16.0),
BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => Align(
alignment: Alignment.bottomCenter,
child: Button.filled(
borderRadius: 12,
elevation: 1,
disabled: true,
onPressed: () {
context.push(ConfirmPaymentPage(
isTable: widget.table == null
? false
: true,
table: widget.table,
));
},
label: 'Lanjutkan Pembayaran',
),
),
loaded: (items,
discountModel,
discount,
discountAmount,
tax,
serviceCharge,
totalQuantity,
totalPrice,
draftName,
orderType) =>
Align(
alignment: Alignment.bottomCenter,
child: Button.filled(
borderRadius: 12,
elevation: 1,
disabled: items.isEmpty,
onPressed: () {
if (orderType.name == 'dineIn' &&
widget.table == null) {
AppFlushbar.showError(context,
'Mohon pilih meja terlebih dahulu');
return;
}
context.push(ConfirmPaymentPage(
isTable: widget.table == null
? false
: true,
table: widget.table,
));
},
label: 'Lanjutkan Pembayaran',
),
),
);
},
),
],
),
);
},
),
],
),
),
),
),
],
Expanded(
flex: 2,
child: Align(
alignment: Alignment.topCenter,
child: Material(
color: Colors.white,
child: Column(
children: [
HomeRightTitle(
table: widget.table,
),
Padding(
padding: const EdgeInsets.all(16.0)
.copyWith(bottom: 0, top: 27),
child: Column(
children: [
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(),
],
),
),
Expanded(
child: BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: Text('No Items'),
),
loaded: (products,
discountModel,
discount,
discountAmount,
tax,
serviceCharge,
totalQuantity,
totalPrice,
draftName,
orderType) {
if (products.isEmpty) {
return const Center(
child: Text('No Items'),
);
}
return ListView.separated(
shrinkWrap: true,
padding: const EdgeInsets.symmetric(
horizontal: 16),
itemBuilder: (context, index) =>
OrderMenu(data: products[index]),
separatorBuilder: (context, index) =>
const SpaceHeight(1.0),
itemCount: products.length,
);
},
);
},
),
),
Padding(
padding: const EdgeInsets.all(16.0).copyWith(top: 0),
child: Column(
children: [
const Divider(),
const SpaceHeight(16.0),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const Text(
'Pajak',
style: TextStyle(
color: AppColors.black,
fontWeight: FontWeight.bold,
),
),
BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
final tax = state.maybeWhen(
orElse: () => 0,
loaded: (products,
discountModel,
discount,
discountAmount,
tax,
serviceCharge,
totalQuantity,
totalPrice,
draftName,
orderType) {
if (products.isEmpty) {
return 0;
}
return tax;
});
return Text(
'$tax %',
style: const TextStyle(
color: AppColors.primary,
fontWeight: FontWeight.w600,
),
);
},
),
],
),
const SpaceHeight(16.0),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
const Text(
'Sub total',
style: TextStyle(
color: AppColors.black,
fontWeight: FontWeight.bold,
),
),
BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
final price = state.maybeWhen(
orElse: () => 0,
loaded: (products,
discountModel,
discount,
discountAmount,
tax,
serviceCharge,
totalQuantity,
totalPrice,
draftName,
orderType) {
if (products.isEmpty) {
return 0;
}
return products
.map((e) =>
e.product.price! *
e.quantity)
.reduce((value, element) =>
value + element);
});
return Text(
price.currencyFormatRp,
style: const TextStyle(
color: AppColors.primary,
fontWeight: FontWeight.w900,
),
);
},
),
],
),
SpaceHeight(16.0),
BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => Align(
alignment: Alignment.bottomCenter,
child: Button.filled(
borderRadius: 12,
elevation: 1,
disabled: true,
onPressed: () {
context.push(ConfirmPaymentPage(
isTable: widget.table == null
? false
: true,
table: widget.table,
));
},
label: 'Lanjutkan Pembayaran',
),
),
loaded: (items,
discountModel,
discount,
discountAmount,
tax,
serviceCharge,
totalQuantity,
totalPrice,
draftName,
orderType) =>
Align(
alignment: Alignment.bottomCenter,
child: Button.filled(
borderRadius: 12,
elevation: 1,
disabled: items.isEmpty,
onPressed: () {
if (orderType.name == 'dineIn' &&
widget.table == null) {
AppFlushbar.showError(context,
'Mohon pilih meja terlebih dahulu');
return;
}
context.push(ConfirmPaymentPage(
isTable: widget.table == null
? false
: true,
table: widget.table,
));
},
label: 'Lanjutkan Pembayaran',
),
),
);
},
),
],
),
),
],
),
),
),
),
],
),
),
),
);