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,131 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/presentation/setting/bloc/discount/discount_bloc.dart';
import '../../home/widgets/custom_tab_bar.dart';
import '../dialogs/form_discount_dialog.dart';
import '../models/discount_model.dart';
import '../widgets/add_data.dart';
import '../widgets/manage_discount_card.dart';
import '../widgets/settings_title.dart';
class DiscountPage extends StatefulWidget {
const DiscountPage({super.key});
@override
State<DiscountPage> createState() => _DiscountPageState();
}
class _DiscountPageState extends State<DiscountPage> {
// final List<DiscountModel> discounts = [
// DiscountModel(
// name: '20',
// code: 'BUKAPUASA',
// description: null,
// discount: 50,
// category: ProductCategory.food,
// ),
// ];
void onEditTap(DiscountModel item) {
showDialog(
context: context,
builder: (context) => FormDiscountDialog(data: item),
);
}
void onAddDataTap() {
showDialog(
context: context,
builder: (context) => const FormDiscountDialog(),
);
}
@override
void initState() {
context.read<DiscountBloc>().add(const DiscountEvent.getDiscounts());
super.initState();
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SettingsTitle('Kelola Diskon'),
const SizedBox(height: 24),
CustomTabBar(
tabTitles: const ['Semua'],
initialTabIndex: 0,
tabViews: [
// SEMUA TAB
SizedBox(
child: BlocBuilder<DiscountBloc, DiscountState>(
builder: (context, state) {
return state.maybeWhen(orElse: () {
return const Center(
child: CircularProgressIndicator(),
);
}, loaded: (discounts) {
return GridView.builder(
shrinkWrap: true,
itemCount: discounts.length + 1,
physics: const NeverScrollableScrollPhysics(),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 0.85,
crossAxisCount: 3,
crossAxisSpacing: 30.0,
mainAxisSpacing: 30.0,
),
itemBuilder: (context, index) {
if (index == 0) {
return AddData(
title: 'Tambah Diskon Baru',
onPressed: onAddDataTap,
);
}
final item = discounts[index - 1];
return ManageDiscountCard(
data: item,
onEditTap: (){},
);
},
);
});
// return GridView.builder(
// shrinkWrap: true,
// itemCount: discounts.length + 1,
// physics: const NeverScrollableScrollPhysics(),
// gridDelegate:
// const SliverGridDelegateWithFixedCrossAxisCount(
// childAspectRatio: 0.85,
// crossAxisCount: 3,
// crossAxisSpacing: 30.0,
// mainAxisSpacing: 30.0,
// ),
// itemBuilder: (context, index) {
// if (index == 0) {
// return AddData(
// title: 'Tambah Diskon Baru',
// onPressed: onAddDataTap,
// );
// }
// final item = discounts[index - 1];
// return ManageDiscountCard(
// data: item,
// onEditTap: () => onEditTap(item),
// );
// },
// );
},
),
),
],
),
],
),
);
}
}
@@ -0,0 +1,431 @@
import 'package:esc_pos_utils_plus/esc_pos_utils_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:enaklo_pos/core/components/components.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:print_bluetooth_thermal/print_bluetooth_thermal.dart';
import '../../../core/constants/colors.dart';
import '../widgets/menu_printer_button.dart';
import '../widgets/menu_printer_content.dart';
class ManagePrinterPage extends StatefulWidget {
const ManagePrinterPage({super.key});
@override
State<ManagePrinterPage> createState() => _ManagePrinterPageState();
}
class _ManagePrinterPageState extends State<ManagePrinterPage> {
int selectedIndex = 0;
int? selectedSize;
// final List<PrinterModel> datas = [
// PrinterModel(
// name: 'Galaxy A30',
// address: 12324567412,
// ),
// PrinterModel(
// name: 'Galaxy A30',
// address: 12324567412,
// ),
// PrinterModel(
// name: 'Galaxy A30',
// address: 12324567412,
// ),
// ];
String macName = '';
String _info = "";
String _msj = '';
bool connected = false;
List<BluetoothInfo> items = [];
final List<String> _options = [
"permission bluetooth granted",
"bluetooth enabled",
"connection status",
"update info"
];
final String _selectSize = "2";
final _txtText = TextEditingController(text: "Hello developer");
bool _progress = false;
String _msjprogress = "";
String optionprinttype = "58 mm";
List<String> options = ["58 mm", "80 mm"];
@override
void initState() {
super.initState();
initPlatformState();
loadData();
}
Future<void> initPlatformState() async {
String platformVersion;
int porcentbatery = 0;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
platformVersion = await PrintBluetoothThermal.platformVersion;
print("patformversion: $platformVersion");
porcentbatery = await PrintBluetoothThermal.batteryLevel;
} on PlatformException {
platformVersion = 'Failed to get platform version.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
final bool result = await PrintBluetoothThermal.bluetoothEnabled;
print("bluetooth enabled: $result");
if (result) {
_msj = "Bluetooth enabled, please search and connect";
} else {
_msj = "Bluetooth not enabled";
}
setState(() {
_info = "$platformVersion ($porcentbatery% battery)";
});
}
Future<void> getBluetoots() async {
setState(() {
_progress = true;
_msjprogress = "Wait";
items = [];
});
var status2 = await Permission.bluetoothScan.status;
if (status2.isDenied) {
await Permission.bluetoothScan.request();
}
var status = await Permission.bluetoothConnect.status;
if (status.isDenied) {
await Permission.bluetoothConnect.request();
}
final List<BluetoothInfo> listResult =
await PrintBluetoothThermal.pairedBluetooths;
setState(() {
_progress = false;
});
if (listResult.isEmpty) {
_msj =
"There are no bluetoohs linked, go to settings and link the printer";
} else {
_msj = "Touch an item in the list to connect";
}
setState(() {
items = listResult;
});
}
Future<void> connect(String mac) async {
setState(() {
_progress = true;
_msjprogress = "Connecting...";
connected = false;
});
final bool result =
await PrintBluetoothThermal.connect(macPrinterAddress: mac);
print("state conected $result");
if (result) connected = true;
setState(() {
_progress = false;
});
}
Future<void> disconnect() async {
final bool status = await PrintBluetoothThermal.disconnect;
setState(() {
connected = false;
});
print("status disconnect $status");
}
Future<void> printTest() async {
bool conexionStatus = await PrintBluetoothThermal.connectionStatus;
//print("connection status: $conexionStatus");
if (conexionStatus) {
List<int> ticket = await testTicket();
final result = await PrintBluetoothThermal.writeBytes(ticket);
print("print test result: $result");
} else {
//no conectado, reconecte
}
}
Future<List<int>> testTicket() async {
List<int> bytes = [];
// Using default profile
final profile = await CapabilityProfile.load();
final generator = Generator(
optionprinttype == "58 mm" ? PaperSize.mm58 : PaperSize.mm80, profile);
//bytes += generator.setGlobalFont(PosFontType.fontA);
bytes += generator.reset();
bytes +=
generator.text('Enaklo POS', styles: const PosStyles(bold: true));
bytes +=
generator.text('Reverse text', styles: const PosStyles(reverse: true));
bytes += generator.text('Underlined text',
styles: const PosStyles(underline: true), linesAfter: 1);
bytes += generator.text('Align left',
styles: const PosStyles(align: PosAlign.left));
bytes += generator.text('Align center',
styles: const PosStyles(align: PosAlign.center));
bytes += generator.text('Align right',
styles: const PosStyles(align: PosAlign.right), linesAfter: 1);
bytes += generator.text(
'FIC Batch 11',
styles: const PosStyles(
height: PosTextSize.size2,
width: PosTextSize.size2,
),
);
bytes += generator.feed(2);
//bytes += generator.cut();
return bytes;
}
Future<void> printWithoutPackage() async {
//impresion sin paquete solo de PrintBluetoothTermal
bool connectionStatus = await PrintBluetoothThermal.connectionStatus;
if (connectionStatus) {
String text = "${_txtText.text}\n";
bool result = await PrintBluetoothThermal.writeString(
printText: PrintTextSize(size: int.parse(_selectSize), text: text));
print("status print result: $result");
setState(() {
_msj = "printed status: $result";
});
} else {
//no conectado, reconecte
setState(() {
_msj = "no connected device";
});
print("no conectado");
}
}
Future<void> printString() async {
bool conexionStatus = await PrintBluetoothThermal.connectionStatus;
if (conexionStatus) {
String enter = '\n';
await PrintBluetoothThermal.writeBytes(enter.codeUnits);
//size of 1-5
String text = "Hello";
await PrintBluetoothThermal.writeString(
printText: PrintTextSize(size: 1, text: text));
await PrintBluetoothThermal.writeString(
printText: PrintTextSize(size: 2, text: "$text size 2"));
await PrintBluetoothThermal.writeString(
printText: PrintTextSize(size: 3, text: "$text size 3"));
} else {
//desconectado
print("desconectado bluetooth $conexionStatus");
}
}
Future<void> loadData() async {
final savedSize = await AuthLocalDataSource().getSizeReceipt();
if (savedSize.isNotEmpty) {
setState(() {
selectedSize = int.parse(savedSize);
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Kelola Printer'),
centerTitle: true,
),
bottomNavigationBar: SizedBox(
height: 60,
child: Button.filled(
onPressed: () async {
// Periksa apakah ukuran dan printer telah dipilih
if (selectedSize != null && macName.isNotEmpty) {
// Simpan ukuran dan alamat MAC printer
AuthLocalDataSource().saveSizeReceipt(selectedSize.toString());
await connect(macName);
// Tampilkan pesan berhasil
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Pengaturan berhasil disimpan')),
);
} else {
// Tampilkan pesan error jika belum dipilih
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text('Pilih ukuran dan printer terlebih dahulu')),
);
}
},
label: 'Simpan'),
),
body: ListView(
padding: const EdgeInsets.all(24.0),
children: [
Text(
"Pilih Ukuran",
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
color: AppColors.primary),
),
SpaceHeight(16),
Container(
width: context.deviceWidth / 2,
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
// Radio button untuk ukuran 58
Expanded(
child: Row(
children: [
Radio<int>(
value: 58,
groupValue: selectedSize,
onChanged: (value) {
setState(() {
selectedSize = value;
});
},
),
const Text('58 mm'),
],
),
),
// Radio button untuk ukuran 80
Expanded(
child: Row(
children: [
Radio<int>(
value: 80,
groupValue: selectedSize,
onChanged: (value) {
setState(() {
selectedSize = value;
});
},
),
const Text('80 mm'),
],
),
),
],
),
),
SpaceHeight(24),
Text(
"Pilih Printer",
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
color: AppColors.primary),
),
SpaceHeight(16),
Container(
width: context.deviceWidth / 2,
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: AppColors.card,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
MenuPrinterButton(
label: 'Search',
onPressed: () {
getBluetoots();
selectedIndex = 0;
setState(() {});
},
isActive: selectedIndex == 0,
),
],
),
),
const SpaceHeight(34.0),
_Body(
// selectedIndex: selectedIndex,
macName: macName,
datas: items,
clickHandler: (mac) async {
macName = mac;
setState(() {});
},
),
SpaceHeight(24),
],
),
);
}
}
class _Body extends StatelessWidget {
// final int selectedIndex;
final String macName;
final List<BluetoothInfo> datas;
//clickHandler
final Function(String) clickHandler;
const _Body({
required this.macName,
required this.datas,
required this.clickHandler,
});
@override
Widget build(BuildContext context) {
if (datas.isEmpty) {
return const Text('No data available');
} else {
return Container(
padding: const EdgeInsets.all(24.0),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: AppColors.card, width: 2),
borderRadius: BorderRadius.circular(6),
),
child: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: datas.length,
separatorBuilder: (context, index) => const SpaceHeight(16.0),
itemBuilder: (context, index) => InkWell(
onTap: () {
clickHandler(datas[index].macAdress);
},
child: MenuPrinterContent(
isSelected: macName == datas[index].macAdress,
data: datas[index],
),
),
),
);
}
// return const Placeholder();
}
}
@@ -0,0 +1,191 @@
import 'package:flutter/material.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
import 'package:enaklo_pos/presentation/setting/pages/discount_page.dart';
import 'package:enaklo_pos/presentation/setting/pages/manage_printer_page.dart';
import 'package:enaklo_pos/presentation/setting/pages/product_page.dart';
import 'package:enaklo_pos/presentation/setting/pages/server_key_page.dart';
import 'package:enaklo_pos/presentation/setting/pages/sync_data_page.dart';
import 'package:enaklo_pos/presentation/setting/pages/tax_page.dart';
import 'package:enaklo_pos/presentation/setting/widgets/bar_printer_page.dart';
import 'package:enaklo_pos/presentation/setting/widgets/checker_printer_page.dart';
import 'package:enaklo_pos/presentation/setting/widgets/kitchen_printer_page.dart';
import 'package:enaklo_pos/presentation/setting/widgets/receipt_printer_page.dart';
import '../../../core/assets/assets.gen.dart';
import '../../../core/components/spaces.dart';
import '../../../core/constants/colors.dart';
class PrinterConfigurationPage extends StatefulWidget {
const PrinterConfigurationPage({super.key});
@override
State<PrinterConfigurationPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<PrinterConfigurationPage> {
int currentIndex = 0;
String? role;
void indexValue(int index) {
currentIndex = index;
setState(() {});
}
@override
void initState() {
super.initState();
setRole();
}
void setRole() {
AuthLocalDataSource().getAuthData().then((value) {
setState(() {
role = value.user!.role;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
// LEFT CONTENT
Expanded(
flex: 2,
child: Align(
alignment: Alignment.topCenter,
child: ListView(
padding: const EdgeInsets.only(
left: 16,
top: 16,
),
children: [
const Text(
'Printer Configuration',
style: TextStyle(
color: AppColors.primary,
fontSize: 28,
fontWeight: FontWeight.w600,
),
),
const SpaceHeight(16.0),
ListTile(
// contentPadding: const EdgeInsets.all(12.0),
// leading: Assets.icons.kelolaDiskon.svg(),
title: const Text('Receipt Printer',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
)),
subtitle: const Text('To Print bill and receipt'),
textColor: AppColors.primary,
tileColor:
currentIndex == 0 ? AppColors.card : Colors.transparent,
onTap: () => indexValue(0),
),
ListTile(
// contentPadding: const EdgeInsets.all(12.0),
// leading: Assets.icons.kelolaPrinter.svg(),
title: const Text('Checker Printer',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
)),
subtitle: const Text('Print checker chit'),
textColor: AppColors.primary,
tileColor: currentIndex == 1
? AppColors.blueLight
: Colors.transparent,
onTap: () => indexValue(1),
),
ListTile(
// contentPadding: const EdgeInsets.all(12.0),
// leading: Assets.icons.kelolaPajak.svg(),
title: const Text('Kitchen Printer',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
)),
subtitle: const Text('To print food to kitchen'),
textColor: AppColors.primary,
tileColor: currentIndex == 2
? AppColors.blueLight
: Colors.transparent,
onTap: () => indexValue(2),
),
ListTile(
// contentPadding: const EdgeInsets.all(12.0),
// leading: Assets.icons.kelolaPajak.svg(),
title: const Text('Bar Printer',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
)),
subtitle: const Text('To print drink to bar'),
textColor: AppColors.primary,
tileColor: currentIndex == 3
? AppColors.blueLight
: Colors.transparent,
onTap: () => indexValue(3),
),
ListTile(
// contentPadding: const EdgeInsets.all(12.0),
// leading: Image.asset(Assets.images.manageQr.path,
// fit: BoxFit.contain),
title: const Text('Additional Printer',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
)),
subtitle:
const Text('To print additional kitchen/bar printer'),
textColor: AppColors.primary,
tileColor: currentIndex == 4
? AppColors.blueLight
: Colors.transparent,
onTap: () => indexValue(4),
),
],
),
),
),
// RIGHT CONTENT
Expanded(
flex: 4,
child: Align(
alignment: AlignmentDirectional.topStart,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: IndexedStack(
index: currentIndex,
children: [
ReceiptPrinterPage(),
CheckerPrinterPage(),
KitchenPrinterPage(),
BarPrinterPage(),
// role != null && role! != 'admin'
// ? SizedBox()
// : ProductPage(),
// DiscountPage(),
// ManagePrinterPage(),
// TaxPage(),
// SyncDataPage(),
// ProductPage(),
// ServerKeyPage()
// Text('tax'),
// ManageDiscount(),
// ManagePrinterPage(),
// ManageTax(),
],
),
),
),
),
],
),
);
}
}
@@ -0,0 +1,116 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/data/datasources/product_remote_datasource.dart';
import 'package:enaklo_pos/presentation/setting/bloc/get_products/get_products_bloc.dart';
import 'package:enaklo_pos/presentation/setting/bloc/update_product/update_product_bloc.dart';
import 'package:enaklo_pos/presentation/setting/bloc/sync_product/sync_product_bloc.dart';
import 'package:enaklo_pos/presentation/setting/bloc/add_product/add_product_bloc.dart';
import 'package:enaklo_pos/presentation/setting/dialogs/form_product_dialog.dart';
import 'package:enaklo_pos/presentation/setting/widgets/add_data.dart';
import 'package:enaklo_pos/presentation/setting/widgets/menu_product_item.dart';
import 'package:enaklo_pos/presentation/setting/widgets/settings_title.dart';
class ProductPage extends StatefulWidget {
const ProductPage({super.key});
@override
State<ProductPage> createState() => _ProductPageState();
}
class _ProductPageState extends State<ProductPage> {
@override
void initState() {
context.read<GetProductsBloc>().add(const GetProductsEvent.fetch());
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
padding: const EdgeInsets.all(24.0),
children: [
const SettingsTitle('Manage Products'),
const SizedBox(height: 24),
BlocBuilder<GetProductsBloc, GetProductsState>(
builder: (context, state) {
return state.maybeWhen(orElse: () {
return const Center(
child: CircularProgressIndicator(),
);
}, success: (products) {
return GridView.builder(
padding: EdgeInsets.zero,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 1,
crossAxisCount: 3,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
),
itemCount: products.length + 1,
shrinkWrap: true,
physics: const ScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
if (index == 0) {
return AddData(
title: 'Add New Product',
onPressed: () {
showDialog(
context: context,
builder: (context) => MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => AddProductBloc(ProductRemoteDatasource()),
),
BlocProvider.value(
value: context.read<SyncProductBloc>(),
),
BlocProvider.value(
value: context.read<GetProductsBloc>(),
),
],
child: const FormProductDialog(),
),
);
},
);
}
final item = products[index - 1];
return MenuProductItem(
data: item,
onTapEdit: () {
showDialog(
context: context,
builder: (context) => MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => UpdateProductBloc(ProductRemoteDatasource()),
),
BlocProvider.value(
value: context.read<SyncProductBloc>(),
),
BlocProvider.value(
value: context.read<GetProductsBloc>(),
),
],
child: FormProductDialog(product: item),
),
);
},
);
},
);
});
},
),
],
),
// floatingActionButton: FloatingActionButton(
// onPressed: () {
// },
// child: const Icon(Icons.add),
// ),
);
}
}
@@ -0,0 +1,75 @@
import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
class ServerKeyPage extends StatefulWidget {
final bool isTablet;
const ServerKeyPage({
super.key,
this.isTablet = false,
});
@override
State<ServerKeyPage> createState() => _ServerKeyPageState();
}
class _ServerKeyPageState extends State<ServerKeyPage> {
TextEditingController? serverKeyController;
String serverKey = '';
Future<void> getServerKey() async {
serverKey = await AuthLocalDataSource().getMitransServerKey();
}
@override
void initState() {
super.initState();
serverKeyController = TextEditingController();
getServerKey();
//delay 2 detik
Future.delayed(const Duration(seconds: 2), () {
serverKeyController!.text = serverKey;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Save Server Key'),
centerTitle: true,
),
//textfield untuk input server key
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(20.0),
child: TextField(
controller: serverKeyController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Server Key',
),
),
),
//button untuk save server key
ElevatedButton(
onPressed: () {
AuthLocalDataSource()
.saveMidtransServerKey(serverKeyController!.text);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Server Key saved'),
backgroundColor: AppColors.primary,
),
);
},
child: const Text('Save'),
),
],
),
);
}
}
@@ -0,0 +1,174 @@
import 'package:flutter/material.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
import 'package:enaklo_pos/presentation/sales/pages/sales_page.dart';
import 'package:enaklo_pos/presentation/setting/pages/discount_page.dart';
import 'package:enaklo_pos/presentation/setting/pages/manage_printer_page.dart';
import 'package:enaklo_pos/presentation/setting/pages/product_page.dart';
import 'package:enaklo_pos/presentation/setting/pages/server_key_page.dart';
import 'package:enaklo_pos/presentation/setting/pages/sync_data_page.dart';
import 'package:enaklo_pos/presentation/setting/pages/tax_page.dart';
import '../../../core/assets/assets.gen.dart';
import '../../../core/components/spaces.dart';
import '../../../core/constants/colors.dart';
class SettingsPage extends StatefulWidget {
const SettingsPage({super.key});
@override
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
int currentIndex = 0;
String? role;
void indexValue(int index) {
currentIndex = index;
setState(() {});
}
@override
void initState() {
super.initState();
setRole();
}
void setRole() {
AuthLocalDataSource().getAuthData().then((value) {
setState(() {
role = value.user!.role;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
// LEFT CONTENT
Expanded(
flex: 2,
child: Align(
alignment: Alignment.topCenter,
child: ListView(
padding: const EdgeInsets.all(16.0),
children: [
const Text(
'Settings',
style: TextStyle(
color: AppColors.primary,
fontSize: 28,
fontWeight: FontWeight.w600,
),
),
const SpaceHeight(16.0),
role != null && role! != 'admin'
? const SizedBox()
: ListTile(
contentPadding: const EdgeInsets.all(12.0),
leading: Assets.icons.kelolaProduk.svg(),
title: const Text('Manage Products'),
subtitle: const Text('Manage products in your store'),
textColor: AppColors.primary,
tileColor: currentIndex == 0
? AppColors.blueLight
: Colors.transparent,
onTap: () => indexValue(0),
),
ListTile(
contentPadding: const EdgeInsets.all(12.0),
leading: Assets.icons.kelolaDiskon.svg(),
title: const Text('Kelola Diskon'),
subtitle: const Text('Kelola Diskon Pelanggan'),
textColor: AppColors.primary,
tileColor: currentIndex == 1
? AppColors.blueLight
: Colors.transparent,
onTap: () => indexValue(1),
),
ListTile(
contentPadding: const EdgeInsets.all(12.0),
leading: Assets.icons.dashboard.svg(),
title: const Text('History Transaksi'),
subtitle: const Text('Lihat history transaksi'),
textColor: AppColors.primary,
tileColor: currentIndex == 2
? AppColors.blueLight
: Colors.transparent,
onTap: () => indexValue(2),
),
ListTile(
contentPadding: const EdgeInsets.all(12.0),
leading: Assets.icons.kelolaPajak.svg(),
title: const Text('Perhitungan Biaya'),
subtitle: const Text('Kelola biaya diluar biaya modal'),
textColor: AppColors.primary,
tileColor: currentIndex == 3
? AppColors.blueLight
: Colors.transparent,
onTap: () => indexValue(3),
),
ListTile(
contentPadding: const EdgeInsets.all(12.0),
leading: Assets.icons.kelolaPajak.svg(),
title: const Text('Sync Data'),
subtitle:
const Text('Sinkronisasi data dari dan ke server'),
textColor: AppColors.primary,
tileColor: currentIndex == 4
? AppColors.blueLight
: Colors.transparent,
onTap: () => indexValue(4),
),
ListTile(
contentPadding: const EdgeInsets.all(12.0),
leading: Image.asset(Assets.images.manageQr.path,
fit: BoxFit.contain),
title: const Text('QR Key Setting'),
subtitle: const Text('QR Key Configuration'),
textColor: AppColors.primary,
tileColor: currentIndex == 6
? AppColors.blueLight
: Colors.transparent,
onTap: () => indexValue(6),
),
],
),
),
),
// RIGHT CONTENT
Expanded(
flex: 4,
child: Align(
alignment: AlignmentDirectional.topStart,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: IndexedStack(
index: currentIndex,
children: [
role != null && role! != 'admin'
? SizedBox()
: ProductPage(),
DiscountPage(),
SalesPage(),
TaxPage(),
SyncDataPage(),
ProductPage(),
ServerKeyPage()
// Text('tax'),
// ManageDiscount(),
// ManagePrinterPage(),
// ManageTax(),
],
),
),
),
),
],
),
);
}
}
@@ -0,0 +1,118 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
import 'package:enaklo_pos/presentation/setting/bloc/sync_order/sync_order_bloc.dart';
import 'package:enaklo_pos/presentation/setting/bloc/sync_product/sync_product_bloc.dart';
class SyncDataPage extends StatefulWidget {
const SyncDataPage({super.key});
@override
State<SyncDataPage> createState() => _SyncDataPageState();
}
class _SyncDataPageState extends State<SyncDataPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sync Data'),
),
body: Column(
children: [
BlocConsumer<SyncProductBloc, SyncProductState>(
listener: (context, state) {
state.maybeWhen(
orElse: () {},
error: (message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
),
);
},
loaded: (productResponseModel) async {
await ProductLocalDatasource.instance.deleteAllProducts();
await ProductLocalDatasource.instance.insertProducts(
productResponseModel.data!,
);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Sync Product Success2'),
backgroundColor: Colors.green,
),
);
},
);
},
builder: (context, state) {
return state.maybeWhen(
orElse: () {
return ElevatedButton(
onPressed: () {
context
.read<SyncProductBloc>()
.add(const SyncProductEvent.syncProduct());
},
child: const Text('Sync Product'));
},
loading: () {
return const Center(
child: CircularProgressIndicator(),
);
},
);
},
),
BlocConsumer<SyncOrderBloc, SyncOrderState>(
listener: (context, state) {
state.maybeWhen(
orElse: () {},
error: (message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Colors.red,
),
);
},
loaded: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Sync Order Success'),
backgroundColor: Colors.green,
),
);
},
);
},
builder: (context, state) {
return state.maybeWhen(
orElse: () {
return ElevatedButton(
onPressed: () {
log("🔘 Sync Order button pressed");
log("🔘 SyncOrderBloc instance: ${context.read<SyncOrderBloc>()}");
context
.read<SyncOrderBloc>()
.add(const SyncOrderEvent.syncOrder());
log("🔘 SyncOrderEvent.syncOrder dispatched");
},
child: const Text('Sync Order'),
);
},
loading: () {
return const Center(
child: CircularProgressIndicator(),
);
},
);
},
)
],
),
);
}
}
@@ -0,0 +1,151 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../home/widgets/custom_tab_bar.dart';
import '../bloc/tax_settings/tax_settings_bloc.dart';
import '../dialogs/form_tax_dialog.dart';
import '../models/tax_model.dart';
import '../widgets/add_data.dart';
import '../widgets/manage_tax_card.dart';
import '../widgets/settings_title.dart';
class TaxPage extends StatefulWidget {
const TaxPage({super.key});
@override
State<TaxPage> createState() => _TaxPageState();
}
class _TaxPageState extends State<TaxPage> {
@override
void initState() {
super.initState();
context.read<TaxSettingsBloc>().add(const TaxSettingsEvent.loadSettings());
}
void onEditTap(TaxModel item, int serviceChargeValue, int taxValue) {
showDialog(
context: context,
builder: (context) => FormTaxDialog(
taxValue: taxValue,
serviceChargeValue: serviceChargeValue,
onSave: (newTaxValue, newServiceChargeValue) {
context.read<TaxSettingsBloc>().add(
TaxSettingsEvent.updateSettings(
taxValue: newTaxValue,
serviceChargeValue: newServiceChargeValue,
),
);
},
),
);
}
void onAddDataTap(int serviceChargeValue, int taxValue) {
showDialog(
context: context,
builder: (context) => FormTaxDialog(
taxValue: taxValue,
serviceChargeValue: serviceChargeValue,
onSave: (newTaxValue, newServiceChargeValue) {
context.read<TaxSettingsBloc>().add(
TaxSettingsEvent.updateSettings(
taxValue: newTaxValue,
serviceChargeValue: newServiceChargeValue,
),
);
},
),
);
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SettingsTitle('Perhitungan Biaya'),
const SizedBox(height: 24),
BlocBuilder<TaxSettingsBloc, TaxSettingsState>(
builder: (context, state) {
return state.when(
initial: () => const Center(child: CircularProgressIndicator()),
loading: () => const Center(child: CircularProgressIndicator()),
error: (message) => Center(child: Text('Error: $message')),
loaded: (taxModel, serviceChargeValue) {
final items = [
TaxModel(name: 'Biaya Layanan', type: TaxType.layanan, value: serviceChargeValue),
taxModel,
];
return CustomTabBar(
tabTitles: const ['Layanan', 'Pajak'],
initialTabIndex: 0,
tabViews: [
// LAYANAN TAB
SizedBox(
child: GridView.builder(
shrinkWrap: true,
itemCount: 2, // Add button + 1 service charge item
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 0.85,
crossAxisCount: 3,
crossAxisSpacing: 30.0,
mainAxisSpacing: 30.0,
),
itemBuilder: (context, index) {
if (index == 0) {
return AddData(
title: 'Edit Perhitungan',
onPressed: () => onAddDataTap(serviceChargeValue, taxModel.value),
);
}
final item = items.firstWhere((element) => element.type.isLayanan);
return ManageTaxCard(
data: item,
onEditTap: () => onEditTap(item, serviceChargeValue, taxModel.value),
);
},
),
),
// PAJAK TAB
SizedBox(
child: GridView.builder(
shrinkWrap: true,
itemCount: 2, // Add button + 1 tax item
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 0.85,
crossAxisCount: 3,
crossAxisSpacing: 30.0,
mainAxisSpacing: 30.0,
),
itemBuilder: (context, index) {
if (index == 0) {
return AddData(
title: 'Edit Perhitungan',
onPressed: () => onAddDataTap(serviceChargeValue, taxModel.value),
);
}
final item = items.firstWhere((element) => element.type.isPajak);
return ManageTaxCard(
data: item,
onEditTap: () => onEditTap(item, serviceChargeValue, taxModel.value),
);
},
),
),
],
);
},
);
},
),
],
),
);
}
}