Compare commits

...

7 Commits

Author SHA1 Message Date
efrilm
e825e5daed feat: report page 2025-08-01 18:27:40 +07:00
efrilm
8e4a289625 feat: diskon page 2025-08-01 15:41:02 +07:00
efrilm
8c946ce3d9 feat: tax page 2025-08-01 15:30:33 +07:00
efrilm
1384253e8a feat: sync data page 2025-08-01 15:12:09 +07:00
efrilm
15b27d8f67 feat: server key page 2025-08-01 15:01:44 +07:00
efrilm
06bf0d7987 feat: form product 2025-08-01 14:47:24 +07:00
efrilm
a1f3fbe854 fix: detail product 2025-08-01 14:17:11 +07:00
34 changed files with 1718 additions and 1158 deletions

View File

@ -1,4 +1,4 @@
# EnakloPOS # ApskelPOS
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="EnakloPOS" android:label="ApskelPOS"
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 EnakloPOS app icon...'); print('Generating ApskelPOS 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('✅ App icon generated successfully at: assets/logo/logo_app_icon.png'); print(
'✅ 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>EnakloPOS</string> <string>ApskelPOS</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>

View File

@ -12,6 +12,7 @@ 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,
@ -23,6 +24,7 @@ class CustomModalDialog extends StatelessWidget {
this.maxWidth, this.maxWidth,
this.minHeight, this.minHeight,
this.maxHeight, this.maxHeight,
this.contentPadding,
}); });
@override @override
@ -100,7 +102,10 @@ class CustomModalDialog extends StatelessWidget {
], ],
), ),
), ),
child, Padding(
padding: contentPadding ?? EdgeInsets.zero,
child: child,
),
], ],
), ),
), ),

View File

@ -38,7 +38,7 @@ class ItemSalesInvoice {
return HelperPdfService.saveDocument( return HelperPdfService.saveDocument(
name: name:
'Enaklo POS | Item Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf', 'Apskel 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('Enaklo POS | Item Sales Report', Text('Apskel POS | Item Sales Report',
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,

View File

@ -28,7 +28,8 @@ class RevenueInvoice {
// Load logo image // Load logo image
log("Loading logo image..."); log("Loading logo image...");
final ByteData dataImage = await rootBundle.load('assets/images/logo.png'); final ByteData dataImage =
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");
@ -49,7 +50,7 @@ class RevenueInvoice {
log("Saving PDF document..."); log("Saving PDF document...");
return HelperPdfService.saveDocument( return HelperPdfService.saveDocument(
name: name:
'Enaklo POS | Summary Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf', 'Apskel POS | Summary Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf',
pdf: pdf, pdf: pdf,
); );
} catch (e) { } catch (e) {
@ -69,7 +70,7 @@ class RevenueInvoice {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 1 * PdfPageFormat.cm), SizedBox(height: 1 * PdfPageFormat.cm),
Text('Enaklo POS | Summary Sales Report', Text('Apskel POS | Summary Sales Report',
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -125,7 +126,8 @@ class RevenueInvoice {
buildText( buildText(
title: 'Discount', title: 'Discount',
titleStyle: TextStyle(fontWeight: FontWeight.normal), titleStyle: TextStyle(fontWeight: FontWeight.normal),
value: "- ${safeParseInt(summaryModel.totalDiscount).currencyFormatRp}", value:
"- ${safeParseInt(summaryModel.totalDiscount).currencyFormatRp}",
unite: true, unite: true,
textStyle: TextStyle( textStyle: TextStyle(
color: PdfColor.fromHex('#FF0000'), color: PdfColor.fromHex('#FF0000'),
@ -147,7 +149,8 @@ class RevenueInvoice {
titleStyle: TextStyle( titleStyle: TextStyle(
fontWeight: FontWeight.normal, fontWeight: FontWeight.normal,
), ),
value: safeParseInt(summaryModel.totalServiceCharge).currencyFormatRp, value:
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:
'Enaklo POS | Transaction Sales Report | ${DateTime.now().millisecondsSinceEpoch}.pdf', 'Apskel 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('Enaklo POS | Transaction Sales Report', Text('Apskel 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('Enaklo POS', bytes += generator.text('Apskel 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('Enaklo POS', bytes += generator.text('Apskel POS',
styles: const PosStyles( styles: const PosStyles(
bold: true, bold: true,
align: PosAlign.center, align: PosAlign.center,
@ -474,21 +474,21 @@ class PrintDataoutputs {
} }
Future<List<int>> printOrderV3( Future<List<int>> printOrderV3(
List<ProductQuantity> products, List<ProductQuantity> products,
int totalQuantity, int totalQuantity,
int totalPrice, int totalPrice,
String paymentMethod, String paymentMethod,
int nominalBayar, int nominalBayar,
int kembalian, int kembalian,
int subTotal, int subTotal,
int discount, int discount,
int pajak, int pajak,
int serviceCharge, int serviceCharge,
String namaKasir, String namaKasir,
String customerName, String customerName,
int paper, int paper,
{int taxPercentage = 11, int serviceChargePercentage = 5} {int taxPercentage = 11,
) async { int serviceChargePercentage = 5}) async {
List<int> bytes = []; List<int> bytes = [];
final profile = await CapabilityProfile.load(); final profile = await CapabilityProfile.load();
@ -778,8 +778,13 @@ class PrintDataoutputs {
return bytes; return bytes;
} }
Future<List<int>> printChecker(List<ProductQuantity> products, Future<List<int>> printChecker(
String tableName, String draftName, String cashierName, int paper, String orderType) async { List<ProductQuantity> products,
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();
@ -908,8 +913,13 @@ class PrintDataoutputs {
return bytes; return bytes;
} }
Future<List<int>> printKitchen(List<ProductQuantity> products, Future<List<int>> printKitchen(
String tableNumber, String draftName, String cashierName, int paper, String orderType) async { List<ProductQuantity> products,
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(
'Enaklo POS', 'Apskel POS',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,

View File

@ -34,264 +34,314 @@ 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(
body: Row( backgroundColor: AppColors.background,
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// LEFT CONTENT ReportTitle(
Expanded( actionWidget: [
flex: 2, SizedBox(
child: Align( width: 300,
alignment: Alignment.topLeft, child: CustomDatePicker(
child: SingleChildScrollView( prefix: const Text('From: '),
padding: const EdgeInsets.all(24.0), initialDate: fromDate,
child: Column( onDateSelected: (selectedDate) {
crossAxisAlignment: CrossAxisAlignment.start, fromDate = selectedDate;
children: [
const ReportTitle(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: CustomDatePicker(
prefix: const Text('From: '),
initialDate: fromDate,
onDateSelected: (selectedDate) {
fromDate = selectedDate;
setState(() {}); setState(() {});
}, },
),
),
const SpaceWidth(24.0),
Flexible(
child: CustomDatePicker(
prefix: const Text('To: '),
initialDate: toDate,
onDateSelected: (selectedDate) {
toDate = selectedDate;
setState(() {});
// context.read<TransactionReportBloc>().add(
// TransactionReportEvent.getReport(
// startDate:
// DateFormatter.formatDateTime(
// fromDate),
// endDate: DateFormatter.formatDateTime(
// toDate)),
// );
// context.read<ItemSalesReportBloc>().add(
// ItemSalesReportEvent.getItemSales(
// startDate:
// DateFormatter.formatDateTime(
// fromDate),
// endDate: DateFormatter.formatDateTime(
// toDate)),
// );
},
),
),
],
),
Padding(
padding: const EdgeInsets.all(15.0),
child: Wrap(
children: [
ReportMenu(
label: 'Transaction Report',
onPressed: () {
selectedMenu = 0;
title = 'Transaction Report';
setState(() {});
//enddate is 1 month before the current date
context.read<TransactionReportBloc>().add(
TransactionReportEvent.getReport(
startDate: DateFormatter.formatDateTime(
fromDate),
endDate: DateFormatter.formatDateTime(
toDate)),
);
},
isActive: selectedMenu == 0,
),
ReportMenu(
label: 'Item Sales Report',
onPressed: () {
selectedMenu = 1;
title = 'Item Sales Report';
setState(() {});
context.read<ItemSalesReportBloc>().add(
ItemSalesReportEvent.getItemSales(
startDate: DateFormatter.formatDateTime(
fromDate),
endDate: DateFormatter.formatDateTime(
toDate)),
);
},
isActive: selectedMenu == 1,
),
ReportMenu(
label: 'Product Sales Chart',
onPressed: () {
selectedMenu = 2;
title = 'Product Sales Chart';
setState(() {});
context.read<ProductSalesBloc>().add(
ProductSalesEvent.getProductSales(
DateFormatter.formatDateTime(fromDate),
DateFormatter.formatDateTime(toDate)),
);
},
isActive: selectedMenu == 2,
),
ReportMenu(
label: 'Summary Sales Report',
onPressed: () {
selectedMenu = 3;
title = 'Summary Sales Report';
setState(() {});
context.read<SummaryBloc>().add(
SummaryEvent.getSummary(
DateFormatter.formatDateTime(fromDate),
DateFormatter.formatDateTime(toDate)),
);
log("Date ${DateFormatter.formatDateTime(fromDate)}");
},
isActive: selectedMenu == 3,
),
ReportMenu(
label: 'Payment Method Report',
onPressed: () {
selectedMenu = 4;
title = 'Payment Method Report';
setState(() {});
context.read<PaymentMethodReportBloc>().add(
PaymentMethodReportEvent.getPaymentMethodReport(
startDate: DateFormatter.formatDateTime(fromDate),
endDate: DateFormatter.formatDateTime(toDate)),
);
},
isActive: selectedMenu == 4,
),
],
),
),
],
), ),
), ),
const SpaceWidth(24.0),
SizedBox(
width: 300,
child: CustomDatePicker(
prefix: const Text('To: '),
initialDate: toDate,
onDateSelected: (selectedDate) {
toDate = selectedDate;
setState(() {});
// context.read<TransactionReportBloc>().add(
// TransactionReportEvent.getReport(
// startDate:
// DateFormatter.formatDateTime(
// fromDate),
// endDate: DateFormatter.formatDateTime(
// toDate)),
// );
// context.read<ItemSalesReportBloc>().add(
// ItemSalesReportEvent.getItemSales(
// startDate:
// DateFormatter.formatDateTime(
// fromDate),
// endDate: DateFormatter.formatDateTime(
// toDate)),
// );
},
),
),
],
),
Expanded(
child: Row(
children: [
// LEFT CONTENT
Expanded(
flex: 2,
child: Material(
color: AppColors.white,
child: Align(
alignment: Alignment.topLeft,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ReportMenu(
label: 'Laporan Transaksi',
subtitle:
'Menampilkan riwayat lengkap semua transaksi yang telah dilakukan.',
icon: Icons.receipt_long_outlined,
onPressed: () {
selectedMenu = 0;
title = 'Laporan Transaksi';
setState(() {});
//enddate is 1 month before the current date
context.read<TransactionReportBloc>().add(
TransactionReportEvent.getReport(
startDate:
DateFormatter.formatDateTime(
fromDate),
endDate: DateFormatter.formatDateTime(
toDate)),
);
},
isActive: selectedMenu == 0,
),
ReportMenu(
label: 'Laporan Penjualan Item',
subtitle:
'Laporan penjualan berdasarkan masing-masing item atau produk.',
icon: Icons.inventory_2_outlined,
onPressed: () {
selectedMenu = 1;
title = 'Laporan Penjualan Item';
setState(() {});
context.read<ItemSalesReportBloc>().add(
ItemSalesReportEvent.getItemSales(
startDate:
DateFormatter.formatDateTime(
fromDate),
endDate: DateFormatter.formatDateTime(
toDate)),
);
},
isActive: selectedMenu == 1,
),
ReportMenu(
label: 'Chart Penjualan Produk',
subtitle:
'Grafik visual penjualan produk untuk analisa performa penjualan.',
icon: Icons.bar_chart_outlined,
onPressed: () {
selectedMenu = 2;
title = 'Chart Penjualan Produk';
setState(() {});
context.read<ProductSalesBloc>().add(
ProductSalesEvent.getProductSales(
DateFormatter.formatDateTime(
fromDate),
DateFormatter.formatDateTime(toDate)),
);
},
isActive: selectedMenu == 2,
),
ReportMenu(
label: 'Ringkasan Laporan Penjualan',
subtitle:
'Ringkasan total penjualan dalam periode tertentu.',
icon: Icons.insert_drive_file_outlined,
onPressed: () {
selectedMenu = 3;
title = 'Ringkasan Laporan Penjualan';
setState(() {});
context.read<SummaryBloc>().add(
SummaryEvent.getSummary(
DateFormatter.formatDateTime(
fromDate),
DateFormatter.formatDateTime(toDate)),
);
log("Date ${DateFormatter.formatDateTime(fromDate)}");
},
isActive: selectedMenu == 3,
),
ReportMenu(
label: 'Laporan Metode Pembayaran',
subtitle:
'Laporan metode pembayaran yang digunakan.',
icon: Icons.payment_outlined,
onPressed: () {
selectedMenu = 4;
title = 'Laporan Metode Pembayaran';
setState(() {});
context.read<PaymentMethodReportBloc>().add(
PaymentMethodReportEvent
.getPaymentMethodReport(
startDate:
DateFormatter.formatDateTime(
fromDate),
endDate:
DateFormatter.formatDateTime(
toDate)),
);
},
isActive: selectedMenu == 4,
),
],
),
),
),
),
),
// RIGHT CONTENT
Expanded(
flex: 4,
child: selectedMenu == 0
? BlocBuilder<TransactionReportBloc,
TransactionReportState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
loaded: (transactionReport) {
return TransactionReportWidget(
transactionReport: transactionReport,
title: title,
searchDateFormatted: searchDateFormatted,
headerWidgets: _getTitleReportPageWidget(),
);
},
);
},
)
: selectedMenu == 1
? BlocBuilder<ItemSalesReportBloc,
ItemSalesReportState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
loaded: (itemSales) {
return ItemSalesReportWidget(
itemSales: itemSales,
title: title,
searchDateFormatted:
searchDateFormatted,
headerWidgets:
_getItemSalesPageWidget(),
);
},
);
},
)
: selectedMenu == 2
? BlocBuilder<ProductSalesBloc,
ProductSalesState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
success: (productSales) {
return ProductSalesChartWidgets(
title: title,
searchDateFormatted:
searchDateFormatted,
productSales: productSales,
);
},
);
},
)
: selectedMenu == 3
? BlocBuilder<SummaryBloc, SummaryState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child:
CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
success: (summary) {
return SummaryReportWidget(
summary: summary,
title: title,
searchDateFormatted:
searchDateFormatted,
);
},
);
},
)
: selectedMenu == 4
? BlocBuilder<PaymentMethodReportBloc,
PaymentMethodReportState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child:
CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
loaded: (paymentMethodData) {
return PaymentMethodReportWidget(
paymentMethodData:
paymentMethodData,
title: title,
searchDateFormatted:
searchDateFormatted,
headerWidgets:
_getPaymentMethodPageWidget(),
);
},
);
},
)
: const SizedBox.shrink()),
],
), ),
), ),
// RIGHT CONTENT
Expanded(
flex: 2,
child: selectedMenu == 0
? BlocBuilder<TransactionReportBloc, TransactionReportState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
loaded: (transactionReport) {
return TransactionReportWidget(
transactionReport: transactionReport,
title: title,
searchDateFormatted: searchDateFormatted,
headerWidgets: _getTitleReportPageWidget(),
);
},
);
},
)
: selectedMenu == 1
? BlocBuilder<ItemSalesReportBloc, ItemSalesReportState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
loaded: (itemSales) {
return ItemSalesReportWidget(
itemSales: itemSales,
title: title,
searchDateFormatted: searchDateFormatted,
headerWidgets: _getItemSalesPageWidget(),
);
},
);
},
)
: selectedMenu == 2
? BlocBuilder<ProductSalesBloc, ProductSalesState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
success: (productSales) {
return ProductSalesChartWidgets(
title: title,
searchDateFormatted: searchDateFormatted,
productSales: productSales,
);
},
);
},
)
: selectedMenu == 3
? BlocBuilder<SummaryBloc, SummaryState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
success: (summary) {
return SummaryReportWidget(
summary: summary,
title: title,
searchDateFormatted: searchDateFormatted,
);
},
);
},
)
: selectedMenu == 4
? BlocBuilder<PaymentMethodReportBloc, PaymentMethodReportState>(
builder: (context, state) {
return state.maybeWhen(
orElse: () => const Center(
child: CircularProgressIndicator(),
),
error: (message) {
return Text(message);
},
loaded: (paymentMethodData) {
return PaymentMethodReportWidget(
paymentMethodData: paymentMethodData,
title: title,
searchDateFormatted: searchDateFormatted,
headerWidgets: _getPaymentMethodPageWidget(),
);
},
);
},
)
: const SizedBox.shrink()),
], ],
), ),
); );
@ -314,11 +364,11 @@ class _ReportPageState extends State<ReportPage> {
List<Widget> _getItemSalesPageWidget() { List<Widget> _getItemSalesPageWidget() {
return [ return [
_getTitleItemWidget('ID', 80), _getTitleItemWidget('ID', 80),
_getTitleItemWidget('Order', 60), _getTitleItemWidget('Order', 100),
_getTitleItemWidget('Product', 160), _getTitleItemWidget('Product', 200),
_getTitleItemWidget('Qty', 60), _getTitleItemWidget('Qty', 60),
_getTitleItemWidget('Price', 140), _getTitleItemWidget('Price', 150),
_getTitleItemWidget('Total Price', 140), _getTitleItemWidget('Total Price', 160),
]; ];
} }

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,166 +26,123 @@ class ItemSalesReportWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( return Column(
color: const Color.fromARGB(255, 255, 255, 255), children: [
child: Column( ReportPageTitle(
children: [ title: title,
const SpaceHeight(24.0), searchDateFormatted: searchDateFormatted,
Center( onExport: () async {
child: Text( try {
title, final status = await PermessionHelper().checkPermission();
style: if (status) {
const TextStyle(fontWeight: FontWeight.w800, fontSize: 16.0), final pdfFile = await ItemSalesInvoice.generate(
), itemSales, searchDateFormatted);
), log("pdfFile: $pdfFile");
const SizedBox( await HelperPdfService.openFile(pdfFile);
height: 8.0, } else {
), ScaffoldMessenger.of(context).showSnackBar(
Padding( const SnackBar(
padding: const EdgeInsets.symmetric(horizontal: 12), content: Text('Storage permission is required to save PDF'),
child: Row( backgroundColor: Colors.red,
mainAxisAlignment: MainAxisAlignment.spaceBetween, ),
children: [ );
Text( }
searchDateFormatted, } catch (e) {
style: const TextStyle(fontSize: 16.0), log("Error generating PDF: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to generate PDF: $e'),
backgroundColor: Colors.red,
), ),
GestureDetector( );
onTap: () async { }
try { },
final status = await PermessionHelper().checkPermission(); ),
if (status) { const SpaceHeight(16.0),
final pdfFile = await ItemSalesInvoice.generate( Expanded(
itemSales, searchDateFormatted); child: Padding(
log("pdfFile: $pdfFile"); padding: const EdgeInsets.all(12),
await HelperPdfService.openFile(pdfFile); child: ClipRRect(
} else { borderRadius: BorderRadius.circular(12),
ScaffoldMessenger.of(context).showSnackBar( child: HorizontalDataTable(
const SnackBar( leftHandSideColumnWidth: 80,
content: Text('Storage permission is required to save PDF'), rightHandSideColumnWidth: 670,
backgroundColor: Colors.red, isFixedHeader: true,
), headerWidgets: headerWidgets,
);
} // isFixedFooter: true,
} catch (e) { // footerWidgets: _getTitleWidget(),
log("Error generating PDF: $e"); leftSideItemBuilder: (context, index) {
ScaffoldMessenger.of(context).showSnackBar( return Container(
SnackBar( width: 80,
content: Text('Failed to generate PDF: $e'), height: 52,
backgroundColor: Colors.red, alignment: Alignment.centerLeft,
), child: Center(child: Text(itemSales[index].id.toString())),
); );
} },
}, rightSideItemBuilder: (context, index) {
child: const Row( return Row(
children: [ children: <Widget>[
Text( Container(
"PDF", width: 100,
style: TextStyle( height: 52,
fontSize: 14.0, alignment: Alignment.centerLeft,
fontWeight: FontWeight.bold, child: Center(
color: AppColors.primary, child: Text(itemSales[index].orderId.toString())),
), ),
Container(
width: 200,
height: 52,
alignment: Alignment.centerLeft,
child:
Center(child: Text(itemSales[index].productName!)),
),
Container(
width: 60,
height: 52,
alignment: Alignment.centerLeft,
child: Center(
child: Text(itemSales[index].quantity.toString())),
),
Container(
width: 150,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
itemSales[index].price!.currencyFormatRp,
)),
),
Container(
width: 160,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
(itemSales[index].price! * itemSales[index].quantity!)
.currencyFormatRp,
)),
), ),
Icon(
Icons.download_outlined,
color: AppColors.primary,
)
], ],
), );
},
itemCount: itemSales.length,
rowSeparatorWidget: const Divider(
color: Colors.black38,
height: 1.0,
thickness: 0.0,
), ),
], leftHandSideColBackgroundColor: AppColors.white,
), rightHandSideColBackgroundColor: AppColors.white,
),
const SpaceHeight(16.0),
Expanded(
child: Padding(
padding: const EdgeInsets.all(12),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: HorizontalDataTable(
leftHandSideColumnWidth: 80,
rightHandSideColumnWidth: 560,
isFixedHeader: true,
headerWidgets: headerWidgets,
// isFixedFooter: true, itemExtent: 55,
// footerWidgets: _getTitleWidget(),
leftSideItemBuilder: (context, index) {
return Container(
width: 80,
height: 52,
alignment: Alignment.centerLeft,
child:
Center(child: Text(itemSales[index].id.toString())),
);
},
rightSideItemBuilder: (context, index) {
return Row(
children: <Widget>[
Container(
width: 60,
height: 52,
alignment: Alignment.centerLeft,
child: Center(
child: Text(itemSales[index].orderId.toString())),
),
Container(
width: 160,
height: 52,
alignment: Alignment.centerLeft,
child: Center(
child: Text(itemSales[index].productName!)),
),
Container(
width: 60,
height: 52,
alignment: Alignment.centerLeft,
child: Center(
child:
Text(itemSales[index].quantity.toString())),
),
Container(
width: 140,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
itemSales[index].price!.currencyFormatRp,
)),
),
Container(
width: 140,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
(itemSales[index].price! *
itemSales[index].quantity!)
.currencyFormatRp,
)),
),
],
);
},
itemCount: itemSales.length,
rowSeparatorWidget: const Divider(
color: Colors.black38,
height: 1.0,
thickness: 0.0,
),
leftHandSideColBackgroundColor: AppColors.white,
rightHandSideColBackgroundColor: AppColors.white,
itemExtent: 55,
),
), ),
), ),
), ),
], ),
), ],
); );
} }
} }

View File

@ -1,9 +1,7 @@
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';
@ -109,20 +107,20 @@ class PaymentMethodReportWidget extends StatelessWidget {
), ),
), ),
child: Row( child: Row(
children: [ children: [
_getBodyItemWidget( _getBodyItemWidget(
item.paymentMethod ?? '-', item.paymentMethod ?? '-',
180, 180,
), ),
_getBodyItemWidget( _getBodyItemWidget(
item.totalAmount?.currencyFormatRpV2 ?? 'Rp 0', item.totalAmount?.currencyFormatRpV2 ?? 'Rp 0',
180, 180,
), ),
_getBodyItemWidget( _getBodyItemWidget(
item.transactionCount?.toString() ?? '0', item.transactionCount?.toString() ?? '0',
180, 180,
), ),
], ],
), ),
); );
}).toList() ?? }).toList() ??

View File

@ -1,6 +1,7 @@
// 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';
@ -67,59 +68,61 @@ class _ProductSalesChartWidgetsState extends State<ProductSalesChartWidgets> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( return Column(
color: const Color.fromARGB(255, 255, 255, 255), children: [
child: Padding( ReportPageTitle(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 16), title: widget.title,
child: Column( searchDateFormatted: widget.searchDateFormatted,
children: [ onExport: () async {},
const SpaceHeight(24.0), isExport: false, // Set to false if export is not needed
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(
dataMap: dataMap2,
animationDuration: Duration(milliseconds: 800),
chartLegendSpacing: 32,
chartRadius: MediaQuery.of(context).size.width / 3.2,
colorList: colorList,
initialAngleInDegree: 0,
chartType: ChartType.disc,
ringStrokeWidth: 32,
// centerText: "HYBRID",
legendOptions: LegendOptions(
showLegendsInRow: false,
legendPosition: LegendPosition.right,
showLegends: true,
legendShape: BoxShape.circle,
legendTextStyle: TextStyle(
fontWeight: FontWeight.bold,
),
),
chartValuesOptions: ChartValuesOptions(
showChartValueBackground: true,
showChartValues: true,
showChartValuesInPercentage: false,
showChartValuesOutside: false,
decimalPlaces: 0,
),
// gradientList: ---To add gradient colors---
// emptyColorGradient: ---Empty Color gradient---
)
],
), ),
), 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(
children: [
PieChart(
dataMap: dataMap2,
animationDuration: Duration(milliseconds: 800),
chartLegendSpacing: 32,
chartRadius: MediaQuery.of(context).size.width / 3.2,
colorList: colorList,
initialAngleInDegree: 0,
chartType: ChartType.disc,
ringStrokeWidth: 32,
// centerText: "HYBRID",
legendOptions: LegendOptions(
showLegendsInRow: false,
legendPosition: LegendPosition.right,
showLegends: true,
legendShape: BoxShape.circle,
legendTextStyle: TextStyle(
fontWeight: FontWeight.bold,
),
),
chartValuesOptions: ChartValuesOptions(
showChartValueBackground: true,
showChartValues: true,
showChartValuesInPercentage: false,
showChartValuesOutside: false,
decimalPlaces: 0,
),
// gradientList: ---To add gradient colors---
// emptyColorGradient: ---Empty Color gradient---
),
],
),
),
),
)
],
); );
} }
} }

View File

@ -1,13 +1,12 @@
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;
@ -16,6 +15,8 @@ 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
@ -23,38 +24,43 @@ class ReportMenu extends StatelessWidget {
return GestureDetector( return GestureDetector(
onTap: onPressed, onTap: onPressed,
child: Container( child: Container(
margin: const EdgeInsets.all(5.0), padding: const EdgeInsets.all(12.0),
width: 180.0,
height: 160.0,
alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
color: border: Border(
isActive ? AppColors.primary.withOpacity(0.13) : AppColors.white, right: BorderSide(
borderRadius: BorderRadius.circular(18.0), color: isActive ? AppColors.primary : Colors.transparent,
border: Border.all( width: 4.0,
color: isActive ? AppColors.primary : AppColors.stroke, ),
), ),
), ),
child: Column( child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
Assets.icons.report.svg( Icon(
colorFilter: isActive icon,
? const ColorFilter.mode( size: 24.0,
AppColors.primary, color: isActive ? AppColors.primary : AppColors.grey,
BlendMode.srcIn,
)
: null,
), ),
const SpaceHeight(28.0), const SpaceWidth(12),
Text( Expanded(
label, child: Column(
style: TextStyle( crossAxisAlignment: CrossAxisAlignment.start,
color: isActive ? AppColors.primary : AppColors.black, children: [
fontWeight: FontWeight.w600, Text(
label,
style: const TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4.0),
Text(
subtitle,
style:
const TextStyle(fontSize: 14.0, color: AppColors.grey),
),
],
), ),
), ),
const SpaceHeight(24.0),
], ],
), ),
), ),

View File

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

View File

@ -9,7 +9,6 @@ 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';
@ -55,7 +54,8 @@ 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 = await PermessionHelper().checkPermission(); final status =
await PermessionHelper().checkPermission();
log("Permission status: $status"); log("Permission status: $status");
if (status) { if (status) {
@ -75,7 +75,8 @@ class SummaryReportWidget extends StatelessWidget {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text('PDF saved successfully: ${pdfFile.path}'), content: Text(
'PDF saved successfully: ${pdfFile.path}'),
backgroundColor: Colors.green, backgroundColor: Colors.green,
), ),
); );
@ -83,7 +84,8 @@ class SummaryReportWidget extends StatelessWidget {
log("Permission denied"); log("Permission denied");
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
content: Text('Storage permission is required to save PDF'), content: Text(
'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,208 +27,168 @@ class TransactionReportWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( return Column(
color: const Color.fromARGB(255, 255, 255, 255), children: [
child: Column( ReportPageTitle(
children: [ title: title,
const SpaceHeight(24.0), searchDateFormatted: searchDateFormatted,
Center( onExport: () async {
child: Text( try {
title, final status = await PermessionHelper().checkPermission();
style: if (status) {
const TextStyle(fontWeight: FontWeight.w800, fontSize: 16.0), final pdfFile = await TransactionSalesInvoice.generate(
), transactionReport, searchDateFormatted);
), log("pdfFile: $pdfFile");
const SizedBox( await HelperPdfService.openFile(pdfFile);
height: 8.0, } else {
), ScaffoldMessenger.of(context).showSnackBar(
Padding( const SnackBar(
padding: const EdgeInsets.symmetric(horizontal: 12), content: Text('Storage permission is required to save PDF'),
child: Row( backgroundColor: Colors.red,
mainAxisAlignment: MainAxisAlignment.spaceBetween, ),
children: [ );
Text( }
searchDateFormatted, } catch (e) {
style: const TextStyle(fontSize: 16.0), log("Error generating PDF: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to generate PDF: $e'),
backgroundColor: Colors.red,
), ),
GestureDetector( );
onTap: () async { }
try { },
final status = await PermessionHelper().checkPermission(); ),
if (status) { const SpaceHeight(16.0),
final pdfFile = await TransactionSalesInvoice.generate( Expanded(
transactionReport, searchDateFormatted); child: Padding(
log("pdfFile: $pdfFile"); padding: const EdgeInsets.all(12),
await HelperPdfService.openFile(pdfFile); child: ClipRRect(
} else { borderRadius: BorderRadius.circular(12),
ScaffoldMessenger.of(context).showSnackBar( child: HorizontalDataTable(
const SnackBar( leftHandSideColumnWidth: 50,
content: Text('Storage permission is required to save PDF'), rightHandSideColumnWidth: 1020,
backgroundColor: Colors.red, isFixedHeader: true,
headerWidgets: headerWidgets,
// isFixedFooter: true,
// footerWidgets: _getTitleWidget(),
leftSideItemBuilder: (context, index) {
return Container(
width: 40,
height: 52,
alignment: Alignment.centerLeft,
child: Center(
child: Text(transactionReport[index].id.toString())),
);
},
rightSideItemBuilder: (context, index) {
return Row(
children: <Widget>[
Container(
width: 120,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].total!.currencyFormatRp,
)),
),
Container(
width: 120,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].subTotal!.currencyFormatRp,
)),
),
Container(
width: 100,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].tax!.currencyFormatRp,
)),
),
Container(
width: 100,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
int.parse(transactionReport[index]
.discountAmount!
.replaceAll('.00', ''))
.currencyFormatRp,
), ),
);
}
} catch (e) {
log("Error generating PDF: $e");
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Failed to generate PDF: $e'),
backgroundColor: Colors.red,
),
);
}
},
child: const Row(
children: [
Text(
"PDF",
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.bold,
color: AppColors.primary,
), ),
), ),
Icon( Container(
Icons.download_outlined, width: 100,
color: AppColors.primary, height: 52,
) padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index]
.serviceCharge!
.currencyFormatRp,
),
),
),
Container(
width: 100,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].totalItem.toString()),
),
),
Container(
width: 150,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(transactionReport[index].namaKasir!),
),
),
Container(
width: 230,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(transactionReport[index]
.transactionTime!
.toFormattedDate()),
),
),
], ],
), );
},
itemCount: transactionReport.length,
rowSeparatorWidget: const Divider(
color: Colors.black38,
height: 1.0,
thickness: 0.0,
), ),
], leftHandSideColBackgroundColor: AppColors.white,
), rightHandSideColBackgroundColor: AppColors.white,
),
const SpaceHeight(16.0),
Expanded(
child: Padding(
padding: const EdgeInsets.all(12),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: HorizontalDataTable(
leftHandSideColumnWidth: 50,
rightHandSideColumnWidth: 1020,
isFixedHeader: true,
headerWidgets: headerWidgets,
// isFixedFooter: true,
// footerWidgets: _getTitleWidget(),
leftSideItemBuilder: (context, index) {
return Container(
width: 40,
height: 52,
alignment: Alignment.centerLeft,
child: Center(
child: Text(transactionReport[index].id.toString())),
);
},
rightSideItemBuilder: (context, index) {
return Row(
children: <Widget>[
Container(
width: 120,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].total!.currencyFormatRp,
)),
),
Container(
width: 120,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].subTotal!.currencyFormatRp,
)),
),
Container(
width: 100,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].tax!.currencyFormatRp,
)),
),
Container(
width: 100,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
int.parse(transactionReport[index]
.discountAmount!
.replaceAll('.00', ''))
.currencyFormatRp,
),
),
),
Container(
width: 100,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index]
.serviceCharge!
.currencyFormatRp,
),
),
),
Container(
width: 100,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(
transactionReport[index].totalItem.toString()),
),
),
Container(
width: 150,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(transactionReport[index].namaKasir!),
),
),
Container(
width: 230,
height: 52,
padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
alignment: Alignment.centerLeft,
child: Center(
child: Text(transactionReport[index]
.transactionTime!
.toFormattedDate()),
),
),
],
);
},
itemCount: transactionReport.length,
rowSeparatorWidget: const Divider(
color: Colors.black38,
height: 1.0,
thickness: 0.0,
),
leftHandSideColBackgroundColor: AppColors.white,
rightHandSideColBackgroundColor: AppColors.white,
itemExtent: 55, itemExtent: 55,
),
), ),
), ),
), ),
], ),
), ],
); );
} }
} }

View File

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

View File

@ -32,6 +32,8 @@ 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,3 +1,4 @@
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';
@ -23,19 +24,10 @@ class _FormDiscountDialogState extends State<FormDiscountDialog> {
final discountController = TextEditingController(); final discountController = TextEditingController();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return CustomModalDialog(
title: Row( title: widget.data == null ? 'Tambah Diskon' : 'Edit Diskon',
mainAxisAlignment: MainAxisAlignment.spaceBetween, contentPadding: const EdgeInsets.all(16.0),
children: [ child: SingleChildScrollView(
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,5 +1,6 @@
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';
@ -21,10 +22,7 @@ import '../../../core/components/spaces.dart';
class FormProductDialog extends StatefulWidget { class FormProductDialog extends StatefulWidget {
final Product? product; final Product? product;
const FormProductDialog({ const FormProductDialog({super.key, this.product});
super.key,
this.product,
});
@override @override
State<FormProductDialog> createState() => _FormProductDialogState(); State<FormProductDialog> createState() => _FormProductDialogState();
@ -78,6 +76,392 @@ 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(
@ -158,7 +542,9 @@ class _FormProductDialogState extends State<FormProductDialog> {
}, },
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 && selectCategory == null && widget.product?.category != null) { if (isEditMode &&
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,
@ -307,7 +693,8 @@ class _FormProductDialogState extends State<FormProductDialog> {
log("isBestSeller: $isBestSeller"); log("isBestSeller: $isBestSeller");
final String name = nameController!.text; final String name = nameController!.text;
final int stock = stockController!.text.toIntegerFromText; final int stock =
stockController!.text.toIntegerFromText;
final Product product = widget.product!.copyWith( final Product product = widget.product!.copyWith(
name: name, name: name,
@ -319,7 +706,8 @@ class _FormProductDialogState extends State<FormProductDialog> {
); );
context.read<UpdateProductBloc>().add( context.read<UpdateProductBloc>().add(
UpdateProductEvent.updateProduct(product, imageFile)); UpdateProductEvent.updateProduct(
product, imageFile));
}, },
label: 'Update Product', label: 'Update Product',
); );
@ -386,7 +774,8 @@ class _FormProductDialogState extends State<FormProductDialog> {
printerType: printType, printerType: printType,
); );
context.read<AddProductBloc>().add( context.read<AddProductBloc>().add(
AddProductEvent.addProduct(product, imageFile!)); AddProductEvent.addProduct(
product, imageFile!));
}, },
label: 'Save Product', label: 'Save Product',
); );

View File

@ -1,3 +1,4 @@
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';
@ -28,7 +29,8 @@ class _FormTaxDialogState extends State<FormTaxDialog> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
serviceFeeController = TextEditingController(text: widget.serviceChargeValue.toString()); serviceFeeController =
TextEditingController(text: widget.serviceChargeValue.toString());
taxFeeController = TextEditingController(text: widget.taxValue.toString()); taxFeeController = TextEditingController(text: widget.taxValue.toString());
} }
@ -41,19 +43,10 @@ class _FormTaxDialogState extends State<FormTaxDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return CustomModalDialog(
title: Row( title: 'Edit Perhitungan Biaya',
mainAxisAlignment: MainAxisAlignment.spaceBetween, contentPadding: const EdgeInsets.all(16.0),
children: [ child: SingleChildScrollView(
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(
@ -78,7 +71,8 @@ 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 = int.tryParse(serviceFeeController.text) ?? 0; final serviceChargeValue =
int.tryParse(serviceFeeController.text) ?? 0;
if (widget.onSave != null) { if (widget.onSave != null) {
widget.onSave!(taxValue, serviceChargeValue); widget.onSave!(taxValue, serviceChargeValue);

View File

@ -1,3 +1,5 @@
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';
@ -5,7 +7,6 @@ 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';
@ -49,83 +50,94 @@ class _DiscountPageState extends State<DiscountPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SingleChildScrollView( return Column(
child: Column( children: [
mainAxisAlignment: MainAxisAlignment.start, SettingsTitle(
children: [ 'Kelola Diskon',
const SettingsTitle('Kelola Diskon'), subtitle: 'Kelola diskon untuk produk Anda',
const SizedBox(height: 24), actionWidget: [
CustomTabBar( Button.outlined(
tabTitles: const ['Semua'], onPressed: onAddDataTap,
initialTabIndex: 0, label: "Tambah Diskon",
tabViews: [ icon: Icon(Icons.add, color: AppColors.primary),
// SEMUA TAB )
SizedBox( ],
child: BlocBuilder<DiscountBloc, DiscountState>( ),
builder: (context, state) { Expanded(
return state.maybeWhen(orElse: () { child: SingleChildScrollView(
return const Center( child: Column(
child: CircularProgressIndicator(), mainAxisAlignment: MainAxisAlignment.start,
); children: [
}, loaded: (discounts) { CustomTabBar(
return GridView.builder( tabTitles: const ['Semua'],
shrinkWrap: true, initialTabIndex: 0,
itemCount: discounts.length + 1, tabViews: [
physics: const NeverScrollableScrollPhysics(), // SEMUA TAB
gridDelegate: SizedBox(
const SliverGridDelegateWithFixedCrossAxisCount( child: BlocBuilder<DiscountBloc, DiscountState>(
childAspectRatio: 0.85, builder: (context, state) {
crossAxisCount: 3, return state.maybeWhen(orElse: () {
crossAxisSpacing: 30.0, return const Center(
mainAxisSpacing: 30.0, child: CircularProgressIndicator(),
),
itemBuilder: (context, index) {
if (index == 0) {
return AddData(
title: 'Tambah Diskon Baru',
onPressed: onAddDataTap,
); );
} }, loaded: (discounts) {
final item = discounts[index - 1]; return GridView.builder(
return ManageDiscountCard( shrinkWrap: true,
data: item, itemCount: discounts.length,
onEditTap: (){}, physics: const NeverScrollableScrollPhysics(),
); padding:
const EdgeInsets.symmetric(horizontal: 16.0),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: 0.85,
crossAxisCount: 3,
crossAxisSpacing: 30.0,
mainAxisSpacing: 30.0,
),
itemBuilder: (context, index) {
final item = discounts[index];
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),
// );
// },
// );
}, },
); ),
}); ),
// 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),
// );
// },
// );
},
), ),
), ],
], ),
), ),
], ),
), ],
); );
} }
} }

View File

@ -171,8 +171,7 @@ class _ManagePrinterPageState extends State<ManagePrinterPage> {
//bytes += generator.setGlobalFont(PosFontType.fontA); //bytes += generator.setGlobalFont(PosFontType.fontA);
bytes += generator.reset(); bytes += generator.reset();
bytes += bytes += generator.text('Apskel POS', styles: const PosStyles(bold: true));
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,3 +1,6 @@
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';
@ -36,37 +39,64 @@ class _ServerKeyPageState extends State<ServerKeyPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( backgroundColor: AppColors.background,
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(20.0), padding: const EdgeInsets.all(16.0),
child: TextField( child: Column(
controller: serverKeyController, children: [
decoration: const InputDecoration( Container(
border: OutlineInputBorder(), padding: const EdgeInsets.all(20.0),
labelText: 'Server Key', decoration: BoxDecoration(
), color: AppColors.white,
), borderRadius: BorderRadius.circular(8.0),
), ),
//button untuk save server key child: Column(
ElevatedButton( crossAxisAlignment: CrossAxisAlignment.start,
onPressed: () { children: [
AuthLocalDataSource() Text(
.saveMidtransServerKey(serverKeyController!.text); 'Server Key',
style: TextStyle(
ScaffoldMessenger.of(context).showSnackBar( fontSize: 14,
const SnackBar( fontWeight: FontWeight.w500,
content: Text('Server Key saved'), color: AppColors.black,
backgroundColor: AppColors.primary, ),
),
SpaceHeight(4),
TextField(
controller: serverKeyController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'Server Key',
),
),
],
),
), ),
); const SpaceHeight(20),
}, Align(
child: const Text('Save'), alignment: Alignment.centerRight,
child: Button.filled(
width: context.deviceWidth * 0.2,
height: 44,
onPressed: () {
AuthLocalDataSource()
.saveMidtransServerKey(serverKeyController!.text);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Server Key saved'),
backgroundColor: AppColors.primary,
),
);
},
label: 'Simpan',
),
),
],
),
), ),
], ],
), ),

View File

@ -1,4 +1,7 @@
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';
@ -16,101 +19,154 @@ class _SyncDataPageState extends State<SyncDataPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( backgroundColor: AppColors.background,
title: const Text('Sync Data'),
),
body: Column( body: Column(
children: [ children: [
BlocConsumer<SyncProductBloc, SyncProductState>( SettingsTitle('Sync Data'),
listener: (context, state) { Padding(
state.maybeWhen( padding: const EdgeInsets.all(16.0),
orElse: () {}, child: Column(
error: (message) { children: [
ScaffoldMessenger.of(context).showSnackBar( Container(
SnackBar( padding: const EdgeInsets.all(16.0),
content: Text(message), margin: const EdgeInsets.only(bottom: 16.0),
backgroundColor: Colors.red, decoration: BoxDecoration(
), color: AppColors.white,
); borderRadius: BorderRadius.circular(8.0),
}, ),
loaded: (productResponseModel) async { child: Row(
await ProductLocalDatasource.instance.deleteAllProducts(); mainAxisAlignment: MainAxisAlignment.spaceBetween,
await ProductLocalDatasource.instance.insertProducts( children: [
productResponseModel.data!, Text(
); 'Sinkronasikan Produk',
ScaffoldMessenger.of(context).showSnackBar( style: TextStyle(
const SnackBar( color: AppColors.black,
content: Text('Sync Product Success2'), fontSize: 16,
backgroundColor: Colors.green, fontWeight: FontWeight.w500,
), ),
); ),
}, BlocConsumer<SyncProductBloc, SyncProductState>(
); listener: (context, state) {
}, state.maybeWhen(
builder: (context, state) { orElse: () {},
return state.maybeWhen( error: (message) {
orElse: () { ScaffoldMessenger.of(context).showSnackBar(
return ElevatedButton( SnackBar(
onPressed: () { content: Text(message),
context backgroundColor: Colors.red,
.read<SyncProductBloc>() ),
.add(const SyncProductEvent.syncProduct()); );
}, },
child: const Text('Sync Product')); loaded: (productResponseModel) async {
}, await ProductLocalDatasource.instance
loading: () { .deleteAllProducts();
return const Center( await ProductLocalDatasource.instance
child: CircularProgressIndicator(), .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 Button.filled(
width: 100,
height: 40,
onPressed: () {
context.read<SyncProductBloc>().add(
const SyncProductEvent.syncProduct());
},
label: 'Sinkronasikan',
);
},
loading: () {
return const Center(
child: CircularProgressIndicator(),
);
},
);
},
),
],
),
),
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>(
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 Button.filled(
width: 100,
height: 40,
onPressed: () {
log("🔘 Sync Order button pressed");
log("🔘 SyncOrderBloc instance: ${context.read<SyncOrderBloc>()}");
context
.read<SyncOrderBloc>()
.add(const SyncOrderEvent.syncOrder());
log("🔘 SyncOrderEvent.syncOrder dispatched");
},
label: 'Sinkronasikan',
);
},
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(),
);
},
);
},
)
], ],
), ),
); );

View File

@ -31,11 +31,11 @@ class _TaxPageState extends State<TaxPage> {
serviceChargeValue: serviceChargeValue, serviceChargeValue: serviceChargeValue,
onSave: (newTaxValue, newServiceChargeValue) { onSave: (newTaxValue, newServiceChargeValue) {
context.read<TaxSettingsBloc>().add( context.read<TaxSettingsBloc>().add(
TaxSettingsEvent.updateSettings( TaxSettingsEvent.updateSettings(
taxValue: newTaxValue, taxValue: newTaxValue,
serviceChargeValue: newServiceChargeValue, serviceChargeValue: newServiceChargeValue,
), ),
); );
}, },
), ),
); );
@ -49,11 +49,11 @@ class _TaxPageState extends State<TaxPage> {
serviceChargeValue: serviceChargeValue, serviceChargeValue: serviceChargeValue,
onSave: (newTaxValue, newServiceChargeValue) { onSave: (newTaxValue, newServiceChargeValue) {
context.read<TaxSettingsBloc>().add( context.read<TaxSettingsBloc>().add(
TaxSettingsEvent.updateSettings( TaxSettingsEvent.updateSettings(
taxValue: newTaxValue, taxValue: newTaxValue,
serviceChargeValue: newServiceChargeValue, serviceChargeValue: newServiceChargeValue,
), ),
); );
}, },
), ),
); );
@ -61,91 +61,110 @@ class _TaxPageState extends State<TaxPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SingleChildScrollView( return Column(
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>( ),
builder: (context, state) { Expanded(
return state.when( child: SingleChildScrollView(
initial: () => const Center(child: CircularProgressIndicator()), child: BlocBuilder<TaxSettingsBloc, TaxSettingsState>(
loading: () => const Center(child: CircularProgressIndicator()), builder: (context, state) {
error: (message) => Center(child: Text('Error: $message')), return state.when(
loaded: (taxModel, serviceChargeValue) { initial: () =>
final items = [ const Center(child: CircularProgressIndicator()),
TaxModel(name: 'Biaya Layanan', type: TaxType.layanan, value: serviceChargeValue), loading: () =>
taxModel, 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( return CustomTabBar(
tabTitles: const ['Layanan', 'Pajak'], tabTitles: const ['Layanan', 'Pajak'],
initialTabIndex: 0, initialTabIndex: 0,
tabViews: [ tabViews: [
// LAYANAN TAB // LAYANAN TAB
SizedBox( SizedBox(
child: GridView.builder( child: GridView.builder(
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(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( padding: const EdgeInsets.symmetric(horizontal: 16),
childAspectRatio: 0.85, gridDelegate:
crossAxisCount: 3, const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: 30.0, childAspectRatio: 0.85,
mainAxisSpacing: 30.0, crossAxisCount: 3,
), crossAxisSpacing: 30.0,
itemBuilder: (context, index) { mainAxisSpacing: 30.0,
if (index == 0) { ),
return AddData( itemBuilder: (context, index) {
title: 'Edit Perhitungan', if (index == 0) {
onPressed: () => onAddDataTap(serviceChargeValue, taxModel.value), 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),
); );
} },
final item = items.firstWhere((element) => element.type.isLayanan); ),
return ManageTaxCard(
data: item,
onEditTap: () => onEditTap(item, serviceChargeValue, taxModel.value),
);
},
), ),
),
// PAJAK TAB // PAJAK TAB
SizedBox( SizedBox(
child: GridView.builder( child: GridView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: 2, // Add button + 1 tax item itemCount: 2, // Add button + 1 tax item
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( padding: const EdgeInsets.symmetric(horizontal: 16),
childAspectRatio: 0.85, gridDelegate:
crossAxisCount: 3, const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: 30.0, childAspectRatio: 0.85,
mainAxisSpacing: 30.0, crossAxisCount: 3,
), crossAxisSpacing: 30.0,
itemBuilder: (context, index) { mainAxisSpacing: 30.0,
if (index == 0) { ),
return AddData( itemBuilder: (context, index) {
title: 'Edit Perhitungan', if (index == 0) {
onPressed: () => onAddDataTap(serviceChargeValue, taxModel.value), 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),
); );
} },
final item = items.firstWhere((element) => element.type.isPajak); ),
return ManageTaxCard(
data: item,
onEditTap: () => onEditTap(item, serviceChargeValue, taxModel.value),
);
},
), ),
), ],
], );
); },
}, );
); },
}, ),
), ),
], ),
), ],
); );
} }
} }

View File

@ -3,8 +3,6 @@ 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;
@ -21,18 +19,25 @@ 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: ShapeDecoration( decoration: BoxDecoration(
shape: RoundedRectangleBorder( color: AppColors.white,
side: const BorderSide(width: 1, color: AppColors.card), borderRadius: BorderRadius.circular(8.0),
borderRadius: BorderRadius.circular(19),
),
), ),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon( Container(
Icons.add, width: 56.0,
color: AppColors.primary, 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,
color: AppColors.primary,
),
), ),
const SpaceHeight(8.0), const SpaceHeight(8.0),
Text( Text(

View File

@ -18,11 +18,9 @@ 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: ShapeDecoration( decoration: BoxDecoration(
shape: RoundedRectangleBorder( color: AppColors.white,
side: const BorderSide(width: 1, color: AppColors.card), borderRadius: BorderRadius.circular(8.0),
borderRadius: BorderRadius.circular(19),
),
), ),
child: Stack( child: Stack(
children: [ children: [
@ -36,7 +34,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.disabled.withOpacity(0.4), color: AppColors.primary.withOpacity(0.1),
), ),
child: Text( child: Text(
'${data.value!.replaceAll('.00', '')}%', '${data.value!.replaceAll('.00', '')}%',
@ -48,23 +46,12 @@ class ManageDiscountCard extends StatelessWidget {
), ),
const Spacer(), const Spacer(),
Center( Center(
child: RichText( child: Text(
text: TextSpan( data.name ?? "-",
text: 'Nama Promo : ', style: const TextStyle(
children: [ fontSize: 16,
TextSpan( fontWeight: FontWeight.w600,
text: data.name, color: AppColors.black,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
],
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: AppColors.black,
),
), ),
), ),
), ),

View File

@ -18,11 +18,9 @@ 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: ShapeDecoration( decoration: BoxDecoration(
shape: RoundedRectangleBorder( color: AppColors.white,
side: const BorderSide(width: 1, color: AppColors.card), borderRadius: BorderRadius.circular(8.0),
borderRadius: BorderRadius.circular(19),
),
), ),
child: Stack( child: Stack(
children: [ children: [
@ -36,7 +34,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.disabled.withOpacity(0.4), color: AppColors.primary.withOpacity(0.1),
), ),
child: Text( child: Text(
'${data.value}%', '${data.value}%',
@ -48,23 +46,12 @@ class ManageTaxCard extends StatelessWidget {
), ),
const Spacer(), const Spacer(),
Center( Center(
child: RichText( child: Text(
text: TextSpan( data.type.name,
text: 'Nama Promo : ', style: const TextStyle(
children: [ fontSize: 16,
TextSpan( fontWeight: FontWeight.w600,
text: data.type.name, color: AppColors.black,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
),
),
],
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: AppColors.black,
),
), ),
), ),
), ),

View File

@ -27,12 +27,19 @@ 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: "EnakloPOS - Point of Sale Application" description: "ApskelPOS - 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