Compare commits

..

No commits in common. "e825e5daed819ae9fa6d8dbefb09b663f7e212e2" and "1737186db7641011675fa563af8e7b11501cdb84" have entirely different histories.

34 changed files with 1154 additions and 1714 deletions

View File

@ -1,4 +1,4 @@
# ApskelPOS # EnakloPOS
A new Flutter project. A new Flutter project.

View File

@ -11,7 +11,7 @@
<!-- Izin khusus untuk akses foto (media images) di Android 33 ke atas --> <!-- Izin khusus untuk akses foto (media images) di Android 33 ke atas -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<application <application
android:label="ApskelPOS" android:label="EnakloPOS"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/launcher_icon"> android:icon="@mipmap/launcher_icon">
<activity <activity

View File

@ -5,7 +5,7 @@ import 'lib/core/utils/app_icon_generator.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
print('Generating ApskelPOS app icon...'); print('Generating EnakloPOS app icon...');
try { try {
final iconData = await AppIconGenerator.generateAppIcon(); final iconData = await AppIconGenerator.generateAppIcon();
@ -20,14 +20,14 @@ void main() async {
final iconFile = File('assets/logo/logo_app_icon.png'); final iconFile = File('assets/logo/logo_app_icon.png');
await iconFile.writeAsBytes(iconData); await iconFile.writeAsBytes(iconData);
print( print('✅ App icon generated successfully at: assets/logo/logo_app_icon.png');
'✅ App icon generated successfully at: assets/logo/logo_app_icon.png');
print('📱 The icon features:'); print('📱 The icon features:');
print(' - White background for visibility'); print(' - White background for visibility');
print(' - Blue circular background'); print(' - Blue circular background');
print(' - Gift box with "e" inside'); print(' - Gift box with "e" inside');
print(' - "ENAKLO" and "POS" text'); print(' - "ENAKLO" and "POS" text');
print(' - 1024x1024 resolution for high quality'); print(' - 1024x1024 resolution for high quality');
} catch (e) { } catch (e) {
print('❌ Error generating app icon: $e'); print('❌ Error generating app icon: $e');
} }

View File

@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string> <string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key> <key>CFBundleDisplayName</key>
<string>ApskelPOS</string> <string>EnakloPOS</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>

View File

@ -12,7 +12,6 @@ class CustomModalDialog extends StatelessWidget {
final double? maxWidth; final double? maxWidth;
final double? minHeight; final double? minHeight;
final double? maxHeight; final double? maxHeight;
final EdgeInsets? contentPadding;
const CustomModalDialog({ const CustomModalDialog({
super.key, super.key,
@ -24,7 +23,6 @@ class CustomModalDialog extends StatelessWidget {
this.maxWidth, this.maxWidth,
this.minHeight, this.minHeight,
this.maxHeight, this.maxHeight,
this.contentPadding,
}); });
@override @override
@ -102,10 +100,7 @@ class CustomModalDialog extends StatelessWidget {
], ],
), ),
), ),
Padding( child,
padding: contentPadding ?? EdgeInsets.zero,
child: child,
),
], ],
), ),
), ),

View File

@ -38,7 +38,7 @@ class ItemSalesInvoice {
return HelperPdfService.saveDocument( return HelperPdfService.saveDocument(
name: name:
'Apskel POS | Item Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf', 'Enaklo POS | Item Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf',
pdf: pdf); pdf: pdf);
} }
@ -48,7 +48,7 @@ class ItemSalesInvoice {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 1 * PdfPageFormat.cm), SizedBox(height: 1 * PdfPageFormat.cm),
Text('Apskel POS | Item Sales Report', Text('Enaklo POS | Item Sales Report',
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,

View File

@ -28,8 +28,7 @@ class RevenueInvoice {
// Load logo image // Load logo image
log("Loading logo image..."); log("Loading logo image...");
final ByteData dataImage = final ByteData dataImage = await rootBundle.load('assets/images/logo.png');
await rootBundle.load('assets/images/logo.png');
final Uint8List bytes = dataImage.buffer.asUint8List(); final Uint8List bytes = dataImage.buffer.asUint8List();
final image = pw.MemoryImage(bytes); final image = pw.MemoryImage(bytes);
log("Logo image loaded successfully, size: ${bytes.length} bytes"); log("Logo image loaded successfully, size: ${bytes.length} bytes");
@ -50,7 +49,7 @@ class RevenueInvoice {
log("Saving PDF document..."); log("Saving PDF document...");
return HelperPdfService.saveDocument( return HelperPdfService.saveDocument(
name: name:
'Apskel POS | Summary Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf', 'Enaklo POS | Summary Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf',
pdf: pdf, pdf: pdf,
); );
} catch (e) { } catch (e) {
@ -70,7 +69,7 @@ class RevenueInvoice {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 1 * PdfPageFormat.cm), SizedBox(height: 1 * PdfPageFormat.cm),
Text('Apskel POS | Summary Sales Report', Text('Enaklo POS | Summary Sales Report',
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -126,8 +125,7 @@ class RevenueInvoice {
buildText( buildText(
title: 'Discount', title: 'Discount',
titleStyle: TextStyle(fontWeight: FontWeight.normal), titleStyle: TextStyle(fontWeight: FontWeight.normal),
value: value: "- ${safeParseInt(summaryModel.totalDiscount).currencyFormatRp}",
"- ${safeParseInt(summaryModel.totalDiscount).currencyFormatRp}",
unite: true, unite: true,
textStyle: TextStyle( textStyle: TextStyle(
color: PdfColor.fromHex('#FF0000'), color: PdfColor.fromHex('#FF0000'),
@ -149,8 +147,7 @@ class RevenueInvoice {
titleStyle: TextStyle( titleStyle: TextStyle(
fontWeight: FontWeight.normal, fontWeight: FontWeight.normal,
), ),
value: value: safeParseInt(summaryModel.totalServiceCharge).currencyFormatRp,
safeParseInt(summaryModel.totalServiceCharge).currencyFormatRp,
unite: true, unite: true,
), ),
Divider(), Divider(),

View File

@ -38,7 +38,7 @@ class TransactionSalesInvoice {
return HelperPdfService.saveDocument( return HelperPdfService.saveDocument(
name: name:
'Apskel POS | Transaction Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf', 'Enaklo POS | Transaction Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf',
pdf: pdf); pdf: pdf);
} }
@ -48,7 +48,7 @@ class TransactionSalesInvoice {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 1 * PdfPageFormat.cm), SizedBox(height: 1 * PdfPageFormat.cm),
Text('Apskel POS | Transaction Sales Report', Text('Enaklo POS | Transaction Sales Report',
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,

View File

@ -35,7 +35,7 @@ class PrintDataoutputs {
final total = totalPrice + pajak; final total = totalPrice + pajak;
bytes += generator.reset(); bytes += generator.reset();
bytes += generator.text('Apskel POS', bytes += generator.text('Enaklo POS',
styles: const PosStyles( styles: const PosStyles(
bold: true, bold: true,
align: PosAlign.center, align: PosAlign.center,
@ -202,7 +202,7 @@ class PrintDataoutputs {
// bytes += generator.feed(3); // bytes += generator.feed(3);
// } // }
bytes += generator.text('Apskel POS', bytes += generator.text('Enaklo POS',
styles: const PosStyles( styles: const PosStyles(
bold: true, bold: true,
align: PosAlign.center, align: PosAlign.center,
@ -487,8 +487,8 @@ class PrintDataoutputs {
String namaKasir, String namaKasir,
String customerName, String customerName,
int paper, int paper,
{int taxPercentage = 11, {int taxPercentage = 11, int serviceChargePercentage = 5}
int serviceChargePercentage = 5}) async { ) async {
List<int> bytes = []; List<int> bytes = [];
final profile = await CapabilityProfile.load(); final profile = await CapabilityProfile.load();
@ -778,13 +778,8 @@ class PrintDataoutputs {
return bytes; return bytes;
} }
Future<List<int>> printChecker( Future<List<int>> printChecker(List<ProductQuantity> products,
List<ProductQuantity> products, String tableName, String draftName, String cashierName, int paper, String orderType) async {
String tableName,
String draftName,
String cashierName,
int paper,
String orderType) async {
List<int> bytes = []; List<int> bytes = [];
final profile = await CapabilityProfile.load(); final profile = await CapabilityProfile.load();
@ -913,13 +908,8 @@ class PrintDataoutputs {
return bytes; return bytes;
} }
Future<List<int>> printKitchen( Future<List<int>> printKitchen(List<ProductQuantity> products,
List<ProductQuantity> products, String tableNumber, String draftName, String cashierName, int paper, String orderType) async {
String tableNumber,
String draftName,
String cashierName,
int paper,
String orderType) async {
List<int> bytes = []; List<int> bytes = [];
final profile = await CapabilityProfile.load(); final profile = await CapabilityProfile.load();

View File

@ -49,7 +49,7 @@ class _LoginPageState extends State<LoginPage> {
const SpaceHeight(24.0), const SpaceHeight(24.0),
const Center( const Center(
child: Text( child: Text(
'Apskel POS', 'Enaklo POS',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,

View File

@ -34,29 +34,28 @@ class _ReportPageState extends State<ReportPage> {
DateTime fromDate = DateTime.now().subtract(const Duration(days: 30)); DateTime fromDate = DateTime.now().subtract(const Duration(days: 30));
DateTime toDate = DateTime.now(); DateTime toDate = DateTime.now();
@override
void initState() {
super.initState();
context.read<TransactionReportBloc>().add(
TransactionReportEvent.getReport(
startDate: DateFormatter.formatDateTime(fromDate),
endDate: DateFormatter.formatDateTime(toDate)),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String searchDateFormatted = String searchDateFormatted =
'${fromDate.toFormattedDate2()} to ${toDate.toFormattedDate2()}'; '${fromDate.toFormattedDate2()} to ${toDate.toFormattedDate2()}';
return Scaffold( return Scaffold(
backgroundColor: AppColors.background, body: Row(
body: Column( children: [
// LEFT CONTENT
Expanded(
flex: 2,
child: Align(
alignment: Alignment.topLeft,
child: SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ReportTitle( const ReportTitle(),
actionWidget: [ Row(
SizedBox( mainAxisAlignment: MainAxisAlignment.spaceBetween,
width: 300, children: [
Flexible(
child: CustomDatePicker( child: CustomDatePicker(
prefix: const Text('From: '), prefix: const Text('From: '),
initialDate: fromDate, initialDate: fromDate,
@ -68,8 +67,7 @@ class _ReportPageState extends State<ReportPage> {
), ),
), ),
const SpaceWidth(24.0), const SpaceWidth(24.0),
SizedBox( Flexible(
width: 300,
child: CustomDatePicker( child: CustomDatePicker(
prefix: const Text('To: '), prefix: const Text('To: '),
initialDate: toDate, initialDate: toDate,
@ -97,34 +95,20 @@ class _ReportPageState extends State<ReportPage> {
), ),
], ],
), ),
Expanded( Padding(
child: Row( padding: const EdgeInsets.all(15.0),
children: [ child: Wrap(
// LEFT CONTENT
Expanded(
flex: 2,
child: Material(
color: AppColors.white,
child: Align(
alignment: Alignment.topLeft,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ReportMenu( ReportMenu(
label: 'Laporan Transaksi', label: 'Transaction Report',
subtitle:
'Menampilkan riwayat lengkap semua transaksi yang telah dilakukan.',
icon: Icons.receipt_long_outlined,
onPressed: () { onPressed: () {
selectedMenu = 0; selectedMenu = 0;
title = 'Laporan Transaksi'; title = 'Transaction Report';
setState(() {}); setState(() {});
//enddate is 1 month before the current date //enddate is 1 month before the current date
context.read<TransactionReportBloc>().add( context.read<TransactionReportBloc>().add(
TransactionReportEvent.getReport( TransactionReportEvent.getReport(
startDate: startDate: DateFormatter.formatDateTime(
DateFormatter.formatDateTime(
fromDate), fromDate),
endDate: DateFormatter.formatDateTime( endDate: DateFormatter.formatDateTime(
toDate)), toDate)),
@ -133,18 +117,14 @@ class _ReportPageState extends State<ReportPage> {
isActive: selectedMenu == 0, isActive: selectedMenu == 0,
), ),
ReportMenu( ReportMenu(
label: 'Laporan Penjualan Item', label: 'Item Sales Report',
subtitle:
'Laporan penjualan berdasarkan masing-masing item atau produk.',
icon: Icons.inventory_2_outlined,
onPressed: () { onPressed: () {
selectedMenu = 1; selectedMenu = 1;
title = 'Laporan Penjualan Item'; title = 'Item Sales Report';
setState(() {}); setState(() {});
context.read<ItemSalesReportBloc>().add( context.read<ItemSalesReportBloc>().add(
ItemSalesReportEvent.getItemSales( ItemSalesReportEvent.getItemSales(
startDate: startDate: DateFormatter.formatDateTime(
DateFormatter.formatDateTime(
fromDate), fromDate),
endDate: DateFormatter.formatDateTime( endDate: DateFormatter.formatDateTime(
toDate)), toDate)),
@ -153,36 +133,28 @@ class _ReportPageState extends State<ReportPage> {
isActive: selectedMenu == 1, isActive: selectedMenu == 1,
), ),
ReportMenu( ReportMenu(
label: 'Chart Penjualan Produk', label: 'Product Sales Chart',
subtitle:
'Grafik visual penjualan produk untuk analisa performa penjualan.',
icon: Icons.bar_chart_outlined,
onPressed: () { onPressed: () {
selectedMenu = 2; selectedMenu = 2;
title = 'Chart Penjualan Produk'; title = 'Product Sales Chart';
setState(() {}); setState(() {});
context.read<ProductSalesBloc>().add( context.read<ProductSalesBloc>().add(
ProductSalesEvent.getProductSales( ProductSalesEvent.getProductSales(
DateFormatter.formatDateTime( DateFormatter.formatDateTime(fromDate),
fromDate),
DateFormatter.formatDateTime(toDate)), DateFormatter.formatDateTime(toDate)),
); );
}, },
isActive: selectedMenu == 2, isActive: selectedMenu == 2,
), ),
ReportMenu( ReportMenu(
label: 'Ringkasan Laporan Penjualan', label: 'Summary Sales Report',
subtitle:
'Ringkasan total penjualan dalam periode tertentu.',
icon: Icons.insert_drive_file_outlined,
onPressed: () { onPressed: () {
selectedMenu = 3; selectedMenu = 3;
title = 'Ringkasan Laporan Penjualan'; title = 'Summary Sales Report';
setState(() {}); setState(() {});
context.read<SummaryBloc>().add( context.read<SummaryBloc>().add(
SummaryEvent.getSummary( SummaryEvent.getSummary(
DateFormatter.formatDateTime( DateFormatter.formatDateTime(fromDate),
fromDate),
DateFormatter.formatDateTime(toDate)), DateFormatter.formatDateTime(toDate)),
); );
@ -191,23 +163,15 @@ class _ReportPageState extends State<ReportPage> {
isActive: selectedMenu == 3, isActive: selectedMenu == 3,
), ),
ReportMenu( ReportMenu(
label: 'Laporan Metode Pembayaran', label: 'Payment Method Report',
subtitle:
'Laporan metode pembayaran yang digunakan.',
icon: Icons.payment_outlined,
onPressed: () { onPressed: () {
selectedMenu = 4; selectedMenu = 4;
title = 'Laporan Metode Pembayaran'; title = 'Payment Method Report';
setState(() {}); setState(() {});
context.read<PaymentMethodReportBloc>().add( context.read<PaymentMethodReportBloc>().add(
PaymentMethodReportEvent PaymentMethodReportEvent.getPaymentMethodReport(
.getPaymentMethodReport( startDate: DateFormatter.formatDateTime(fromDate),
startDate: endDate: DateFormatter.formatDateTime(toDate)),
DateFormatter.formatDateTime(
fromDate),
endDate:
DateFormatter.formatDateTime(
toDate)),
); );
}, },
isActive: selectedMenu == 4, isActive: selectedMenu == 4,
@ -215,16 +179,17 @@ class _ReportPageState extends State<ReportPage> {
], ],
), ),
), ),
],
),
), ),
), ),
), ),
// RIGHT CONTENT // RIGHT CONTENT
Expanded( Expanded(
flex: 4, flex: 2,
child: selectedMenu == 0 child: selectedMenu == 0
? BlocBuilder<TransactionReportBloc, ? BlocBuilder<TransactionReportBloc, TransactionReportState>(
TransactionReportState>(
builder: (context, state) { builder: (context, state) {
return state.maybeWhen( return state.maybeWhen(
orElse: () => const Center( orElse: () => const Center(
@ -245,8 +210,7 @@ class _ReportPageState extends State<ReportPage> {
}, },
) )
: selectedMenu == 1 : selectedMenu == 1
? BlocBuilder<ItemSalesReportBloc, ? BlocBuilder<ItemSalesReportBloc, ItemSalesReportState>(
ItemSalesReportState>(
builder: (context, state) { builder: (context, state) {
return state.maybeWhen( return state.maybeWhen(
orElse: () => const Center( orElse: () => const Center(
@ -259,18 +223,15 @@ class _ReportPageState extends State<ReportPage> {
return ItemSalesReportWidget( return ItemSalesReportWidget(
itemSales: itemSales, itemSales: itemSales,
title: title, title: title,
searchDateFormatted: searchDateFormatted: searchDateFormatted,
searchDateFormatted, headerWidgets: _getItemSalesPageWidget(),
headerWidgets:
_getItemSalesPageWidget(),
); );
}, },
); );
}, },
) )
: selectedMenu == 2 : selectedMenu == 2
? BlocBuilder<ProductSalesBloc, ? BlocBuilder<ProductSalesBloc, ProductSalesState>(
ProductSalesState>(
builder: (context, state) { builder: (context, state) {
return state.maybeWhen( return state.maybeWhen(
orElse: () => const Center( orElse: () => const Center(
@ -282,8 +243,7 @@ class _ReportPageState extends State<ReportPage> {
success: (productSales) { success: (productSales) {
return ProductSalesChartWidgets( return ProductSalesChartWidgets(
title: title, title: title,
searchDateFormatted: searchDateFormatted: searchDateFormatted,
searchDateFormatted,
productSales: productSales, productSales: productSales,
); );
}, },
@ -295,8 +255,7 @@ class _ReportPageState extends State<ReportPage> {
builder: (context, state) { builder: (context, state) {
return state.maybeWhen( return state.maybeWhen(
orElse: () => const Center( orElse: () => const Center(
child: child: CircularProgressIndicator(),
CircularProgressIndicator(),
), ),
error: (message) { error: (message) {
return Text(message); return Text(message);
@ -305,34 +264,28 @@ class _ReportPageState extends State<ReportPage> {
return SummaryReportWidget( return SummaryReportWidget(
summary: summary, summary: summary,
title: title, title: title,
searchDateFormatted: searchDateFormatted: searchDateFormatted,
searchDateFormatted,
); );
}, },
); );
}, },
) )
: selectedMenu == 4 : selectedMenu == 4
? BlocBuilder<PaymentMethodReportBloc, ? BlocBuilder<PaymentMethodReportBloc, PaymentMethodReportState>(
PaymentMethodReportState>(
builder: (context, state) { builder: (context, state) {
return state.maybeWhen( return state.maybeWhen(
orElse: () => const Center( orElse: () => const Center(
child: child: CircularProgressIndicator(),
CircularProgressIndicator(),
), ),
error: (message) { error: (message) {
return Text(message); return Text(message);
}, },
loaded: (paymentMethodData) { loaded: (paymentMethodData) {
return PaymentMethodReportWidget( return PaymentMethodReportWidget(
paymentMethodData: paymentMethodData: paymentMethodData,
paymentMethodData,
title: title, title: title,
searchDateFormatted: searchDateFormatted: searchDateFormatted,
searchDateFormatted, headerWidgets: _getPaymentMethodPageWidget(),
headerWidgets:
_getPaymentMethodPageWidget(),
); );
}, },
); );
@ -341,9 +294,6 @@ class _ReportPageState extends State<ReportPage> {
: const SizedBox.shrink()), : const SizedBox.shrink()),
], ],
), ),
),
],
),
); );
} }
@ -364,11 +314,11 @@ class _ReportPageState extends State<ReportPage> {
List<Widget> _getItemSalesPageWidget() { List<Widget> _getItemSalesPageWidget() {
return [ return [
_getTitleItemWidget('ID', 80), _getTitleItemWidget('ID', 80),
_getTitleItemWidget('Order', 100), _getTitleItemWidget('Order', 60),
_getTitleItemWidget('Product', 200), _getTitleItemWidget('Product', 160),
_getTitleItemWidget('Qty', 60), _getTitleItemWidget('Qty', 60),
_getTitleItemWidget('Price', 150), _getTitleItemWidget('Price', 140),
_getTitleItemWidget('Total Price', 160), _getTitleItemWidget('Total Price', 140),
]; ];
} }

View File

@ -4,12 +4,12 @@ import 'package:enaklo_pos/core/components/spaces.dart';
import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart'; import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:enaklo_pos/core/utils/helper_pdf_service.dart'; import 'package:enaklo_pos/core/utils/helper_pdf_service.dart';
import 'package:enaklo_pos/presentation/report/widgets/report_page_title.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/utils/item_sales_invoice.dart'; import 'package:enaklo_pos/core/utils/item_sales_invoice.dart';
import 'package:enaklo_pos/core/utils/permession_handler.dart'; import 'package:enaklo_pos/core/utils/permession_handler.dart';
import 'package:enaklo_pos/data/models/response/item_sales_response_model.dart'; import 'package:enaklo_pos/data/models/response/item_sales_response_model.dart';
import 'package:horizontal_data_table/horizontal_data_table.dart'; import 'package:horizontal_data_table/horizontal_data_table.dart';
import 'package:permission_handler/permission_handler.dart';
class ItemSalesReportWidget extends StatelessWidget { class ItemSalesReportWidget extends StatelessWidget {
final String title; final String title;
@ -26,12 +26,32 @@ class ItemSalesReportWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Card(
color: const Color.fromARGB(255, 255, 255, 255),
child: Column(
children: [ children: [
ReportPageTitle( const SpaceHeight(24.0),
title: title, Center(
searchDateFormatted: searchDateFormatted, child: Text(
onExport: () async { title,
style:
const TextStyle(fontWeight: FontWeight.w800, fontSize: 16.0),
),
),
const SizedBox(
height: 8.0,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
searchDateFormatted,
style: const TextStyle(fontSize: 16.0),
),
GestureDetector(
onTap: () async {
try { try {
final status = await PermessionHelper().checkPermission(); final status = await PermessionHelper().checkPermission();
if (status) { if (status) {
@ -57,6 +77,25 @@ class ItemSalesReportWidget extends StatelessWidget {
); );
} }
}, },
child: const Row(
children: [
Text(
"PDF",
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.bold,
color: AppColors.primary,
),
),
Icon(
Icons.download_outlined,
color: AppColors.primary,
)
],
),
),
],
),
), ),
const SpaceHeight(16.0), const SpaceHeight(16.0),
Expanded( Expanded(
@ -66,7 +105,7 @@ class ItemSalesReportWidget extends StatelessWidget {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
child: HorizontalDataTable( child: HorizontalDataTable(
leftHandSideColumnWidth: 80, leftHandSideColumnWidth: 80,
rightHandSideColumnWidth: 670, rightHandSideColumnWidth: 560,
isFixedHeader: true, isFixedHeader: true,
headerWidgets: headerWidgets, headerWidgets: headerWidgets,
@ -77,35 +116,37 @@ class ItemSalesReportWidget extends StatelessWidget {
width: 80, width: 80,
height: 52, height: 52,
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Center(child: Text(itemSales[index].id.toString())), child:
Center(child: Text(itemSales[index].id.toString())),
); );
}, },
rightSideItemBuilder: (context, index) { rightSideItemBuilder: (context, index) {
return Row( return Row(
children: <Widget>[ children: <Widget>[
Container( Container(
width: 100, width: 60,
height: 52, height: 52,
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Center( child: Center(
child: Text(itemSales[index].orderId.toString())), child: Text(itemSales[index].orderId.toString())),
), ),
Container( Container(
width: 200, width: 160,
height: 52, height: 52,
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: child: Center(
Center(child: Text(itemSales[index].productName!)), child: Text(itemSales[index].productName!)),
), ),
Container( Container(
width: 60, width: 60,
height: 52, height: 52,
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Center( child: Center(
child: Text(itemSales[index].quantity.toString())), child:
Text(itemSales[index].quantity.toString())),
), ),
Container( Container(
width: 150, width: 140,
height: 52, height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
@ -115,13 +156,14 @@ class ItemSalesReportWidget extends StatelessWidget {
)), )),
), ),
Container( Container(
width: 160, width: 140,
height: 52, height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0), padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Center( child: Center(
child: Text( child: Text(
(itemSales[index].price! * itemSales[index].quantity!) (itemSales[index].price! *
itemSales[index].quantity!)
.currencyFormatRp, .currencyFormatRp,
)), )),
), ),
@ -143,6 +185,7 @@ class ItemSalesReportWidget extends StatelessWidget {
), ),
), ),
], ],
),
); );
} }
} }

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:enaklo_pos/core/extensions/string_ext.dart'; import 'package:enaklo_pos/core/extensions/string_ext.dart';
import 'package:enaklo_pos/data/models/response/payment_method_response_model.dart'; import 'package:enaklo_pos/data/models/response/payment_method_response_model.dart';
import 'package:enaklo_pos/presentation/report/widgets/report_title.dart';
import '../../../core/components/spaces.dart'; import '../../../core/components/spaces.dart';

View File

@ -1,7 +1,6 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first // ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:enaklo_pos/core/components/spaces.dart'; import 'package:enaklo_pos/core/components/spaces.dart';
import 'package:enaklo_pos/presentation/report/widgets/report_page_title.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pie_chart/pie_chart.dart'; import 'package:pie_chart/pie_chart.dart';
@ -68,26 +67,27 @@ class _ProductSalesChartWidgetsState extends State<ProductSalesChartWidgets> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Card(
children: [ color: const Color.fromARGB(255, 255, 255, 255),
ReportPageTitle( child: Padding(
title: widget.title, padding: EdgeInsets.symmetric(horizontal: 8, vertical: 16),
searchDateFormatted: widget.searchDateFormatted,
onExport: () async {},
isExport: false, // Set to false if export is not needed
),
const SpaceHeight(16.0),
Expanded(
child: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.all(16.0),
margin: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column( child: Column(
children: [ children: [
const SpaceHeight(24.0),
Center(
child: Text(
widget.title,
style: const TextStyle(
fontWeight: FontWeight.w800, fontSize: 16.0),
),
),
Center(
child: Text(
widget.searchDateFormatted,
style: const TextStyle(fontSize: 16.0),
),
),
const SpaceHeight(16.0),
PieChart( PieChart(
dataMap: dataMap2, dataMap: dataMap2,
animationDuration: Duration(milliseconds: 800), animationDuration: Duration(milliseconds: 800),
@ -116,13 +116,10 @@ class _ProductSalesChartWidgetsState extends State<ProductSalesChartWidgets> {
), ),
// gradientList: ---To add gradient colors--- // gradientList: ---To add gradient colors---
// emptyColorGradient: ---Empty Color gradient--- // emptyColorGradient: ---Empty Color gradient---
),
],
),
),
),
) )
], ],
),
),
); );
} }
} }

View File

@ -1,12 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/assets/assets.gen.dart';
import '../../../core/components/spaces.dart'; import '../../../core/components/spaces.dart';
import '../../../core/constants/colors.dart'; import '../../../core/constants/colors.dart';
class ReportMenu extends StatelessWidget { class ReportMenu extends StatelessWidget {
final String label; final String label;
final String subtitle;
final IconData icon;
final VoidCallback onPressed; final VoidCallback onPressed;
final bool isActive; final bool isActive;
@ -15,8 +16,6 @@ class ReportMenu extends StatelessWidget {
required this.label, required this.label,
required this.onPressed, required this.onPressed,
required this.isActive, required this.isActive,
required this.subtitle,
required this.icon,
}); });
@override @override
@ -24,43 +23,38 @@ class ReportMenu extends StatelessWidget {
return GestureDetector( return GestureDetector(
onTap: onPressed, onTap: onPressed,
child: Container( child: Container(
padding: const EdgeInsets.all(12.0), margin: const EdgeInsets.all(5.0),
width: 180.0,
height: 160.0,
alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( color:
right: BorderSide( isActive ? AppColors.primary.withOpacity(0.13) : AppColors.white,
color: isActive ? AppColors.primary : Colors.transparent, borderRadius: BorderRadius.circular(18.0),
width: 4.0, border: Border.all(
color: isActive ? AppColors.primary : AppColors.stroke,
), ),
), ),
),
child: Row(
children: [
Icon(
icon,
size: 24.0,
color: isActive ? AppColors.primary : AppColors.grey,
),
const SpaceWidth(12),
Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
Assets.icons.report.svg(
colorFilter: isActive
? const ColorFilter.mode(
AppColors.primary,
BlendMode.srcIn,
)
: null,
),
const SpaceHeight(28.0),
Text( Text(
label, label,
style: const TextStyle( style: TextStyle(
fontSize: 16.0, color: isActive ? AppColors.primary : AppColors.black,
fontWeight: FontWeight.bold, fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 4.0),
Text(
subtitle,
style:
const TextStyle(fontSize: 14.0, color: AppColors.grey),
),
],
), ),
), ),
const SpaceHeight(24.0),
], ],
), ),
), ),

View File

@ -1,65 +0,0 @@
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:flutter/material.dart';
class ReportPageTitle extends StatelessWidget {
final String title;
final String searchDateFormatted;
final Function() onExport;
final bool isExport;
const ReportPageTitle(
{super.key,
required this.title,
required this.searchDateFormatted,
required this.onExport,
this.isExport = true});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(color: AppColors.white),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontWeight: FontWeight.w800, fontSize: 16.0),
),
const SizedBox(
height: 8.0,
),
Text(
searchDateFormatted,
style: const TextStyle(fontSize: 16.0),
),
],
),
if (isExport)
GestureDetector(
onTap: onExport,
child: const Row(
children: [
Text(
"PDF",
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.bold,
color: AppColors.primary,
),
),
Icon(
Icons.download_outlined,
color: AppColors.primary,
)
],
),
),
],
),
);
}
}

View File

@ -1,60 +1,38 @@
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/components/components.dart';
import 'package:enaklo_pos/core/extensions/date_time_ext.dart'; import 'package:enaklo_pos/core/extensions/date_time_ext.dart';
import '../../../core/constants/colors.dart'; import '../../../core/constants/colors.dart';
class ReportTitle extends StatelessWidget { class ReportTitle extends StatelessWidget {
final List<Widget>? actionWidget; const ReportTitle({super.key});
const ReportTitle({super.key, this.actionWidget});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Column(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 10.0,
),
width: double.infinity,
height: context.deviceHeight * 0.1,
decoration: const BoxDecoration(
color: AppColors.white,
border: Border(
bottom: BorderSide(
color: AppColors.background,
width: 1.0,
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const Text( const Text(
'Laporan', 'Report',
style: TextStyle( style: TextStyle(
color: AppColors.black, color: AppColors.primary,
fontSize: 20, fontSize: 28,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
), ),
const SpaceHeight(4.0),
Text( Text(
DateTime.now().toFormattedDate2(), DateTime.now().toFormattedDate(),
style: TextStyle( style: const TextStyle(
color: AppColors.grey, color: AppColors.subtitle,
fontSize: 14, fontSize: 16,
), ),
), ),
const SpaceHeight(20.0),
const Divider(),
], ],
),
if (actionWidget != null)
Row(
children: actionWidget!,
),
],
),
); );
} }
} }

View File

@ -9,6 +9,7 @@ import 'package:enaklo_pos/core/utils/helper_pdf_service.dart';
import 'package:enaklo_pos/core/utils/permession_handler.dart'; import 'package:enaklo_pos/core/utils/permession_handler.dart';
import 'package:enaklo_pos/data/models/response/summary_response_model.dart'; import 'package:enaklo_pos/data/models/response/summary_response_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import '../../../core/utils/revenue_invoice.dart'; import '../../../core/utils/revenue_invoice.dart';
@ -54,8 +55,7 @@ class SummaryReportWidget extends StatelessWidget {
log("PDF button clicked for summary report"); log("PDF button clicked for summary report");
try { try {
log("Checking permissions..."); log("Checking permissions...");
final status = final status = await PermessionHelper().checkPermission();
await PermessionHelper().checkPermission();
log("Permission status: $status"); log("Permission status: $status");
if (status) { if (status) {
@ -75,8 +75,7 @@ class SummaryReportWidget extends StatelessWidget {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text( content: Text('PDF saved successfully: ${pdfFile.path}'),
'PDF saved successfully: ${pdfFile.path}'),
backgroundColor: Colors.green, backgroundColor: Colors.green,
), ),
); );
@ -84,8 +83,7 @@ class SummaryReportWidget extends StatelessWidget {
log("Permission denied"); log("Permission denied");
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
content: Text( content: Text('Storage permission is required to save PDF'),
'Storage permission is required to save PDF'),
backgroundColor: Colors.red, backgroundColor: Colors.red,
), ),
); );

View File

@ -5,12 +5,12 @@ import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/core/extensions/date_time_ext.dart'; import 'package:enaklo_pos/core/extensions/date_time_ext.dart';
import 'package:enaklo_pos/core/extensions/int_ext.dart'; import 'package:enaklo_pos/core/extensions/int_ext.dart';
import 'package:enaklo_pos/core/utils/helper_pdf_service.dart'; import 'package:enaklo_pos/core/utils/helper_pdf_service.dart';
import 'package:enaklo_pos/presentation/report/widgets/report_page_title.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/utils/permession_handler.dart'; import 'package:enaklo_pos/core/utils/permession_handler.dart';
import 'package:enaklo_pos/core/utils/transaction_sales_invoice.dart'; import 'package:enaklo_pos/core/utils/transaction_sales_invoice.dart';
import 'package:enaklo_pos/data/models/response/order_remote_datasource.dart'; import 'package:enaklo_pos/data/models/response/order_remote_datasource.dart';
import 'package:horizontal_data_table/horizontal_data_table.dart'; import 'package:horizontal_data_table/horizontal_data_table.dart';
import 'package:permission_handler/permission_handler.dart';
class TransactionReportWidget extends StatelessWidget { class TransactionReportWidget extends StatelessWidget {
final String title; final String title;
@ -27,12 +27,32 @@ class TransactionReportWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Card(
color: const Color.fromARGB(255, 255, 255, 255),
child: Column(
children: [ children: [
ReportPageTitle( const SpaceHeight(24.0),
title: title, Center(
searchDateFormatted: searchDateFormatted, child: Text(
onExport: () async { title,
style:
const TextStyle(fontWeight: FontWeight.w800, fontSize: 16.0),
),
),
const SizedBox(
height: 8.0,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
searchDateFormatted,
style: const TextStyle(fontSize: 16.0),
),
GestureDetector(
onTap: () async {
try { try {
final status = await PermessionHelper().checkPermission(); final status = await PermessionHelper().checkPermission();
if (status) { if (status) {
@ -58,6 +78,25 @@ class TransactionReportWidget extends StatelessWidget {
); );
} }
}, },
child: const Row(
children: [
Text(
"PDF",
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.bold,
color: AppColors.primary,
),
),
Icon(
Icons.download_outlined,
color: AppColors.primary,
)
],
),
),
],
),
), ),
const SpaceHeight(16.0), const SpaceHeight(16.0),
Expanded( Expanded(
@ -189,6 +228,7 @@ class TransactionReportWidget extends StatelessWidget {
), ),
), ),
], ],
),
); );
} }
} }

View File

@ -32,7 +32,7 @@ class _SalesPageState extends State<SalesPage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Text( const Text(
'Apskel POS ', 'Enaklo POS ',
style: TextStyle( style: TextStyle(
color: AppColors.primary, color: AppColors.primary,
fontSize: 22, fontSize: 22,

View File

@ -32,8 +32,6 @@ class DetailProductDialog extends StatelessWidget {
? product.image! ? product.image!
: '${Variables.baseUrl}/${product.image}', : '${Variables.baseUrl}/${product.image}',
fit: BoxFit.cover, fit: BoxFit.cover,
width: 120,
height: 120,
errorWidget: (context, url, error) => Container( errorWidget: (context, url, error) => Container(
width: 120, width: 120,
height: 120, height: 120,

View File

@ -1,4 +1,3 @@
import 'package:enaklo_pos/core/components/custom_modal_dialog.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/core/components/custom_text_field.dart'; import 'package:enaklo_pos/core/components/custom_text_field.dart';
@ -24,10 +23,19 @@ class _FormDiscountDialogState extends State<FormDiscountDialog> {
final discountController = TextEditingController(); final discountController = TextEditingController();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CustomModalDialog( return AlertDialog(
title: widget.data == null ? 'Tambah Diskon' : 'Edit Diskon', title: Row(
contentPadding: const EdgeInsets.all(16.0), mainAxisAlignment: MainAxisAlignment.spaceBetween,
child: SingleChildScrollView( children: [
IconButton(
onPressed: () => context.pop(),
icon: const Icon(Icons.close),
),
const Text('Tambah Diskon'),
const Spacer(),
],
),
content: SingleChildScrollView(
child: SizedBox( child: SizedBox(
width: context.deviceWidth / 3, width: context.deviceWidth / 3,
child: Column( child: Column(

View File

@ -1,6 +1,5 @@
import 'dart:developer'; import 'dart:developer';
import 'package:enaklo_pos/core/components/custom_modal_dialog.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/core/components/custom_text_field.dart'; import 'package:enaklo_pos/core/components/custom_text_field.dart';
@ -22,7 +21,10 @@ import '../../../core/components/spaces.dart';
class FormProductDialog extends StatefulWidget { class FormProductDialog extends StatefulWidget {
final Product? product; final Product? product;
const FormProductDialog({super.key, this.product}); const FormProductDialog({
super.key,
this.product,
});
@override @override
State<FormProductDialog> createState() => _FormProductDialogState(); State<FormProductDialog> createState() => _FormProductDialogState();
@ -76,392 +78,6 @@ class _FormProductDialogState extends State<FormProductDialog> {
stockController!.dispose(); stockController!.dispose();
} }
@override
Widget build(BuildContext context) {
return CustomModalDialog(
title: widget.product == null ? "Tambah Produk" : "Ubah Produk",
subtitle: widget.product == null
? "Silakan isi formulir untuk menambahkan produk baru"
: "Silakan edit formulir untuk memperbarui produk",
child: Expanded(
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomTextField(
controller: nameController!,
label: 'Nama Produk',
keyboardType: TextInputType.text,
textInputAction: TextInputAction.next,
textCapitalization: TextCapitalization.words,
),
const SpaceHeight(20.0),
CustomTextField(
controller: priceController!,
label: 'Harga',
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
onChanged: (value) {
priceValue = value.toIntegerFromText;
final int newValue = value.toIntegerFromText;
priceController!.text = newValue.currencyFormatRp;
priceController!.selection = TextSelection.fromPosition(
TextPosition(offset: priceController!.text.length));
},
),
const SpaceHeight(20.0),
ImagePickerWidget(
label: 'Foto Produk',
onChanged: (file) {
if (file == null) {
return;
}
imageFile = file;
},
initialImageUrl: imageUrl,
),
const SpaceHeight(20.0),
CustomTextField(
controller: stockController!,
label: 'Stok',
keyboardType: TextInputType.number,
),
const SpaceHeight(20.0),
const Text(
"Kategori",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
const SpaceHeight(12.0),
BlocBuilder<GetCategoriesBloc, GetCategoriesState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () {
return const Center(
child: CircularProgressIndicator(),
);
},
success: (categories) {
// Set the selected category if in edit mode and not already set
if (isEditMode &&
selectCategory == null &&
widget.product?.category != null) {
try {
selectCategory = categories.firstWhere(
(cat) => cat.id == widget.product!.category!.id,
);
} catch (e) {
// If no exact match found, leave selectCategory as null
// This will show the hint text instead
log("No matching category found for product category ID: ${widget.product!.category!.id}");
}
}
return DropdownButtonHideUnderline(
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 5),
child: DropdownButton<CategoryModel>(
value: selectCategory,
hint: const Text("Pilih Kategori"),
isExpanded: true, // Untuk mengisi lebar container
onChanged: (newValue) {
if (newValue != null) {
selectCategory = newValue;
setState(() {});
log("selectCategory: ${selectCategory!.name}");
}
},
items: categories
.map<DropdownMenuItem<CategoryModel>>(
(CategoryModel category) {
return DropdownMenuItem<CategoryModel>(
value: category,
child: Text(category.name!),
);
}).toList(),
),
),
);
},
);
},
),
const SpaceHeight(12.0),
const Text(
"Tipe Print",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
//radio printer type
const SpaceHeight(12.0),
Row(
children: [
Radio(
value: 'kitchen',
groupValue: printType,
onChanged: (value) {
setState(() {
printType = value;
});
},
),
const Text('Kitchen'),
],
),
Row(
children: [
Radio(
value: 'bar',
groupValue: printType,
onChanged: (value) {
setState(() {
printType = value;
});
},
),
const Text('Bar'),
],
),
const SpaceHeight(20.0),
Row(
children: [
Checkbox(
value: isBestSeller,
onChanged: (value) {
setState(() {
isBestSeller = value!;
});
},
),
const Text('Produk Favorit'),
],
),
const SpaceHeight(20.0),
const SpaceHeight(24.0),
if (isEditMode)
BlocConsumer<UpdateProductBloc, UpdateProductState>(
listener: (context, state) {
state.maybeMap(
orElse: () {},
success: (_) {
context
.read<SyncProductBloc>()
.add(const SyncProductEvent.syncProduct());
context
.read<GetProductsBloc>()
.add(const GetProductsEvent.fetch());
context.pop(true);
const snackBar = SnackBar(
content: Text('Success Update Product'),
backgroundColor: AppColors.primary,
);
ScaffoldMessenger.of(context).showSnackBar(
snackBar,
);
},
error: (message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error: $message'),
backgroundColor: Colors.red,
),
);
},
);
},
builder: (context, state) {
return state.maybeWhen(
orElse: () {
return Button.filled(
onPressed: () {
if (selectCategory == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please select a category'),
backgroundColor: Colors.red,
),
);
return;
}
log("isBestSeller: $isBestSeller");
final String name = nameController!.text;
final int stock =
stockController!.text.toIntegerFromText;
final Product product = widget.product!.copyWith(
name: name,
price: priceValue.toString(),
stock: stock,
categoryId: selectCategory!.id!,
isFavorite: isBestSeller ? 1 : 0,
printerType: printType,
);
context.read<UpdateProductBloc>().add(
UpdateProductEvent.updateProduct(
product, imageFile));
},
label: 'Ubah Produk',
);
},
loading: () {
return const Center(
child: CircularProgressIndicator(),
);
},
);
},
)
else
BlocConsumer<AddProductBloc, AddProductState>(
listener: (context, state) {
state.maybeMap(
orElse: () {},
success: (_) {
context
.read<SyncProductBloc>()
.add(const SyncProductEvent.syncProduct());
context
.read<GetProductsBloc>()
.add(const GetProductsEvent.fetch());
context.pop(true);
const snackBar = SnackBar(
content: Text('Success Add Product'),
backgroundColor: AppColors.primary,
);
ScaffoldMessenger.of(context).showSnackBar(
snackBar,
);
},
);
},
builder: (context, state) {
return state.maybeWhen(
orElse: () {
return Button.filled(
onPressed: () {
if (selectCategory == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please select a category'),
backgroundColor: Colors.red,
),
);
return;
}
log("isBestSeller: $isBestSeller");
final String name = nameController!.text;
final int stock =
stockController!.text.toIntegerFromText;
final Product product = Product(
name: name,
price: priceValue.toString(),
stock: stock,
categoryId: selectCategory!.id!,
isFavorite: isBestSeller ? 1 : 0,
image: imageFile!.path,
printerType: printType,
);
context.read<AddProductBloc>().add(
AddProductEvent.addProduct(
product, imageFile!));
},
label: 'Simpan Produk',
);
},
loading: () {
return const Center(
child: CircularProgressIndicator(),
);
},
);
},
),
const SpaceHeight(16.0),
],
),
),
),
),
);
}
}
class FormProductDialogOld extends StatefulWidget {
final Product? product;
const FormProductDialogOld({
super.key,
this.product,
});
@override
State<FormProductDialogOld> createState() => _FormProductDialogOldState();
}
class _FormProductDialogOldState extends State<FormProductDialogOld> {
TextEditingController? nameController;
TextEditingController? priceController;
TextEditingController? stockController;
XFile? imageFile;
bool isBestSeller = false;
int priceValue = 0;
CategoryModel? selectCategory;
String? imageUrl;
String? printType = 'kitchen';
bool isEditMode = false;
@override
void initState() {
context.read<GetCategoriesBloc>().add(const GetCategoriesEvent.fetch());
nameController = TextEditingController();
priceController = TextEditingController();
stockController = TextEditingController();
// Check if we're in edit mode
isEditMode = widget.product != null;
if (isEditMode) {
// Pre-fill the form with existing product data
final product = widget.product!;
nameController!.text = product.name ?? '';
priceValue = int.tryParse(product.price ?? '0') ?? 0;
priceController!.text = priceValue.currencyFormatRp;
stockController!.text = (product.stock ?? 0).toString();
isBestSeller = product.isFavorite == 1;
printType = product.printerType ?? 'kitchen';
imageUrl = product.image;
}
super.initState();
}
@override
void dispose() {
super.dispose();
nameController!.dispose();
priceController!.dispose();
stockController!.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return AlertDialog(
@ -542,9 +158,7 @@ class _FormProductDialogOldState extends State<FormProductDialogOld> {
}, },
success: (categories) { success: (categories) {
// Set the selected category if in edit mode and not already set // Set the selected category if in edit mode and not already set
if (isEditMode && if (isEditMode && selectCategory == null && widget.product?.category != null) {
selectCategory == null &&
widget.product?.category != null) {
try { try {
selectCategory = categories.firstWhere( selectCategory = categories.firstWhere(
(cat) => cat.id == widget.product!.category!.id, (cat) => cat.id == widget.product!.category!.id,
@ -693,8 +307,7 @@ class _FormProductDialogOldState extends State<FormProductDialogOld> {
log("isBestSeller: $isBestSeller"); log("isBestSeller: $isBestSeller");
final String name = nameController!.text; final String name = nameController!.text;
final int stock = final int stock = stockController!.text.toIntegerFromText;
stockController!.text.toIntegerFromText;
final Product product = widget.product!.copyWith( final Product product = widget.product!.copyWith(
name: name, name: name,
@ -706,8 +319,7 @@ class _FormProductDialogOldState extends State<FormProductDialogOld> {
); );
context.read<UpdateProductBloc>().add( context.read<UpdateProductBloc>().add(
UpdateProductEvent.updateProduct( UpdateProductEvent.updateProduct(product, imageFile));
product, imageFile));
}, },
label: 'Update Product', label: 'Update Product',
); );
@ -774,8 +386,7 @@ class _FormProductDialogOldState extends State<FormProductDialogOld> {
printerType: printType, printerType: printType,
); );
context.read<AddProductBloc>().add( context.read<AddProductBloc>().add(
AddProductEvent.addProduct( AddProductEvent.addProduct(product, imageFile!));
product, imageFile!));
}, },
label: 'Save Product', label: 'Save Product',
); );

View File

@ -1,4 +1,3 @@
import 'package:enaklo_pos/core/components/custom_modal_dialog.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart'; import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
@ -29,8 +28,7 @@ class _FormTaxDialogState extends State<FormTaxDialog> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
serviceFeeController = serviceFeeController = TextEditingController(text: widget.serviceChargeValue.toString());
TextEditingController(text: widget.serviceChargeValue.toString());
taxFeeController = TextEditingController(text: widget.taxValue.toString()); taxFeeController = TextEditingController(text: widget.taxValue.toString());
} }
@ -43,10 +41,19 @@ class _FormTaxDialogState extends State<FormTaxDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CustomModalDialog( return AlertDialog(
title: 'Edit Perhitungan Biaya', title: Row(
contentPadding: const EdgeInsets.all(16.0), mainAxisAlignment: MainAxisAlignment.spaceBetween,
child: SingleChildScrollView( children: [
IconButton(
onPressed: () => context.pop(),
icon: const Icon(Icons.close),
),
const Text('Edit Perhitungan Biaya'),
const Spacer(),
],
),
content: SingleChildScrollView(
child: SizedBox( child: SizedBox(
width: context.deviceWidth / 3, width: context.deviceWidth / 3,
child: Column( child: Column(
@ -71,8 +78,7 @@ class _FormTaxDialogState extends State<FormTaxDialog> {
Button.filled( Button.filled(
onPressed: () { onPressed: () {
final taxValue = int.tryParse(taxFeeController.text) ?? 0; final taxValue = int.tryParse(taxFeeController.text) ?? 0;
final serviceChargeValue = final serviceChargeValue = int.tryParse(serviceFeeController.text) ?? 0;
int.tryParse(serviceFeeController.text) ?? 0;
if (widget.onSave != null) { if (widget.onSave != null) {
widget.onSave!(taxValue, serviceChargeValue); widget.onSave!(taxValue, serviceChargeValue);

View File

@ -1,5 +1,3 @@
import 'package:enaklo_pos/core/components/buttons.dart';
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/presentation/setting/bloc/discount/discount_bloc.dart'; import 'package:enaklo_pos/presentation/setting/bloc/discount/discount_bloc.dart';
@ -7,6 +5,7 @@ import 'package:enaklo_pos/presentation/setting/bloc/discount/discount_bloc.dart
import '../../home/widgets/custom_tab_bar.dart'; import '../../home/widgets/custom_tab_bar.dart';
import '../dialogs/form_discount_dialog.dart'; import '../dialogs/form_discount_dialog.dart';
import '../models/discount_model.dart'; import '../models/discount_model.dart';
import '../widgets/add_data.dart';
import '../widgets/manage_discount_card.dart'; import '../widgets/manage_discount_card.dart';
import '../widgets/settings_title.dart'; import '../widgets/settings_title.dart';
@ -50,24 +49,12 @@ class _DiscountPageState extends State<DiscountPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return SingleChildScrollView(
children: [
SettingsTitle(
'Kelola Diskon',
subtitle: 'Kelola diskon untuk produk Anda',
actionWidget: [
Button.outlined(
onPressed: onAddDataTap,
label: "Tambah Diskon",
icon: Icon(Icons.add, color: AppColors.primary),
)
],
),
Expanded(
child: SingleChildScrollView(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
const SettingsTitle('Kelola Diskon'),
const SizedBox(height: 24),
CustomTabBar( CustomTabBar(
tabTitles: const ['Semua'], tabTitles: const ['Semua'],
initialTabIndex: 0, initialTabIndex: 0,
@ -83,10 +70,8 @@ class _DiscountPageState extends State<DiscountPage> {
}, loaded: (discounts) { }, loaded: (discounts) {
return GridView.builder( return GridView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: discounts.length, itemCount: discounts.length + 1,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
padding:
const EdgeInsets.symmetric(horizontal: 16.0),
gridDelegate: gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount( const SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 0.85, childAspectRatio: 0.85,
@ -95,10 +80,16 @@ class _DiscountPageState extends State<DiscountPage> {
mainAxisSpacing: 30.0, mainAxisSpacing: 30.0,
), ),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = discounts[index]; if (index == 0) {
return AddData(
title: 'Tambah Diskon Baru',
onPressed: onAddDataTap,
);
}
final item = discounts[index - 1];
return ManageDiscountCard( return ManageDiscountCard(
data: item, data: item,
onEditTap: () {}, onEditTap: (){},
); );
}, },
); );
@ -135,9 +126,6 @@ class _DiscountPageState extends State<DiscountPage> {
), ),
], ],
), ),
),
),
],
); );
} }
} }

View File

@ -171,7 +171,8 @@ class _ManagePrinterPageState extends State<ManagePrinterPage> {
//bytes += generator.setGlobalFont(PosFontType.fontA); //bytes += generator.setGlobalFont(PosFontType.fontA);
bytes += generator.reset(); bytes += generator.reset();
bytes += generator.text('Apskel POS', styles: const PosStyles(bold: true)); bytes +=
generator.text('Enaklo POS', styles: const PosStyles(bold: true));
bytes += bytes +=
generator.text('Reverse text', styles: const PosStyles(reverse: true)); generator.text('Reverse text', styles: const PosStyles(reverse: true));
bytes += generator.text('Underlined text', bytes += generator.text('Underlined text',

View File

@ -1,6 +1,3 @@
import 'package:enaklo_pos/core/components/components.dart';
import 'package:enaklo_pos/core/extensions/build_context_ext.dart';
import 'package:enaklo_pos/presentation/setting/widgets/settings_title.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:enaklo_pos/core/constants/colors.dart'; import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart'; import 'package:enaklo_pos/data/datasources/auth_local_datasource.dart';
@ -39,48 +36,25 @@ class _ServerKeyPageState extends State<ServerKeyPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: AppColors.background, appBar: AppBar(
title: const Text('Save Server Key'),
centerTitle: true,
),
//textfield untuk input server key
body: Column( body: Column(
children: [ children: [
SettingsTitle('Server Key'),
Padding( Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
decoration: BoxDecoration( child: TextField(
color: AppColors.white,
borderRadius: BorderRadius.circular(8.0),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Server Key',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.black,
),
),
SpaceHeight(4),
TextField(
controller: serverKeyController, controller: serverKeyController,
decoration: const InputDecoration( decoration: const InputDecoration(
border: OutlineInputBorder(), border: OutlineInputBorder(),
hintText: 'Server Key', labelText: 'Server Key',
), ),
), ),
],
), ),
), //button untuk save server key
const SpaceHeight(20), ElevatedButton(
Align(
alignment: Alignment.centerRight,
child: Button.filled(
width: context.deviceWidth * 0.2,
height: 44,
onPressed: () { onPressed: () {
AuthLocalDataSource() AuthLocalDataSource()
.saveMidtransServerKey(serverKeyController!.text); .saveMidtransServerKey(serverKeyController!.text);
@ -92,11 +66,7 @@ class _ServerKeyPageState extends State<ServerKeyPage> {
), ),
); );
}, },
label: 'Simpan', child: const Text('Save'),
),
),
],
),
), ),
], ],
), ),

View File

@ -1,7 +1,4 @@
import 'dart:developer'; import 'dart:developer';
import 'package:enaklo_pos/core/components/buttons.dart';
import 'package:enaklo_pos/core/constants/colors.dart';
import 'package:enaklo_pos/presentation/setting/widgets/settings_title.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:enaklo_pos/data/datasources/product_local_datasource.dart'; import 'package:enaklo_pos/data/datasources/product_local_datasource.dart';
@ -19,32 +16,11 @@ class _SyncDataPageState extends State<SyncDataPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: AppColors.background, appBar: AppBar(
title: const Text('Sync Data'),
),
body: Column( body: Column(
children: [ children: [
SettingsTitle('Sync Data'),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(16.0),
margin: const EdgeInsets.only(bottom: 16.0),
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(8.0),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Sinkronasikan Produk',
style: TextStyle(
color: AppColors.black,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
BlocConsumer<SyncProductBloc, SyncProductState>( BlocConsumer<SyncProductBloc, SyncProductState>(
listener: (context, state) { listener: (context, state) {
state.maybeWhen( state.maybeWhen(
@ -58,10 +34,8 @@ class _SyncDataPageState extends State<SyncDataPage> {
); );
}, },
loaded: (productResponseModel) async { loaded: (productResponseModel) async {
await ProductLocalDatasource.instance await ProductLocalDatasource.instance.deleteAllProducts();
.deleteAllProducts(); await ProductLocalDatasource.instance.insertProducts(
await ProductLocalDatasource.instance
.insertProducts(
productResponseModel.data!, productResponseModel.data!,
); );
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@ -76,15 +50,13 @@ class _SyncDataPageState extends State<SyncDataPage> {
builder: (context, state) { builder: (context, state) {
return state.maybeWhen( return state.maybeWhen(
orElse: () { orElse: () {
return Button.filled( return ElevatedButton(
width: 100,
height: 40,
onPressed: () { onPressed: () {
context.read<SyncProductBloc>().add( context
const SyncProductEvent.syncProduct()); .read<SyncProductBloc>()
.add(const SyncProductEvent.syncProduct());
}, },
label: 'Sinkronasikan', child: const Text('Sync Product'));
);
}, },
loading: () { loading: () {
return const Center( return const Center(
@ -94,26 +66,6 @@ class _SyncDataPageState extends State<SyncDataPage> {
); );
}, },
), ),
],
),
),
Container(
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.circular(8.0),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Sinkronasikan Pesanan',
style: TextStyle(
color: AppColors.black,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
BlocConsumer<SyncOrderBloc, SyncOrderState>( BlocConsumer<SyncOrderBloc, SyncOrderState>(
listener: (context, state) { listener: (context, state) {
state.maybeWhen( state.maybeWhen(
@ -139,9 +91,7 @@ class _SyncDataPageState extends State<SyncDataPage> {
builder: (context, state) { builder: (context, state) {
return state.maybeWhen( return state.maybeWhen(
orElse: () { orElse: () {
return Button.filled( return ElevatedButton(
width: 100,
height: 40,
onPressed: () { onPressed: () {
log("🔘 Sync Order button pressed"); log("🔘 Sync Order button pressed");
log("🔘 SyncOrderBloc instance: ${context.read<SyncOrderBloc>()}"); log("🔘 SyncOrderBloc instance: ${context.read<SyncOrderBloc>()}");
@ -150,7 +100,7 @@ class _SyncDataPageState extends State<SyncDataPage> {
.add(const SyncOrderEvent.syncOrder()); .add(const SyncOrderEvent.syncOrder());
log("🔘 SyncOrderEvent.syncOrder dispatched"); log("🔘 SyncOrderEvent.syncOrder dispatched");
}, },
label: 'Sinkronasikan', child: const Text('Sync Order'),
); );
}, },
loading: () { loading: () {
@ -163,12 +113,6 @@ class _SyncDataPageState extends State<SyncDataPage> {
) )
], ],
), ),
),
],
),
),
],
),
); );
} }
} }

View File

@ -61,29 +61,21 @@ class _TaxPageState extends State<TaxPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
const SettingsTitle( const SettingsTitle('Perhitungan Biaya'),
'Perhitungan Biaya', const SizedBox(height: 24),
subtitle: 'Biaya Layanan dan Pajak', BlocBuilder<TaxSettingsBloc, TaxSettingsState>(
),
Expanded(
child: SingleChildScrollView(
child: BlocBuilder<TaxSettingsBloc, TaxSettingsState>(
builder: (context, state) { builder: (context, state) {
return state.when( return state.when(
initial: () => initial: () => const Center(child: CircularProgressIndicator()),
const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
loading: () =>
const Center(child: CircularProgressIndicator()),
error: (message) => Center(child: Text('Error: $message')), error: (message) => Center(child: Text('Error: $message')),
loaded: (taxModel, serviceChargeValue) { loaded: (taxModel, serviceChargeValue) {
final items = [ final items = [
TaxModel( TaxModel(name: 'Biaya Layanan', type: TaxType.layanan, value: serviceChargeValue),
name: 'Biaya Layanan',
type: TaxType.layanan,
value: serviceChargeValue),
taxModel, taxModel,
]; ];
@ -97,9 +89,7 @@ class _TaxPageState extends State<TaxPage> {
shrinkWrap: true, shrinkWrap: true,
itemCount: 2, // Add button + 1 service charge item itemCount: 2, // Add button + 1 service charge item
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 16), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 0.85, childAspectRatio: 0.85,
crossAxisCount: 3, crossAxisCount: 3,
crossAxisSpacing: 30.0, crossAxisSpacing: 30.0,
@ -109,16 +99,13 @@ class _TaxPageState extends State<TaxPage> {
if (index == 0) { if (index == 0) {
return AddData( return AddData(
title: 'Edit Perhitungan', title: 'Edit Perhitungan',
onPressed: () => onAddDataTap( onPressed: () => onAddDataTap(serviceChargeValue, taxModel.value),
serviceChargeValue, taxModel.value),
); );
} }
final item = items.firstWhere( final item = items.firstWhere((element) => element.type.isLayanan);
(element) => element.type.isLayanan);
return ManageTaxCard( return ManageTaxCard(
data: item, data: item,
onEditTap: () => onEditTap( onEditTap: () => onEditTap(item, serviceChargeValue, taxModel.value),
item, serviceChargeValue, taxModel.value),
); );
}, },
), ),
@ -130,9 +117,7 @@ class _TaxPageState extends State<TaxPage> {
shrinkWrap: true, shrinkWrap: true,
itemCount: 2, // Add button + 1 tax item itemCount: 2, // Add button + 1 tax item
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.symmetric(horizontal: 16), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 0.85, childAspectRatio: 0.85,
crossAxisCount: 3, crossAxisCount: 3,
crossAxisSpacing: 30.0, crossAxisSpacing: 30.0,
@ -142,16 +127,13 @@ class _TaxPageState extends State<TaxPage> {
if (index == 0) { if (index == 0) {
return AddData( return AddData(
title: 'Edit Perhitungan', title: 'Edit Perhitungan',
onPressed: () => onAddDataTap( onPressed: () => onAddDataTap(serviceChargeValue, taxModel.value),
serviceChargeValue, taxModel.value),
); );
} }
final item = items.firstWhere( final item = items.firstWhere((element) => element.type.isPajak);
(element) => element.type.isPajak);
return ManageTaxCard( return ManageTaxCard(
data: item, data: item,
onEditTap: () => onEditTap( onEditTap: () => onEditTap(item, serviceChargeValue, taxModel.value),
item, serviceChargeValue, taxModel.value),
); );
}, },
), ),
@ -162,9 +144,8 @@ class _TaxPageState extends State<TaxPage> {
); );
}, },
), ),
),
),
], ],
),
); );
} }
} }

View File

@ -3,6 +3,8 @@ import 'package:flutter/material.dart';
import '../../../core/components/spaces.dart'; import '../../../core/components/spaces.dart';
import '../../../core/constants/colors.dart'; import '../../../core/constants/colors.dart';
class AddData extends StatelessWidget { class AddData extends StatelessWidget {
final String title; final String title;
final VoidCallback onPressed; final VoidCallback onPressed;
@ -19,26 +21,19 @@ class AddData extends StatelessWidget {
onTap: onPressed, onTap: onPressed,
child: Container( child: Container(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration( decoration: ShapeDecoration(
color: AppColors.white, shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0), side: const BorderSide(width: 1, color: AppColors.card),
borderRadius: BorderRadius.circular(19),
),
), ),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Container( const Icon(
width: 56.0,
height: 56.0,
padding: const EdgeInsets.all(12.0),
decoration: BoxDecoration(
color: AppColors.primary.withOpacity(0.1),
shape: BoxShape.circle,
),
child: const Icon(
Icons.add, Icons.add,
color: AppColors.primary, color: AppColors.primary,
), ),
),
const SpaceHeight(8.0), const SpaceHeight(8.0),
Text( Text(
title, title,

View File

@ -18,9 +18,11 @@ class ManageDiscountCard extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration( decoration: ShapeDecoration(
color: AppColors.white, shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0), side: const BorderSide(width: 1, color: AppColors.card),
borderRadius: BorderRadius.circular(19),
),
), ),
child: Stack( child: Stack(
children: [ children: [
@ -34,7 +36,7 @@ class ManageDiscountCard extends StatelessWidget {
margin: const EdgeInsets.only(top: 30.0), margin: const EdgeInsets.only(top: 30.0),
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: AppColors.primary.withOpacity(0.1), color: AppColors.disabled.withOpacity(0.4),
), ),
child: Text( child: Text(
'${data.value!.replaceAll('.00', '')}%', '${data.value!.replaceAll('.00', '')}%',
@ -46,15 +48,26 @@ class ManageDiscountCard extends StatelessWidget {
), ),
const Spacer(), const Spacer(),
Center( Center(
child: Text( child: RichText(
data.name ?? "-", text: TextSpan(
text: 'Nama Promo : ',
children: [
TextSpan(
text: data.name,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
],
style: const TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w400,
color: AppColors.black, color: AppColors.black,
), ),
), ),
), ),
),
const Spacer(), const Spacer(),
], ],
), ),

View File

@ -18,9 +18,11 @@ class ManageTaxCard extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration( decoration: ShapeDecoration(
color: AppColors.white, shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0), side: const BorderSide(width: 1, color: AppColors.card),
borderRadius: BorderRadius.circular(19),
),
), ),
child: Stack( child: Stack(
children: [ children: [
@ -34,7 +36,7 @@ class ManageTaxCard extends StatelessWidget {
margin: const EdgeInsets.only(top: 30.0), margin: const EdgeInsets.only(top: 30.0),
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: AppColors.primary.withOpacity(0.1), color: AppColors.disabled.withOpacity(0.4),
), ),
child: Text( child: Text(
'${data.value}%', '${data.value}%',
@ -46,15 +48,26 @@ class ManageTaxCard extends StatelessWidget {
), ),
const Spacer(), const Spacer(),
Center( Center(
child: Text( child: RichText(
data.type.name, text: TextSpan(
text: 'Nama Promo : ',
children: [
TextSpan(
text: data.type.name,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
],
style: const TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w400,
color: AppColors.black, color: AppColors.black,
), ),
), ),
), ),
),
const Spacer(), const Spacer(),
], ],
), ),

View File

@ -27,19 +27,12 @@ class SettingsTitle extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColors.white, color: AppColors.white,
border: Border(
bottom: BorderSide(
color: AppColors.background,
width: 1.0,
),
),
), ),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
title, title,

View File

@ -1,5 +1,5 @@
name: enaklo_pos name: enaklo_pos
description: "ApskelPOS - Point of Sale Application" description: "EnakloPOS - Point of Sale Application"
# The following line prevents the package from being accidentally published to # The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages. # pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: "none" # Remove this line if you wish to publish to pub.dev publish_to: "none" # Remove this line if you wish to publish to pub.dev