first commit

This commit is contained in:
Aditya Siregar
2025-07-30 22:38:44 +07:00
commit 73320561b0
444 changed files with 64633 additions and 0 deletions
@@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import '../../../core/assets/assets.gen.dart';
import '../../../core/components/spaces.dart';
import '../../../core/constants/colors.dart';
class ColumnButton extends StatelessWidget {
final String label;
final SvgGenImage svgGenImage;
final VoidCallback onPressed;
const ColumnButton({
super.key,
required this.label,
required this.svgGenImage,
required this.onPressed,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onPressed,
child: Column(
children: [
Container(
padding: const EdgeInsets.all(8.0),
height: 40.0,
width: 40.0,
decoration: BoxDecoration(
color: AppColors.white,
border: Border.all(color: AppColors.primary),
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
),
child: svgGenImage.svg(),
),
const SpaceHeight(8.0),
Text(
label,
style: const TextStyle(
color: AppColors.primary,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
}
@@ -0,0 +1,77 @@
import 'package:flutter/material.dart';
import '../../../core/constants/colors.dart';
class CustomTabBar extends StatefulWidget {
final List<String> tabTitles;
final int initialTabIndex;
final List<Widget> tabViews;
const CustomTabBar({
super.key,
required this.tabTitles,
required this.initialTabIndex,
required this.tabViews,
});
@override
State<CustomTabBar> createState() => _CustomTabBarState();
}
class _CustomTabBarState extends State<CustomTabBar> {
late int _selectedIndex;
@override
void initState() {
super.initState();
_selectedIndex = widget.initialTabIndex;
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: List.generate(
widget.tabTitles.length,
(index) => GestureDetector(
onTap: () {
setState(() {
_selectedIndex = index;
});
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 14),
margin: const EdgeInsets.only(right: 32),
decoration: BoxDecoration(
border: _selectedIndex == index
? const Border(
bottom: BorderSide(
width: 3.0,
color: AppColors.primary,
),
)
: null,
),
child: Text(
widget.tabTitles[index],
style: const TextStyle(
color: AppColors.primary,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 18.0),
child: widget.tabViews[_selectedIndex],
),
],
);
}
}
@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/extensions/date_time_ext.dart';
import '../../../core/components/search_input.dart';
import '../../../core/constants/colors.dart';
class HomeTitle extends StatelessWidget {
final TextEditingController controller;
final Function(String value)? onChanged;
const HomeTitle({
super.key,
required this.controller,
this.onChanged,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Enaklo POS',
style: TextStyle(
color: AppColors.primary,
fontSize: 22,
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 4.0),
Text(
DateTime.now().toFormattedDate(),
style: const TextStyle(
color: AppColors.subtitle,
fontSize: 16,
),
),
],
),
SizedBox(
width: 300.0,
child: SearchInput(
controller: controller,
onChanged: onChanged,
hintText: 'Search..',
),
),
],
);
}
}
@@ -0,0 +1,84 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
import 'package:enaklo_pos/data/models/response/product_response_model.dart';
import '../../../core/components/buttons.dart';
import '../../../core/components/spaces.dart';
import '../../../core/constants/colors.dart';
class ItemNotesDialog extends StatefulWidget {
final ProductQuantity item;
const ItemNotesDialog({
super.key,
required this.item,
});
@override
State<ItemNotesDialog> createState() => _ItemNotesDialogState();
}
class _ItemNotesDialogState extends State<ItemNotesDialog> {
late TextEditingController notesController;
@override
void initState() {
super.initState();
notesController = TextEditingController(text: widget.item.notes);
}
@override
void dispose() {
notesController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Add Notes'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.item.product.name ?? 'Product',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SpaceHeight(16.0),
TextField(
controller: notesController,
maxLines: 3,
decoration: const InputDecoration(
hintText: 'Enter notes for this item...',
border: OutlineInputBorder(),
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'),
),
Button.filled(
onPressed: () {
context.read<CheckoutBloc>().add(
CheckoutEvent.updateItemNotes(
widget.item.product,
notesController.text,
),
);
Navigator.of(context).pop();
},
label: 'Save',
),
],
);
}
}
@@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import '../../../core/constants/colors.dart';
class NavItem extends StatelessWidget {
final String iconPath;
final bool isActive;
final VoidCallback onTap;
final Color color;
const NavItem({
super.key,
required this.iconPath,
required this.isActive,
required this.onTap,
this.color = AppColors.white,
});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
borderRadius: const BorderRadius.all(Radius.circular(16.0)),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(12.0)),
child: ColoredBox(
color: isActive
? AppColors.disabled.withOpacity(0.25)
: Colors.transparent,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 25.0,
height: 25.0,
child: SvgPicture.asset(
iconPath,
colorFilter: ColorFilter.mode(
color,
BlendMode.srcIn,
),
),
),
],
),
),
),
),
),
);
}
}
@@ -0,0 +1,160 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/core/constants/variables.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
import 'package:enaklo_pos/presentation/home/widgets/item_notes_dialog.dart';
import '../../../core/components/spaces.dart';
import '../../../core/constants/colors.dart';
class OrderMenu extends StatelessWidget {
final ProductQuantity data;
const OrderMenu({super.key, required this.data});
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
children: [
Flexible(
child: ListTile(
contentPadding: EdgeInsets.zero,
leading: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(50.0)),
child:
// Icon(
// Icons.fastfood,
// size: 50,
// color: AppColors.primary,
// ),
CachedNetworkImage(
imageUrl: data.product.image!.contains('http')
? data.product.image!
: '${Variables.baseUrl}/${data.product.image}',
width: 50.0,
height: 50.0,
fit: BoxFit.cover,
),
),
title: Row(
children: [
Expanded(
child: Text(data.product.name ?? "-",
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
)),
),
GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (context) => ItemNotesDialog(item: data),
);
},
child: const Icon(
Icons.edit_note,
size: 20,
color: AppColors.primary,
),
),
],
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(data.product.price!.toIntegerFromText.currencyFormatRp),
if (data.notes.isNotEmpty) ...[
const SpaceHeight(4.0),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
vertical: 4.0,
),
decoration: BoxDecoration(
color: AppColors.primary.withOpacity(0.1),
borderRadius: BorderRadius.circular(4.0),
),
child: Text(
'Notes: ${data.notes}',
style: const TextStyle(
fontSize: 12,
color: AppColors.primary,
fontStyle: FontStyle.italic,
),
),
),
],
],
),
),
),
Row(
children: [
GestureDetector(
onTap: () {
context
.read<CheckoutBloc>()
.add(CheckoutEvent.removeItem(data.product));
},
child: Container(
width: 30,
height: 30,
color: AppColors.white,
child: const Icon(
Icons.remove_circle,
color: AppColors.primary,
),
),
),
SizedBox(
width: 30.0,
child: Center(
child: Text(
data.quantity.toString(),
)),
),
GestureDetector(
onTap: () {
context
.read<CheckoutBloc>()
.add(CheckoutEvent.addItem(data.product));
},
child: Container(
width: 30,
height: 30,
color: AppColors.white,
child: const Icon(
Icons.add_circle,
color: AppColors.primary,
),
),
),
],
),
const SpaceWidth(8),
SizedBox(
width: 80.0,
child: Text(
(data.product.price!.toIntegerFromText * data.quantity)
.currencyFormatRp,
textAlign: TextAlign.right,
style: const TextStyle(
color: AppColors.primary,
fontWeight: FontWeight.bold,
),
),
),
],
),
],
);
}
}
@@ -0,0 +1,74 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart';
import 'package:enaklo_pos/presentation/home/models/order_type.dart';
import '../../../core/components/spaces.dart';
import '../../../core/constants/colors.dart';
class OrderTypeSelector extends StatelessWidget {
const OrderTypeSelector({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const SizedBox.shrink(),
loaded: (items, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Order Type',
style: TextStyle(
color: AppColors.primary,
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
const SpaceHeight(12.0),
Wrap(
spacing: 12.0,
runSpacing: 8.0,
children: OrderType.values.map((type) {
final isSelected = orderType == type;
return GestureDetector(
onTap: () {
context.read<CheckoutBloc>().add(
CheckoutEvent.updateOrderType(type),
);
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8.0,
),
decoration: BoxDecoration(
color: isSelected ? AppColors.primary : AppColors.white,
border: Border.all(
color: isSelected ? AppColors.primary : AppColors.grey,
width: 1.0,
),
borderRadius: BorderRadius.circular(8.0),
),
child: Text(
type.value,
style: TextStyle(
color: isSelected ? AppColors.white : AppColors.black,
fontSize: 14,
fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
),
),
),
);
}).toList(),
),
],
);
},
);
},
);
}
}
@@ -0,0 +1,189 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/core/constants/variables.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/data/models/response/product_response_model.dart';
import 'package:enaklo_pos/presentation/home/bloc/checkout/checkout_bloc.dart';
import '../../../core/assets/assets.gen.dart';
import '../../../core/components/spaces.dart';
import '../../../core/constants/colors.dart';
class ProductCard extends StatelessWidget {
final Product data;
final VoidCallback onCartButton;
const ProductCard({
super.key,
required this.data,
required this.onCartButton,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
context.read<CheckoutBloc>().add(CheckoutEvent.addItem(data));
},
child: Container(
padding: const EdgeInsets.all(16.0),
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
side: const BorderSide(width: 1, color: AppColors.card),
borderRadius: BorderRadius.circular(16),
),
),
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SpaceHeight(8),
Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColors.disabled.withOpacity(0.4),
),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(40.0)),
child:
// Icon(
// Icons.fastfood,
// size: 40,
// color: AppColors.primary,
// ),
CachedNetworkImage(
imageUrl: data.image!.contains('http')
? data.image!
: '${Variables.baseUrl}/${data.image}',
width: 60,
height: 60,
fit: BoxFit.cover,
),
),
),
const Spacer(),
Text(
"${data.name}",
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
data.category?.name ?? '-',
style: const TextStyle(
color: AppColors.grey,
fontSize: 12,
),
),
),
Flexible(
child: Text(
data.price!.toIntegerFromText.currencyFormatRp,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 13,
),
),
),
],
),
const Spacer(),
],
),
BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const SizedBox(),
loaded: (products,
discountModel,
discount,
discountAmount,
tax,
serviceCharge,
totalQuantity,
totalPrice,
draftName,
orderType) {
return products.any((element) => element.product == data)
? products
.firstWhere(
(element) => element.product == data)
.quantity >
0
? Align(
alignment: Alignment.topRight,
child: Container(
width: 40,
height: 40,
padding: const EdgeInsets.all(6),
decoration: const BoxDecoration(
borderRadius:
BorderRadius.all(Radius.circular(9.0)),
color: AppColors.primary,
),
child: Center(
child: Text(
products
.firstWhere((element) =>
element.product == data)
.quantity
.toString(),
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold),
),
),
),
)
: Align(
alignment: Alignment.topRight,
child: Container(
width: 36,
height: 36,
padding: const EdgeInsets.all(6),
decoration: const BoxDecoration(
borderRadius:
BorderRadius.all(Radius.circular(9.0)),
color: AppColors.primary,
),
child: Assets.icons.shoppingBasket.svg(),
),
)
: Align(
alignment: Alignment.topRight,
child: Container(
width: 36,
height: 36,
padding: const EdgeInsets.all(6),
decoration: const BoxDecoration(
borderRadius:
BorderRadius.all(Radius.circular(9.0)),
color: AppColors.primary,
),
child: Assets.icons.shoppingBasket.svg(),
),
);
},
);
},
),
],
),
),
);
}
}
@@ -0,0 +1,205 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_esc_pos_network/flutter_esc_pos_network.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
import 'package:intl/intl.dart';
import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:enaklo_pos/data/dataoutputs/print_dataoutputs.dart';
import 'package:enaklo_pos/data/models/response/table_model.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
import '../../../core/assets/assets.gen.dart';
import '../../../core/components/buttons.dart';
import '../../../core/components/spaces.dart';
import '../../table/blocs/get_table/get_table_bloc.dart';
import '../bloc/checkout/checkout_bloc.dart';
import '../bloc/order/order_bloc.dart';
import '../models/order_type.dart';
import 'package:enaklo_pos/core/utils/printer_service.dart';
class SaveOrderDialog extends StatefulWidget {
const SaveOrderDialog({
super.key,
required this.data,
required this.totalQty,
required this.totalPrice,
required this.totalTax,
required this.totalDiscount,
required this.subTotal,
required this.normalPrice,
required this.table,
required this.draftName,
});
final List<ProductQuantity> data;
final int totalQty;
final int totalPrice;
final int totalTax;
final int totalDiscount;
final int subTotal;
final int normalPrice;
final TableModel table;
final String draftName;
@override
State<SaveOrderDialog> createState() => _SaveOrderDialogState();
}
class _SaveOrderDialogState extends State<SaveOrderDialog> {
// List<ProductQuantity> data = [];
// int totalQty = 0;
// int totalPrice = 0;
@override
Widget build(BuildContext context) {
return AlertDialog(
content: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(child: Assets.icons.success.svg()),
const SpaceHeight(16.0),
const Center(
child: Text(
'Order Berhasil Disimpan',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
),
),
),
const SpaceHeight(20.0),
Row(
children: [
Flexible(
child: Button.outlined(
onPressed: () {
context
.read<CheckoutBloc>()
.add(const CheckoutEvent.started());
context
.read<GetTableBloc>()
.add(const GetTableEvent.getTables());
context.popToRoot();
},
label: 'Kembali',
),
),
const SpaceWidth(8.0),
Flexible(
child: BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, state) {
final orderType = state.maybeWhen(
orElse: () => OrderType.dineIn,
loaded: (items, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) => orderType,
);
return Button.filled(
onPressed: () async {
final checkerPrinter = await ProductLocalDatasource
.instance
.getPrinterByCode('checker');
final kitchenPrinter = await ProductLocalDatasource
.instance
.getPrinterByCode('kitchen');
final barPrinter = await ProductLocalDatasource.instance
.getPrinterByCode('bar');
log("Checker printer: ${checkerPrinter?.toMap()}");
log("Kitchen printer: ${kitchenPrinter?.toMap()}");
log("Bar printer: ${barPrinter?.toMap()}");
// Checker printer
if (checkerPrinter != null) {
try {
final printValue = await PrintDataoutputs.instance
.printChecker(
widget.data,
widget.table.tableName,
widget.draftName,
'kasir',
checkerPrinter.paper.toIntegerFromText,
orderType.value);
await PrinterService().printWithPrinter(
checkerPrinter,
printValue,
context
);
} catch (e) {
log("Error printing checker: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error printing checker: $e')),
);
}
}
// Kitchen printer
if (kitchenPrinter != null) {
try {
final printValue = await PrintDataoutputs.instance.printKitchen(
widget.data,
widget.table.tableName,
widget.draftName,
'kasir',
kitchenPrinter.paper.toIntegerFromText,
orderType.value,
);
await PrinterService().printWithPrinter(
kitchenPrinter,
printValue,
context
);
} catch (e) {
log("Error printing kitchen order: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error printing kitchen order: $e')),
);
}
}
// Bar printer
if (barPrinter != null) {
try {
final printValue = await PrintDataoutputs.instance.printBar(
widget.data,
widget.table.tableName,
widget.draftName,
'kasir',
barPrinter.paper.toIntegerFromText,
orderType.value,
);
await PrinterService().printWithPrinter(
barPrinter,
printValue,
context
);
} catch (e) {
log("Error printing bar order: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error printing bar order: $e')),
);
}
}
},
label: 'Print Checker',
);
},
),
),
],
),
],
),
),
);
}
}
@@ -0,0 +1,331 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_esc_pos_network/flutter_esc_pos_network.dart';
import 'package:intl/intl.dart';
import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/data/dataoutputs/laman_print.dart';
import 'package:enaklo_pos/data/dataoutputs/print_dataoutputs.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
import 'package:enaklo_pos/presentation/home/models/product_quantity.dart';
import 'package:enaklo_pos/presentation/home/models/order_type.dart';
import 'package:enaklo_pos/core/utils/printer_service.dart';
import 'package:enaklo_pos/data/datasources/settings_local_datasource.dart';
import '../../../core/assets/assets.gen.dart';
import '../../../core/components/buttons.dart';
import '../../../core/components/spaces.dart';
import '../../table/blocs/get_table/get_table_bloc.dart';
import '../bloc/checkout/checkout_bloc.dart';
import '../bloc/order/order_bloc.dart';
class SuccessPaymentDialog extends StatefulWidget {
const SuccessPaymentDialog({
Key? key,
required this.data,
required this.totalQty,
required this.totalPrice,
required this.totalTax,
required this.totalDiscount,
required this.subTotal,
required this.normalPrice,
required this.totalService,
required this.draftName,
this.isTablePaymentPage = false,
}) : super(key: key);
final List<ProductQuantity> data;
final int totalQty;
final int totalPrice;
final int totalTax;
final int totalDiscount;
final int subTotal;
final int normalPrice;
final int totalService;
final String draftName;
final bool? isTablePaymentPage;
@override
State<SuccessPaymentDialog> createState() => _SuccessPaymentDialogState();
}
class _SuccessPaymentDialogState extends State<SuccessPaymentDialog> {
// List<ProductQuantity> data = [];
// int totalQty = 0;
// int totalPrice = 0;
@override
Widget build(BuildContext context) {
return AlertDialog(
content: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(child: Assets.icons.success.svg()),
const SpaceHeight(16.0),
const Center(
child: Text(
'Pembayaran telah sukses dilakukan',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
),
),
),
const SpaceHeight(20.0),
const Text('METODE BAYAR'),
const SpaceHeight(5.0),
BlocBuilder<OrderBloc, OrderState>(
builder: (context, state) {
final paymentMethod = state.maybeWhen(
orElse: () => 'Cash',
loaded: (model, orderId) => model.paymentMethod,
);
return Text(
paymentMethod,
style: const TextStyle(
fontWeight: FontWeight.w700,
),
);
},
),
const SpaceHeight(10.0),
const Divider(),
const SpaceHeight(8.0),
const Text('TOTAL TAGIHAN'),
const SpaceHeight(5.0),
BlocBuilder<OrderBloc, OrderState>(
builder: (context, state) {
final total = state.maybeWhen(
orElse: () => 0,
loaded: (model, orderId) => model.total,
);
return Text(
widget.totalPrice.currencyFormatRp,
style: const TextStyle(
fontWeight: FontWeight.w700,
),
);
},
),
const SpaceHeight(10.0),
const Divider(),
const SpaceHeight(8.0),
const Text('NOMINAL BAYAR'),
const SpaceHeight(5.0),
BlocBuilder<OrderBloc, OrderState>(
builder: (context, state) {
final paymentAmount = state.maybeWhen(
orElse: () => 0,
loaded: (model, orderId) => model.paymentAmount,
);
return Text(
paymentAmount.ceil().currencyFormatRp,
style: const TextStyle(
fontWeight: FontWeight.w700,
),
);
},
),
const Divider(),
const SpaceHeight(8.0),
const Text('KEMBALIAN'),
const SpaceHeight(5.0),
BlocBuilder<OrderBloc, OrderState>(
builder: (context, state) {
final paymentAmount = state.maybeWhen(
orElse: () => 0,
loaded: (model, orderId) => model.paymentAmount,
);
final total = state.maybeWhen(
orElse: () => 0,
loaded: (model, orderId) => model.total,
);
final diff = paymentAmount - total;
log("DIFF: $diff paymentAmount: $paymentAmount total: $total");
return Text(
diff.ceil().currencyFormatRp,
style: const TextStyle(
fontWeight: FontWeight.w700,
),
);
},
),
const SpaceHeight(10.0),
const Divider(),
const SpaceHeight(8.0),
const Text('WAKTU PEMBAYARAN'),
const SpaceHeight(5.0),
Text(
DateFormat('dd MMMM yyyy, HH:mm').format(DateTime.now()),
style: const TextStyle(
fontWeight: FontWeight.w700,
),
),
const SpaceHeight(20.0),
Row(
children: [
Flexible(
child: Button.outlined(
onPressed: () {
// For table payment page, just close the dialog
// The cleanup and navigation is handled by the payment page
if (widget.isTablePaymentPage == true) {
Navigator.of(context).pop(); // Close dialog only
} else {
// For regular payment flow, reset and go to root
context
.read<CheckoutBloc>()
.add(const CheckoutEvent.started());
context
.read<GetTableBloc>()
.add(const GetTableEvent.getTables());
context.popToRoot();
}
},
label: 'Kembali',
),
),
const SpaceWidth(8.0),
Flexible(
child: BlocBuilder<OrderBloc, OrderState>(
builder: (context, state) {
final paymentAmount = state.maybeWhen(
orElse: () => 0,
loaded: (model, orderId) => model.paymentAmount,
);
final kembalian = paymentAmount - widget.totalPrice;
return BlocBuilder<CheckoutBloc, CheckoutState>(
builder: (context, checkoutState) {
final orderType = checkoutState.maybeWhen(
orElse: () => OrderType.dineIn,
loaded: (items, discountModel, discount, discountAmount, tax, serviceCharge, totalQuantity, totalPrice, draftName, orderType) => orderType,
);
return Button.filled(
onPressed: () async {
final receiptPrinter = await ProductLocalDatasource
.instance
.getPrinterByCode('receipt');
final kitchenPrinter = await ProductLocalDatasource
.instance
.getPrinterByCode('kitchen');
final barPrinter = await ProductLocalDatasource.instance
.getPrinterByCode('bar');
// Receipt Printer
if (receiptPrinter != null) {
try {
final settingsLocalDatasource = SettingsLocalDatasource();
final taxModel = await settingsLocalDatasource.getTax();
final serviceChargeValue = await settingsLocalDatasource.getServiceCharge();
// Get the actual payment method from OrderBloc
final paymentMethod = state.maybeWhen(
orElse: () => 'Cash',
loaded: (model, orderId) => model.paymentMethod,
);
final printValue =
await PrintDataoutputs.instance.printOrderV3(
widget.data,
widget.totalQty,
widget.totalPrice,
paymentMethod,
paymentAmount,
kembalian,
widget.subTotal,
widget.totalDiscount,
widget.totalTax,
widget.totalService,
'kasir',
widget.draftName,
receiptPrinter.paper.toIntegerFromText,
taxPercentage: taxModel.value,
serviceChargePercentage: serviceChargeValue,
);
await PrinterService().printWithPrinter(
receiptPrinter,
printValue,
context
);
} catch (e) {
log("Error printing receipt: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error printing receipt: $e')),
);
}
}
// Kitchen Printer
if (kitchenPrinter != null && widget.isTablePaymentPage == false) {
try {
final printValue = await PrintDataoutputs.instance.printKitchen(
widget.data,
'',
widget.draftName,
'kasir',
kitchenPrinter.paper.toIntegerFromText,
orderType.value,
);
await PrinterService().printWithPrinter(
kitchenPrinter,
printValue,
context
);
} catch (e) {
log("Error printing kitchen order: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error printing kitchen order: $e')),
);
}
}
// Bar printer
if (barPrinter != null && widget.isTablePaymentPage == false) {
try {
final printValue = await PrintDataoutputs.instance.printBar(
widget.data,
'',
widget.draftName,
'kasir',
barPrinter.paper.toIntegerFromText,
orderType.value,
);
await PrinterService().printWithPrinter(
barPrinter,
printValue,
context
);
} catch (e) {
log("Error printing bar order: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error printing bar order: $e')),
);
}
}
},
label: 'Print',
);
},
);
},
),
),
],
),
],
),
),
);
}
}