Compare commits
7 Commits
1737186db7
...
e825e5daed
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e825e5daed | ||
|
|
8e4a289625 | ||
|
|
8c946ce3d9 | ||
|
|
1384253e8a | ||
|
|
15b27d8f67 | ||
|
|
06bf0d7987 | ||
|
|
a1f3fbe854 |
@ -1,4 +1,4 @@
|
|||||||
# EnakloPOS
|
# ApskelPOS
|
||||||
|
|
||||||
A new Flutter project.
|
A new Flutter project.
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -4,31 +4,31 @@ 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();
|
||||||
|
|
||||||
// Ensure the assets/logo directory exists
|
// Ensure the assets/logo directory exists
|
||||||
final logoDir = Directory('assets/logo');
|
final logoDir = Directory('assets/logo');
|
||||||
if (!await logoDir.exists()) {
|
if (!await logoDir.exists()) {
|
||||||
await logoDir.create(recursive: true);
|
await logoDir.create(recursive: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the generated icon to file
|
// Write the generated icon to file
|
||||||
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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -22,13 +22,14 @@ class RevenueInvoice {
|
|||||||
log("Starting PDF generation for summary report");
|
log("Starting PDF generation for summary report");
|
||||||
log("Summary model: ${summaryModel.toMap()}");
|
log("Summary model: ${summaryModel.toMap()}");
|
||||||
log("Search date formatted: $searchDateFormatted");
|
log("Search date formatted: $searchDateFormatted");
|
||||||
|
|
||||||
final pdf = Document();
|
final pdf = Document();
|
||||||
log("PDF document created");
|
log("PDF document created");
|
||||||
|
|
||||||
// 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,
|
||||||
@ -93,7 +94,7 @@ class RevenueInvoice {
|
|||||||
|
|
||||||
static Widget buildTotal(SummaryModel summaryModel) {
|
static Widget buildTotal(SummaryModel summaryModel) {
|
||||||
log("Building total section with summary model: ${summaryModel.toMap()}");
|
log("Building total section with summary model: ${summaryModel.toMap()}");
|
||||||
|
|
||||||
// Helper function to safely parse string to int
|
// Helper function to safely parse string to int
|
||||||
int safeParseInt(String? value) {
|
int safeParseInt(String? value) {
|
||||||
if (value == null || value.isEmpty) return 0;
|
if (value == null || value.isEmpty) return 0;
|
||||||
@ -104,7 +105,7 @@ class RevenueInvoice {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -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(),
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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() ??
|
||||||
@ -150,4 +148,4 @@ class PaymentMethodReportWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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---
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
65
lib/presentation/report/widgets/report_page_title.dart
Normal file
65
lib/presentation/report/widgets/report_page_title.dart
Normal 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,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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!,
|
||||||
],
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,27 +54,29 @@ 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) {
|
||||||
log("Permission granted, starting PDF generation...");
|
log("Permission granted, starting PDF generation...");
|
||||||
log("Summary data: ${summary.toMap()}");
|
log("Summary data: ${summary.toMap()}");
|
||||||
log("Search date: $searchDateFormatted");
|
log("Search date: $searchDateFormatted");
|
||||||
|
|
||||||
final pdfFile = await RevenueInvoice.generate(
|
final pdfFile = await RevenueInvoice.generate(
|
||||||
summary, searchDateFormatted);
|
summary, searchDateFormatted);
|
||||||
log("PDF file generated: ${pdfFile.path}");
|
log("PDF file generated: ${pdfFile.path}");
|
||||||
log("PDF file exists: ${await pdfFile.exists()}");
|
log("PDF file exists: ${await pdfFile.exists()}");
|
||||||
log("PDF file size: ${await pdfFile.length()} bytes");
|
log("PDF file size: ${await pdfFile.length()} bytes");
|
||||||
|
|
||||||
log("Attempting to open PDF file...");
|
log("Attempting to open PDF file...");
|
||||||
await HelperPdfService.openFile(pdfFile);
|
await HelperPdfService.openFile(pdfFile);
|
||||||
log("PDF file opened successfully");
|
log("PDF file opened successfully");
|
||||||
|
|
||||||
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,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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();
|
||||||
@ -54,7 +52,393 @@ class _FormProductDialogState extends State<FormProductDialog> {
|
|||||||
|
|
||||||
// Check if we're in edit mode
|
// Check if we're in edit mode
|
||||||
isEditMode = widget.product != null;
|
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
|
||||||
|
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) {
|
if (isEditMode) {
|
||||||
// Pre-fill the form with existing product data
|
// Pre-fill the form with existing product data
|
||||||
final product = widget.product!;
|
final product = widget.product!;
|
||||||
@ -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,
|
||||||
@ -169,7 +555,7 @@ class _FormProductDialogState extends State<FormProductDialog> {
|
|||||||
log("No matching category found for product category ID: ${widget.product!.category!.id}");
|
log("No matching category found for product category ID: ${widget.product!.category!.id}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return DropdownButtonHideUnderline(
|
return DropdownButtonHideUnderline(
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -304,11 +690,12 @@ class _FormProductDialogState extends State<FormProductDialog> {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
price: priceValue.toString(),
|
price: priceValue.toString(),
|
||||||
@ -317,9 +704,10 @@ class _FormProductDialogState extends State<FormProductDialog> {
|
|||||||
isFavorite: isBestSeller ? 1 : 0,
|
isFavorite: isBestSeller ? 1 : 0,
|
||||||
printerType: printType,
|
printerType: printType,
|
||||||
);
|
);
|
||||||
|
|
||||||
context.read<UpdateProductBloc>().add(
|
context.read<UpdateProductBloc>().add(
|
||||||
UpdateProductEvent.updateProduct(product, imageFile));
|
UpdateProductEvent.updateProduct(
|
||||||
|
product, imageFile));
|
||||||
},
|
},
|
||||||
label: 'Update Product',
|
label: 'Update Product',
|
||||||
);
|
);
|
||||||
@ -370,7 +758,7 @@ class _FormProductDialogState extends State<FormProductDialog> {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log("isBestSeller: $isBestSeller");
|
log("isBestSeller: $isBestSeller");
|
||||||
final String name = nameController!.text;
|
final String name = nameController!.text;
|
||||||
|
|
||||||
@ -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',
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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,12 +71,13 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.pop();
|
context.pop();
|
||||||
},
|
},
|
||||||
label: 'Simpan',
|
label: 'Simpan',
|
||||||
|
|||||||
@ -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),
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
);
|
||||||
);
|
},
|
||||||
},
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user